Read the class base after the callable-base case in super calls (#1281)

This commit is contained in:
Chris Tate
2026-10-18 08:14:23 -05:00
committed by GitHub
11 changed files with 97 additions and 19 deletions
@@ -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. 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 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,6 +2,6 @@
"schemaVersion": 3, "schemaVersion": 3,
"nodeVersion": "24.22.0", "nodeVersion": "24.22.0",
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93", "nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
"artifactVersion": "56f660f5e2eb7391a048", "artifactVersion": "cade1b8e6e0c5c42fcc9",
"rowCount": 3880 "rowCount": 3880
} }
@@ -569,9 +569,9 @@
"apiEntries": 120, "apiEntries": 120,
"static": { "static": {
"supported": 0, "supported": 0,
"partial": 50, "partial": 51,
"refused": 0, "refused": 0,
"not-implemented": 70, "not-implemented": 69,
"by-design": 0, "by-design": 0,
"unreviewed": 0, "unreviewed": 0,
"not-applicable": 0 "not-applicable": 0
@@ -57593,9 +57593,9 @@
"anchor": "", "anchor": "",
"nodeStability": null, "nodeStability": null,
"static": { "static": {
"status": "not-implemented", "status": "partial",
"detail": "No static compiler lowering is registered for this API yet.", "detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "registry-gap" "verification": "test-backed"
} }
}, },
{ {
@@ -7,8 +7,8 @@
"tierItemCount": 3456, "tierItemCount": 3456,
"tiers": { "tiers": {
"static": { "static": {
"audit-partial": 1142, "audit-partial": 1143,
"verify-gap": 2187, "verify-gap": 2186,
"replace-refusal": 40, "replace-refusal": 40,
"classify": 2, "classify": 2,
"implement": 85 "implement": 85
@@ -57507,12 +57507,15 @@
"priority": "normal", "priority": "normal",
"tiers": { "tiers": {
"static": { "static": {
"status": "not-implemented", "status": "partial",
"action": "verify-gap", "action": "audit-partial",
"verification": "registry-gap", "verification": "test-backed",
"confidence": "low", "confidence": "high",
"source": "compiler-unmatched:process", "source": "surface-manifest:node-builtin.process.execArgv",
"tests": [] "tests": [
"tests/corpus/990-process-basics.ts",
"tests/corpus/2314-process-introspection.ts"
]
} }
} }
}, },
@@ -596,9 +596,9 @@
"apiEntries": 120, "apiEntries": 120,
"static": { "static": {
"supported": 0, "supported": 0,
"partial": 50, "partial": 51,
"refused": 0, "refused": 0,
"not-implemented": 70, "not-implemented": 69,
"by-design": 0, "by-design": 0,
"unreviewed": 0, "unreviewed": 0,
"not-applicable": 0 "not-applicable": 0
@@ -80949,8 +80949,12 @@
"anchor": "", "anchor": "",
"nodeStability": null, "nodeStability": null,
"static": { "static": {
"status": "not-implemented", "status": "partial",
"evidence": "compiler-unmatched:process" "evidence": "surface-manifest:node-builtin.process.execArgv",
"tests": [
"tests/corpus/990-process-basics.ts",
"tests/corpus/2314-process-introspection.ts"
]
}, },
"anchorSource": "chapter" "anchorSource": "chapter"
}, },
@@ -7861,7 +7861,6 @@ export function superCallStmt(
args: IrExpr[], args: IrExpr[],
loc: SrcLoc, loc: SrcLoc,
): IrStmt { ): IrStmt {
const base = superBaseOf(info)!;
const thisRef: IrExpr = { const thisRef: IrExpr = {
kind: "varRef", kind: "varRef",
localId: thisLocal.id, localId: thisLocal.id,
@@ -7906,6 +7905,8 @@ export function superCallStmt(
}, },
loc, loc,
}; };
// A callable base has no class info, so the base is read after that case.
const base = superBaseOf(info)!;
if (base.builtinError) { if (base.builtinError) {
// super(message) into the runtime-provided Error constructor: stamps // super(message) into the runtime-provided Error constructor: stamps
// name/message on the (already-allocated) object. Receiver + message // name/message on the (already-allocated) object. Receiver + message
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6020-class-extends-function-constructor.js"
],
"diags": []
}
@@ -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
View File
@@ -1915,6 +1915,7 @@
"600-closures-basic.ts": 2349, "600-closures-basic.ts": 2349,
"601-closures-loops.ts": 1788, "601-closures-loops.ts": 1788,
"602-closures-identity-recursion.ts": 2372, "602-closures-identity-recursion.ts": 2372,
"6020-class-extends-function-constructor.js": 4300,
"603-closures-rc-stress.ts": 1832, "603-closures-rc-stress.ts": 1832,
"604-closures-for-of.ts": 2639, "604-closures-for-of.ts": 2639,
"605-closures-forward-capture-tdz.ts": 3277, "605-closures-forward-capture-tdz.ts": 3277,
@@ -4611,6 +4612,7 @@
"600-closures-basic.ts": 2534, "600-closures-basic.ts": 2534,
"601-closures-loops.ts": 2297, "601-closures-loops.ts": 2297,
"602-closures-identity-recursion.ts": 2427, "602-closures-identity-recursion.ts": 2427,
"6020-class-extends-function-constructor.js": 7500,
"603-closures-rc-stress.ts": 2589, "603-closures-rc-stress.ts": 2589,
"604-closures-for-of.ts": 3514, "604-closures-for-of.ts": 3514,
"605-closures-forward-capture-tdz.ts": 4638, "605-closures-forward-capture-tdz.ts": 4638,
+32
View File
@@ -764,6 +764,38 @@ console.log("recovered");
expect(r.stdout).toBe("TypeError true\narray: TypeError\nfunc: TypeError\nrecovered\n"); 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 () => { 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 // funcCoerceAdapter's stranded dispositions. Param strand: an array
// literal of arrows collapses to one element signature — the // literal of arrows collapses to one element signature — the
+1
View File
@@ -65,6 +65,7 @@ export const programs = [
"nullish-long-chain.ts", "nullish-long-chain.ts",
"class-array-optional-return.ts", "class-array-optional-return.ts",
"contextual-array-union-write.ts", "contextual-array-union-write.ts",
"6020-class-extends-function-constructor.js",
]; ];
interface LowerRequest { interface LowerRequest {