TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Error: Debug Failure when importing `AssertionError` from `node:assert/strict`

Open rauschma opened this issue 1 year ago • 4 comments

🔎 Search Terms

Error: Debug Failure

🕗 Version & Regression Information

Happened when using tsc v5.4.5 with @types/node. Wasn’t an issue before (older tsc and older @types/node v20.12.12).

⏯ Playground Link

https://github.com/rauschma/tsc-debug-failure

💻 Code

import { AssertionError } from 'node:assert/strict';

Some of my compilerOptions in tsconfig:

"module": "NodeNext",
"moduleResolution": "NodeNext",
"isolatedModules": true,
"verbatimModuleSyntax": true,
"resolveJsonModule": true,

🙁 Actual behavior

Error: Debug Failure.
    at getTypeOfVariableOrParameterOrPropertyWorker (/usr/local/lib/node_modules/typescript/lib/tsc.js:53005:11)
    at getTypeOfVariableOrParameterOrProperty (/usr/local/lib/node_modules/typescript/lib/tsc.js:52974:20)
    at getTypeOfSymbol (/usr/local/lib/node_modules/typescript/lib/tsc.js:53306:14)
    at getTypeOfAlias (/usr/local/lib/node_modules/typescript/lib/tsc.js:53226:358)
    at getTypeOfSymbol (/usr/local/lib/node_modules/typescript/lib/tsc.js:53318:14)
    at getNarrowedTypeOfSymbol (/usr/local/lib/node_modules/typescript/lib/tsc.js:67681:18)
    at checkIdentifier (/usr/local/lib/node_modules/typescript/lib/tsc.js:67808:16)
    at checkExpressionWorker (/usr/local/lib/node_modules/typescript/lib/tsc.js:76261:16)
    at checkExpression (/usr/local/lib/node_modules/typescript/lib/tsc.js:76216:32)
    at checkNonNullExpression (/usr/local/lib/node_modules/typescript/lib/tsc.js:70497:29)

🙂 Expected behavior

No exception (normal operation)

Additional information about the issue

Possibly helpful:

  • I initially couldn’t tell what the problem was (in my rather large repo) but Nathan Shively-Sanders suggested I change line tsc.js:53005 as follows. That told me that this problem was about AssertionError.
- Debug.assertIsDefined(symbol.valueDeclaration);
+ Debug.assertIsDefined(symbol.valueDeclaration, Debug.formatSymbol(symbol));

rauschma avatar May 14 '24 16:05 rauschma

It repros in the playground too: TS playground. To repro you need to hover over AssertionError. An interesting fact is that the first hover after a source edit triggers the error but the second one doesn't and it renders a good tooltip content.

Andarist avatar May 14 '24 16:05 Andarist

Failing test case:

// @strict: true
// @moduleResolution: node
// @noEmit: true

// @filename: node_modules/@types/node/index.d.ts
/// <reference path="assert.d.ts" />
/// <reference path="assert/strict.d.ts" />

// @filename: node_modules/@types/node/assert/strict.d.ts
declare module "node:assert/strict" {
  import { strict } from "node:assert";
  export = strict;
}

// @filename: node_modules/@types/node/assert.d.ts
declare module "assert" {
  function assert(value: unknown, message?: string | Error): asserts value;
  namespace assert {
    class AssertionError extends Error {
      actual: unknown;
      expected: unknown;
      code: "ERR_ASSERTION";
      constructor(options?: {
        message?: string | undefined;
        actual?: unknown | undefined;
        expected?: unknown | undefined;
      });
    }

    namespace strict {
      type AssertionError = assert.AssertionError;
    }

    const strict: Omit<typeof assert, "strict">;
  }
  export = assert;
}
declare module "node:assert" {
  import assert = require("assert");
  export = assert;
}

// @filename: index.ts
import { AssertionError } from 'node:assert/strict';

throw new AssertionError({
  message: 'Hello',
  actual: 3,
  expected: 3,
});

Andarist avatar May 14 '24 18:05 Andarist

The above test crashes even in TS 4.5 (and back even in 4.0 though with a slightly different error). So this most definitely is not a recent regression at all.

jakebailey avatar May 15 '24 01:05 jakebailey

This is an interesting one. So the symbol that leads to a crash is a result of combineValueAndTypeSymbols. That combines a type symbol with a value symbol that is a mapped symbol. Mapped symbols don't have .valueDeclaration so there is nothing to attach onto the resulting combined symbol here. This leads to a crash.

We could propagate checkFlags here so getTypeOfSymbol could call getTypeOfMappedSymbol instead of getTypeOfVariableOrParameterOrProperty but that doesn't solve anything on its own. The combined symbol isn't truly a mapped symbol so it lacks properties that getTypeOfMappedSymbol expects.

I need to think more about it but it seems like more stuff should get propagated onto that combined symbol from the mapped symbol. getTypeOfSymbol here is interested in the type of the value and the value is typed using a mapped type... so - if I'm not mistaken - the only way to get the correct type is to resolve that mapped type symbol (we just can't do it right now since we lost the information required to do that along the way)

Andarist avatar May 15 '24 08:05 Andarist

Hello there,

Here is my temporary fix, it does seem to work at first glance.

Before:

import assert, { AssertionError } from 'node:assert/strict';
import { AssertionError } from 'node:assert';
import assert from 'node:assert/strict';

It looks like assert.fail (from /strict) is emitting the same AssertionError as the one from the regular assert.

After that, no problem to build with tsc, no Debug Failure anymore.

Only minor issue I have now is that ESLint will complain:

'node:assert' import is restricted from being used. Use node:assert/strict instead.eslint[no-restricted-imports](https://eslint.org/docs/latest/rules/no-restricted-imports)

Cheers!

JulianCataldo avatar Jun 25 '24 15:06 JulianCataldo

I can reproduce it with latest ts 5.5.3

for me it isn't working with @types/node: 20.14.9 but it does work with 18.19.9

  • package.json
{
  "name": "ts-issue",
  "scripts": {
    "typecheck": "tsc --noEmit -p tsconfig.json"
  },
  "license": "ISC",
  "devDependencies": {
    "@types/node": "20.14.9",
    "typescript": "5.5.3",
    "tslib": "2.6.3"
  }
}
  • tsconfig.json
{
  "compileOnSave": false,
  "include": ["./src/issue.ts"],
  "compilerOptions": {
    "baseUrl": ".",
    "rootDir": ".",

    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "importHelpers": true,

    "target": "es2021",
    "module": "CommonJS",
    "moduleResolution": "Node",
    "incremental": true,
    "sourceMap": true,
    "declaration": false,
    "esModuleInterop": true,
    "useDefineForClassFields": false,
    "allowJs": true,
    "resolveJsonModule": true,
    "moduleDetection": "force",
    "isolatedModules": false,
    "lib": ["es2021", "es2022.Error", "ESNext.Disposable"],

    "strict": true,
    "noUncheckedIndexedAccess": false,
    "noImplicitOverride": false,

    "skipLibCheck": true,
    "skipDefaultLibCheck": true,
    "pretty": true,

    "paths": {}
  }
}
  • issue.ts
import { AssertionError } from "node:assert/strict";

throw new AssertionError({
  message: "unreachable assertion",
  operator: "unreachable",
});
  • Output:
..../node_modules/typescript/lib/tsc.js:120127
      throw e;
      ^

Error: Debug Failure.
    at getTypeOfVariableOrParameterOrPropertyWorker (..../node_modules/typescript/lib/tsc.js:54702:11)
    at getTypeOfVariableOrParameterOrProperty (..../node_modules/typescript/lib/tsc.js:54674:20)
    at getTypeOfSymbol (..../node_modules/typescript/lib/tsc.js:55003:14)
    at getTypeOfAlias (..../node_modules/typescript/lib/tsc.js:54923:373)
    at getTypeOfSymbol (..../node_modules/typescript/lib/tsc.js:55015:14)
    at getNarrowedTypeOfSymbol (..../node_modules/typescript/lib/tsc.js:69744:18)
    at checkIdentifier (..../node_modules/typescript/lib/tsc.js:69883:16)
    at checkExpressionWorker (..../node_modules/typescript/lib/tsc.js:78402:16)
    at checkExpression (..../node_modules/typescript/lib/tsc.js:78356:32)
    at checkNonNullExpression (..../node_modules/typescript/lib/tsc.js:72591:29)

Node.js v20.11.0

tonivj5 avatar Jul 02 '24 10:07 tonivj5

@JulianCataldo another workaround is to use assert.AssertionError instead:

import assert from 'node:assert/strict';

const { AssertionError } = assert;
new AssertionError();

// Or...
new assert.AssertionError();

I ran into this issue while porting a project to ESM and Jest tests to native node tests. Tests were passing when running them with Webstorm (using tsx, no type checking), and no type error reported on the IDE. However, running tests with borp failed with a tsc Debug Failure error.

bmenant avatar Nov 06 '24 10:11 bmenant

Here's a more minimal reproduction:

// @filename: index.ts
import { AssertionError } from "./assert";
new AssertionError("assert");

// @filename: assert.d.ts
declare namespace assert {
  class AssertionError extends Error {}

  const strict: Omit<typeof assert, never>;
}

export = assert.strict;

You can do it in one file if you want.

I've filed a fix at https://github.com/DefinitelyTyped/DefinitelyTyped/pull/71889.

LukeAbby avatar Feb 08 '25 21:02 LukeAbby