Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/content/limitations/runtime-type-checks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ Casts from values whose type is only known at runtime validate the value. For ex
Casts between program classes are checked too: casting an instance to a class it does not extend throws a `TypeError`, where Node.js reads the members structurally. `SC6004` reports calls that pass such an instance to a function that casts it.

A Promise converted from `unknown` to a typed result validates its fulfilled value. A mismatch rejects with a `TypeError`. Rejection reasons and reference equality are preserved across conversions.

A non-null assertion (`value!`) checks the value when it extracts an object, array or other non-nullable member from a union. If the value holds `null` or `undefined`, the assertion throws a `TypeError`, even when the result is never used; Node.js continues until a property of the value is read.
2 changes: 1 addition & 1 deletion docs/src/generated/node-v24-compatibility-meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"schemaVersion": 3,
"nodeVersion": "24.22.0",
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
"artifactVersion": "56f660f5e2eb7391a048",
"artifactVersion": "cade1b8e6e0c5c42fcc9",
"rowCount": 3880
}
10 changes: 5 additions & 5 deletions docs/src/generated/node-v24-compatibility.json
Original file line number Diff line number Diff line change
Expand Up @@ -569,9 +569,9 @@
"apiEntries": 120,
"static": {
"supported": 0,
"partial": 50,
"partial": 51,
"refused": 0,
"not-implemented": 70,
"not-implemented": 69,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
Expand Down Expand Up @@ -57593,9 +57593,9 @@
"anchor": "",
"nodeStability": null,
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
Expand Down
19 changes: 11 additions & 8 deletions internal/compatibility/generated/node-v24-backlog.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"tierItemCount": 3456,
"tiers": {
"static": {
"audit-partial": 1142,
"verify-gap": 2187,
"audit-partial": 1143,
"verify-gap": 2186,
"replace-refusal": 40,
"classify": 2,
"implement": 85
Expand Down Expand Up @@ -57507,12 +57507,15 @@
"priority": "normal",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:process",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.process.execArgv",
"tests": [
"tests/corpus/990-process-basics.ts",
"tests/corpus/2314-process-introspection.ts"
]
}
}
},
Expand Down
12 changes: 8 additions & 4 deletions internal/compatibility/generated/node-v24-internal.json
Original file line number Diff line number Diff line change
Expand Up @@ -596,9 +596,9 @@
"apiEntries": 120,
"static": {
"supported": 0,
"partial": 50,
"partial": 51,
"refused": 0,
"not-implemented": 70,
"not-implemented": 69,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
Expand Down Expand Up @@ -80949,8 +80949,12 @@
"anchor": "",
"nodeStability": null,
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:process"
"status": "partial",
"evidence": "surface-manifest:node-builtin.process.execArgv",
"tests": [
"tests/corpus/990-process-basics.ts",
"tests/corpus/2314-process-introspection.ts"
]
},
"anchorSource": "chapter"
},
Expand Down
3 changes: 2 additions & 1 deletion packages/compiler/src/frontend/lowering/lower-classes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7861,7 +7861,6 @@ export function superCallStmt(
args: IrExpr[],
loc: SrcLoc,
): IrStmt {
const base = superBaseOf(info)!;
const thisRef: IrExpr = {
kind: "varRef",
localId: thisLocal.id,
Expand Down Expand Up @@ -7906,6 +7905,8 @@ export function superCallStmt(
},
loc,
};
// A callable base has no class info, so the base is read after that case.
const base = superBaseOf(info)!;
if (base.builtinError) {
// super(message) into the runtime-provided Error constructor: stamps
// name/message on the (already-allocated) object. Receiver + message
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6020-class-extends-function-constructor.js"
],
"diags": []
}
27 changes: 27 additions & 0 deletions tests/corpus/6020-class-extends-function-constructor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Classes whose base is an ordinary function constructor, with a declared
// constructor calling super and with the implicit derived constructor.
function Shape(name, sides) {
this.name = name;
this.sides = sides;
}
Shape.prototype.describe = function () {
return `${this.name} with ${this.sides} sides`;
};

class Polygon extends Shape {
constructor(name, sides, length) {
super(name, sides);
this.length = length;
}
perimeter() {
return this.sides * this.length;
}
}

class Unnamed extends Shape {}

const square = new Polygon("square", 4, 3);
console.log(square.describe(), square.perimeter());
console.log(square instanceof Polygon);
const shape = new Unnamed("triangle", 3);
console.log(shape.describe(), Object.keys(shape).join(","));
2 changes: 2 additions & 0 deletions tests/harness/corpus-costs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1915,6 +1915,7 @@
"600-closures-basic.ts": 2349,
"601-closures-loops.ts": 1788,
"602-closures-identity-recursion.ts": 2372,
"6020-class-extends-function-constructor.js": 4300,
"603-closures-rc-stress.ts": 1832,
"604-closures-for-of.ts": 2639,
"605-closures-forward-capture-tdz.ts": 3277,
Expand Down Expand Up @@ -4611,6 +4612,7 @@
"600-closures-basic.ts": 2534,
"601-closures-loops.ts": 2297,
"602-closures-identity-recursion.ts": 2427,
"6020-class-extends-function-constructor.js": 7500,
"603-closures-rc-stress.ts": 2589,
"604-closures-for-of.ts": 3514,
"605-closures-forward-capture-tdz.ts": 4638,
Expand Down
32 changes: 32 additions & 0 deletions tests/harness/dyncheck.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,38 @@ console.log("recovered");
expect(r.stdout).toBe("TypeError true\narray: TypeError\nfunc: TypeError\nrecovered\n");
});

test("a non-null assertion on a null object result throws at the assertion, even when the binding is never read", async () => {
// Node lets the null ride until a member read; the compiled program
// throws the stranded TypeError where `!` extracts the object.
const r = await compileAndRun(
"asserted-null-object-result",
`interface Info {
name: string;
base: Info | null;
callable: boolean;
}
function baseOf(info: Info): Info | null {
return info.base;
}
function describe(info: Info): string {
const base = baseOf(info)!;
if (info.callable) return info.name + ": callable base";
return info.name + " extends " + base.name;
}
const root: Info = { name: "Root", base: null, callable: false };
console.log(describe({ name: "Child", base: root, callable: false }));
try {
console.log("unreachable", describe({ name: "Wrapped", base: null, callable: true }));
} catch (e) {
console.log((e as Error).name, (e as Error).message.includes("not representable in the target union"));
}
console.log("recovered");
`,
);
expect(r.exitCode).toBe(0);
expect(r.stdout).toBe("Child extends Root\nTypeError true\nrecovered\n");
});

test("a checker-approved function mismatch strands per piece: mismatched params compile at the flow, void results trap after the call", async () => {
// funcCoerceAdapter's stranded dispositions. Param strand: an array
// literal of arrows collapses to one element signature — the
Expand Down
1 change: 1 addition & 0 deletions tests/harness/native-frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const programs = [
"nullish-long-chain.ts",
"class-array-optional-return.ts",
"contextual-array-union-write.ts",
"6020-class-extends-function-constructor.js",
];

interface LowerRequest {
Expand Down
Loading