From b0ce48f168fdb9170e185ff4ae7f6a4f9aa79ef2 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 11:47:04 -0700 Subject: [PATCH 1/5] Fence refused statements inside capture buffers instead of dropping them A refused statement in a JavaScript function body was dropped without a diagnostic or runtime fence when the body lowered inside a capture buffer whose owner kept the result. tryLowerExpression lowers the receiver of `(function () { ... }).call(this)` or `.apply(...)` and a computed class base that way: the refused statement (for example `console.log(root === module.exports)`) was missing from the kept function, its diagnostic was handed back to the enclosing statement, and that statement, having lowered cleanly, filed it as an informational runtime-fence entry. The program printed nothing for that statement. lowerStmts now emits the statement's runtime fence inside a buffer as well (Lowerer.capturedRuntimeFence, the same message deferToRuntimeFence would give), leaving the diagnostics with the buffer's owner. The three JavaScript class initializer sites that skipped their fence under a buffer (static field, instance field, static initializer block) do the same. An owner that discards the candidate discards the fence with it, so every program that compiled before keeps byte-identical IR unless it contained such a dropped statement: all 3006 corpus programs produce the same IR as before. Co-Authored-By: Claude Opus 5.5 --- .../src/frontend/lowering/lower-classes.ts | 28 +++++--- .../src/frontend/lowering/lower-stmts.ts | 6 ++ .../compiler/src/frontend/lowering/lowerer.ts | 65 ++++++++++++++----- .../6110-cjs-speculative-wrapper-fences.cjs | 26 ++++++++ .../speculative-function-refusal.ts | 14 ++++ tests/harness/errors.test.ts | 53 +++++++++++++++ 6 files changed, 166 insertions(+), 26 deletions(-) create mode 100644 tests/corpus/6110-cjs-speculative-wrapper-fences.cjs create mode 100644 tests/diagnostics/speculative-function-refusal.ts diff --git a/packages/compiler/src/frontend/lowering/lower-classes.ts b/packages/compiler/src/frontend/lowering/lower-classes.ts index 7b2d176c3..f666effd1 100644 --- a/packages/compiler/src/frontend/lowering/lower-classes.ts +++ b/packages/compiler/src/frontend/lowering/lower-classes.ts @@ -4419,6 +4419,7 @@ export function lowerStaticFieldInits(lowerer: Lowerer, info: ClassInfo): IrStmt // instantiation's bindings/context (a no-op for everything else — // generic FAMILIES own their statics and carry no genericInstance). const diagsBefore = lowerer.diags.length; + const sinkMark = lowerer.diagSinkMark(); try { return withInstanceBindings(lowerer, info, () => lowerStaticFieldInitsInner(lowerer, info)); } catch (error) { @@ -4426,8 +4427,11 @@ export function lowerStaticFieldInits(lowerer: Lowerer, info: ClassInfo): IrStmt // Keep their refusal attached to the class declaration instead of // letting a poisoned initializer escape the module lowering pass. if (!(error instanceof PoisonError)) throw error; - if (info.decl && isJsSourceFile(info.decl.getSourceFile()) && lowerer.diagSink === null) { - const fence = lowerer.deferToRuntimeFence(diagsBefore, info.decl, { kind: "statement" }); + if (info.decl && isJsSourceFile(info.decl.getSourceFile())) { + const fence = + lowerer.diagSink === null + ? lowerer.deferToRuntimeFence(diagsBefore, info.decl, { kind: "statement" }) + : lowerer.capturedRuntimeFence(sinkMark, info.decl); if (fence) return [fence]; } return []; @@ -4564,6 +4568,7 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] lowerer.stats.statementsTotal++; lowerer.bumpFileStat(locOf(f.initializer).file, "total"); const diagsBefore = lowerer.diags.length; + const sinkMark = lowerer.diagSinkMark(); try { const previousThis = lowerer.ctx.thisLocal; const receiver = @@ -4599,10 +4604,11 @@ function lowerStaticFieldInitsInner(lowerer: Lowerer, info: ClassInfo): IrStmt[] if (!(e instanceof PoisonError)) throw e; lowerer.stats.statementsFailed++; lowerer.bumpFileStat(locOf(f.initializer).file, "failed"); - if (isJsSourceFile(f.initializer.getSourceFile()) && lowerer.diagSink === null) { - const fence = lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { - kind: "statement", - }); + if (isJsSourceFile(f.initializer.getSourceFile())) { + const fence = + lowerer.diagSink === null + ? lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { kind: "statement" }) + : lowerer.capturedRuntimeFence(sinkMark, f.initializer); if (fence) out.push(fence); } } @@ -7703,6 +7709,7 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL lowerer.stats.statementsTotal++; lowerer.bumpFileStat(locOf(f.initializer).file, "total"); const diagsBefore = lowerer.diags.length; + const sinkMark = lowerer.diagSinkMark(); try { const value = lowerer.lowerExprExpecting(f.initializer, f.type); out.push({ @@ -7717,10 +7724,11 @@ export function fieldInitStmts(lowerer: Lowerer, info: ClassInfo, thisLocal: IrL if (!(e instanceof PoisonError)) throw e; lowerer.stats.statementsFailed++; lowerer.bumpFileStat(locOf(f.initializer).file, "failed"); - if (isJsSourceFile(f.initializer.getSourceFile()) && lowerer.diagSink === null) { - const fence = lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { - kind: "statement", - }); + if (isJsSourceFile(f.initializer.getSourceFile())) { + const fence = + lowerer.diagSink === null + ? lowerer.deferToRuntimeFence(diagsBefore, f.initializer, { kind: "statement" }) + : lowerer.capturedRuntimeFence(sinkMark, f.initializer); if (fence) out.push(fence); } } diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 76a3971d0..148f837ec 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -671,6 +671,7 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir if (sf !== undefined) lowerer.bumpFileStat(sf.fileName, "total"); } const diagsBefore = lowerer.diags.length; + const sinkMark = lowerer.diagSinkMark(); try { let lowered: IrStmt | IrStmt[] | null; let appended = false; @@ -755,6 +756,11 @@ export function lowerStmts(lowerer: Lowerer, stmts: readonly ts.Statement[]): Ir }, }); if (fence) out.push(fence); + } else if (deferFences) { + // Inside a capture buffer the owner keeps the diagnostics, but + // the statement still must not vanish from a result it keeps. + const fence = lowerer.capturedRuntimeFence(sinkMark, stmt); + if (fence) out.push(fence); } } } diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index fe998af36..42cc16705 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -7011,23 +7011,8 @@ export class Lowerer { } this.runtimeFences.push(...captured); - const first = captured[0]; + const fence = this.runtimeFenceStmt(captured[0], node, target.fallback, target.bareMessage); const loc = locOf(node); - const code = first?.code ?? target.fallback!.code; - const baseMessage = first?.message ?? target.fallback!.message; - const messageLoc = first?.loc ?? loc; - const source = first - ? (this.program.getSourceFile(messageLoc.file) ?? node.getSourceFile()) - : node.getSourceFile(); - const pos = ts.getLineAndCharacterOfPosition(source, messageLoc.start); - const fence: IrStmt = { - kind: "runtimeFence", - code, - message: target.bareMessage - ? baseMessage - : `${baseMessage} [${code} at ${messageLoc.file}:${pos.line + 1}]`, - loc, - }; if (target.kind === "statement") return fence; const params = target.params ?? []; @@ -7055,6 +7040,54 @@ export class Lowerer { return { kind: "closure", fnName: fn.name, captures: [], type: target.type, loc }; } + /** The runtimeFence statement that throws `first`'s message (or the + * fallback's) at `node`. */ + private runtimeFenceStmt( + first: ScrDiagnostic | undefined, + node: ts.Node, + fallback: { code: string; message: string } | undefined, + bareMessage = false, + ): IrStmt & { kind: "runtimeFence" } { + const loc = locOf(node); + const code = first?.code ?? fallback?.code ?? "SC1090"; + const baseMessage = + first?.message ?? + fallback?.message ?? + "this statement uses a construct with no static lowering"; + const messageLoc = first?.loc ?? loc; + const source = first + ? (this.program.getSourceFile(messageLoc.file) ?? node.getSourceFile()) + : node.getSourceFile(); + const pos = ts.getLineAndCharacterOfPosition(source, messageLoc.start); + return { + kind: "runtimeFence", + code, + message: bareMessage + ? baseMessage + : `${baseMessage} [${code} at ${messageLoc.file}:${pos.line + 1}]`, + loc, + }; + } + + /** The current length of the capture buffer (diagSink), or 0 without one: + * the mark capturedRuntimeFence reads the refusal from. */ + diagSinkMark(): number { + return this.diagSink?.length ?? 0; + } + + /** The runtimeFence for a JavaScript statement refused while a capture + * buffer (diagSink) is active. The buffer's owner decides whether its + * diagnostics report, and some owners keep the lowered result (a + * successful tryLowerExpression candidate, a computed class base whose + * diagnostics defer to the class). Without a fence the refused statement + * would vanish from that result; with one it throws the refusal when it + * runs, exactly like the fence lowerStmts emits outside a buffer. The + * diagnostics stay in the buffer for its owner. Null without a buffer. */ + capturedRuntimeFence(sinkMark: number, node: ts.Node): IrStmt | null { + if (this.diagSink === null) return null; + return this.runtimeFenceStmt(this.diagSink[sinkMark], node, undefined); + } + /** All diagnostics land here; while a generic instance body is lowering, * the instantiation context is appended so the user knows which concrete * types made the (source-anchored) construct fail. */ diff --git a/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs b/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs new file mode 100644 index 000000000..a26760710 --- /dev/null +++ b/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs @@ -0,0 +1,26 @@ +// Function expressions lowered speculatively — the `.call(this)` and +// `.apply` receivers of CommonJS wrappers, and a computed class base — keep +// every statement of their bodies. A statement with no static lowering +// (here `module.exports` read as a value) compiles to its runtime fence +// instead of vanishing; these never run, so the binary matches Node. +const verbose = process.argv.length > 99; +(function () { + var root = this; + console.log("call wrapper"); + if (verbose) console.log(root === module.exports); + console.log("call wrapper done"); +}).call(this); +(function (label) { + console.log("apply wrapper", label); + if (verbose) console.log(typeof module.exports); +}).apply(null, ["ok"]); +class Base { + hello() { + return "hello"; + } +} +class Derived extends (function () { + if (verbose) console.log(module.exports); + return Base; +})() {} +console.log(new Derived().hello()); diff --git a/tests/diagnostics/speculative-function-refusal.ts b/tests/diagnostics/speculative-function-refusal.ts new file mode 100644 index 000000000..fc48d9935 --- /dev/null +++ b/tests/diagnostics/speculative-function-refusal.ts @@ -0,0 +1,14 @@ +// Function expressions lowered speculatively — an `.apply` receiver and a +// computed class base — report a refused statement in their body like any +// other function: the statement never disappears from the build. +(function () { + console.log("wrapper"); + console.log(eval("1 + 1")); +}).apply(null, []); + +class Base {} +class Derived extends (function () { + console.log(new Function("return 1")()); + return Base; +})() {} +console.log(typeof Derived); diff --git a/tests/harness/errors.test.ts b/tests/harness/errors.test.ts index 28daa88d3..51f04d310 100644 --- a/tests/harness/errors.test.ts +++ b/tests/harness/errors.test.ts @@ -960,3 +960,56 @@ for (const input of [ciphertext, Buffer.from("forged data"), Buffer.alloc(0)]) { expect(r.stderr).toBe(""); }); }); + +// Function expressions the frontend lowers speculatively (the receiver of +// `.call`/`.apply`, a computed class base) keep their refused statements as +// runtime fences. They used to vanish: the capture buffer held the refusal, +// the candidate was kept, and the statement was simply missing from it. +describe("refused statements in speculatively lowered JavaScript functions", () => { + test.each([ + [ + "speculative-call-this", + `(function () { + var root = this; + console.log("before"); + console.log(root === module.exports); + console.log("after"); +}).call(this); +`, + "before\n", + 4, + ], + [ + "speculative-apply", + `(function () { + console.log("before"); + console.log(typeof module.exports); +}).apply(null, []); +`, + "before\n", + 3, + ], + [ + "speculative-class-base", + `class Base {} +class Derived extends (function () { + console.log("before"); + console.log(typeof module.exports); + return Base; +})() {} +console.log(typeof Derived); +`, + "before\n", + 4, + ], + ] as const)("%s", async (name, source, stdout, line) => { + const r = await compileAndRun(name, source, "cjs"); + expect(r.exitCode).toBe(1); + expect(r.stdout).toBe(stdout); + expect(r.stderr).toMatch( + new RegExp( + `^Uncaught Error: 'module\\.exports' is part of the standard library types but has no scriptc lowering yet \\[SC2020 at .*${name}\\.cjs:${line}\\]\\n$`, + ), + ); + }); +}); From 8531a45509303e0db37786863fe67bb077b3bd68 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 12:04:28 -0700 Subject: [PATCH 2/5] Record baselines for the capture-buffer fence fixtures The diagnostics snapshot for speculative-function-refusal.ts and the ts7 order-parity baselines for it and 6110-cjs-speculative-wrapper-fences.cjs. Co-Authored-By: Claude Opus 5.5 --- ...6110-cjs-speculative-wrapper-fences.cjs.json | 6 ++++++ .../speculative-function-refusal.ts.json | 6 ++++++ .../speculative-function-refusal.ts.txt | 17 +++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/speculative-function-refusal.ts.json create mode 100644 tests/harness/__snapshots__/speculative-function-refusal.ts.txt diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs.json new file mode 100644 index 000000000..0a5ce66b0 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/speculative-function-refusal.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/speculative-function-refusal.ts.json new file mode 100644 index 000000000..e24349766 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/speculative-function-refusal.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/speculative-function-refusal.ts" + ], + "diags": [] +} diff --git a/tests/harness/__snapshots__/speculative-function-refusal.ts.txt b/tests/harness/__snapshots__/speculative-function-refusal.ts.txt new file mode 100644 index 000000000..c8b0cd28c --- /dev/null +++ b/tests/harness/__snapshots__/speculative-function-refusal.ts.txt @@ -0,0 +1,17 @@ +speculative-function-refusal.ts:6:15 - error SC2020: 'eval' is part of the standard library types but has no scriptc lowering yet + + 5 | console.log("wrapper"); + 6 | console.log(eval("1 + 1")); + | ^~~~ + 7 | }).apply(null, []); + + hint: runtime code evaluation cannot be compiled ahead of time + +speculative-function-refusal.ts:11:15 - error SC2020: 'new Function' is part of the standard library types but has no scriptc lowering yet + + 10 | class Derived extends (function () { + 11 | console.log(new Function("return 1")()); + | ^~~~~~~~~~~~~~~~~~~~~~~~ + 12 | return Base; + + hint: runtime code generation cannot be compiled ahead of time (the eval stance) — write the function \ No newline at end of file From f6499596099e02d46a31520dee83fc518ad67150 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 09:31:23 -0700 Subject: [PATCH 3/5] Report refused local classes from speculative type queries again The previous change dropped the diagnostics of a function-local class refused during a speculative whole-program type query, expecting the class's own declaration to report them. In JavaScript that declaration only defers them to a runtime fence, so two npm programs that the compile refused (stream-shims and node24-file-url-conversion, both SC1090) began to compile and diverged from Node at run time. The speculative query now leaves the refusal reported, as it was wherever such an analysis already caught the poison, and only stops the poison from escaping the compiler. The JavaScript crash repro becomes a diagnostics fixture (local-class-type-query-js.js); the corpus program 6102 never crashed the base compiler and is removed. Co-Authored-By: Claude Opus 5.5 --- .../compiler/src/frontend/lowering/lowerer.ts | 14 ++----- .../6102-local-class-type-query.js.json | 6 --- .../local-class-type-query-js.js.json | 6 +++ tests/corpus/6102-local-class-type-query.js | 42 ------------------- .../diagnostics/local-class-type-query-js.js | 26 ++++++++++++ .../local-class-type-query-js.js.txt | 6 +++ 6 files changed, 42 insertions(+), 58 deletions(-) delete mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6102-local-class-type-query.js.json create mode 100644 packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query-js.js.json delete mode 100644 tests/corpus/6102-local-class-type-query.js create mode 100644 tests/diagnostics/local-class-type-query-js.js create mode 100644 tests/harness/__snapshots__/local-class-type-query-js.js.txt diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index bf4ed5464..94f99185a 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -9804,27 +9804,21 @@ export class Lowerer { * instance type. Collection and body lowering own a refusal of that * class: the poison reaches the declaration or statement that asked, as * before. A speculative whole-program analysis owns none, so there the - * class is unmappable for the query and its diagnostics are dropped; - * lowering the class's own declaration collects it again and reports - * them if the program reaches it. */ + * refusal stays reported (the build fails with it, as it did where such + * an analysis already caught the poison) and the class is unmappable for + * the query instead of the poison escaping the compiler. */ collectLocalClassForType(decl: ts.ClassLikeDeclaration): boolean { if (this.speculativeTypeQueryDepth === 0 || this.fnStack.length > 0) { this.lowerClassExpressionInfo(decl); return true; } - const sink: ScrDiagnostic[] = []; - const previousSink = this.diagSink; - this.diagSink = sink; try { this.lowerClassExpressionInfo(decl); + return true; } catch (e) { if (e instanceof PoisonError) return false; throw e; - } finally { - this.diagSink = previousSink; } - for (const d of sink) this.pushDiag(d); - return true; } lowerClassExpression(expr: ts.ClassLikeDeclaration): IrExpr { diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6102-local-class-type-query.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6102-local-class-type-query.js.json deleted file mode 100644 index 86c6ba9e8..000000000 --- a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6102-local-class-type-query.js.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "order": [ - "/tests/corpus/6102-local-class-type-query.js" - ], - "diags": [] -} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query-js.js.json b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query-js.js.json new file mode 100644 index 000000000..6bf774cd8 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/diagnostics/local-class-type-query-js.js.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/diagnostics/local-class-type-query-js.js" + ], + "diags": [] +} diff --git a/tests/corpus/6102-local-class-type-query.js b/tests/corpus/6102-local-class-type-query.js deleted file mode 100644 index 92fe35725..000000000 --- a/tests/corpus/6102-local-class-type-query.js +++ /dev/null @@ -1,42 +0,0 @@ -// A whole-program analysis that queries types before reachability is known -// (the computed-key write census here) can name a function-local class whose -// collection is refused. That query has no owner for the refusal: the class -// stays unmapped for it, and the refusal belongs to the class's own -// declaration, which this program never executes (it used to crash the -// compiler with an uncaught PoisonError). -function main(run) { - if (!run) return "skipped"; - const Project = class { - constructor() { - this.name = "p"; - } - close() { - if (this.missingFilesMap) { - this.missingFilesMap.clear(); - this.missingFilesMap = void 0; - } - } - add(k) { - (this.missingFilesMap || (this.missingFilesMap = new Map())).set(k, 1); - } - }; - const holder = { p: new Project() }; - const key = String(run); - holder.p[key] = "q"; - return holder.p.name; -} - -class Counter { - constructor() { - this.counts = {}; - } - bump(key) { - this.counts[key] = (this.counts[key] || 0) + 1; - return this.counts[key]; - } -} - -const counter = new Counter(); -const word = process.argv.length > 99 ? "unused" : "word"; -counter.bump(word); -console.log(main(0), counter.bump(word)); diff --git a/tests/diagnostics/local-class-type-query-js.js b/tests/diagnostics/local-class-type-query-js.js new file mode 100644 index 000000000..2da619133 --- /dev/null +++ b/tests/diagnostics/local-class-type-query-js.js @@ -0,0 +1,26 @@ +// A whole-program analysis that queries types before reachability is known +// (the computed-key write census here, through `holder.p`) names a +// function-local class whose collection is refused. The refusal is reported, +// once, at the class; it used to crash the compiler with an uncaught +// PoisonError. +function main() { + const Project = class _Project { + constructor() { + this.name = "p"; + } + close() { + if (this.missingFilesMap) { + this.missingFilesMap.clear(); + this.missingFilesMap = void 0; + } + } + add(k) { + (this.missingFilesMap || (this.missingFilesMap = new Map())).set(k, 1); + } + }; + const holder = { p: new Project() }; + const key = process.argv.length > 5 ? String(process.argv.length) : "name"; + holder.p[key] = "q"; + console.log(holder.p.name); +} +main(); diff --git a/tests/harness/__snapshots__/local-class-type-query-js.js.txt b/tests/harness/__snapshots__/local-class-type-query-js.js.txt new file mode 100644 index 000000000..60422a5dc --- /dev/null +++ b/tests/harness/__snapshots__/local-class-type-query-js.js.txt @@ -0,0 +1,6 @@ +local-class-type-query-js.js:14:9 - error SC1090: fields assigned outside the constructor's top level ('this.missingFilesMap' would be undefined until the first assignment runs — assign it unconditionally at the top of the constructor) is not supported yet + + 13 | this.missingFilesMap.clear(); + 14 | this.missingFilesMap = void 0; + | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + 15 | } \ No newline at end of file From 5746dc30c5fb4db460563988ef19beb2fe79018e Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 12:05:40 -0700 Subject: [PATCH 4/5] Expect the compile to refuse stream-shims and node24-file-url-conversion again With the local-class refusal reported again, both npm fixtures fail to compile with SC1090 as they did before #1303; restore their build expectations. The deleted corpus program 6102 also leaves the measured cost table. Co-Authored-By: Claude Opus 5.5 --- tests/harness/corpus-costs.json | 2 -- tests/harness/npm.test.ts | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/harness/corpus-costs.json b/tests/harness/corpus-costs.json index 810e6a798..e413e9d3a 100644 --- a/tests/harness/corpus-costs.json +++ b/tests/harness/corpus-costs.json @@ -2209,7 +2209,6 @@ "605-closures-forward-capture-tdz.ts": 3277, "6100-record-get-or-create.ts": 3771, "6101-record-get-or-create-unchecked/main.ts": 2107, - "6102-local-class-type-query.js": 2600, "6103-typeof-builtin-bindings.cjs": 2202, "6104-typeof-builtin-bindings.mjs": 2031, "6105-cjs-top-level-this/main.js": 1371, @@ -5245,7 +5244,6 @@ "605-closures-forward-capture-tdz.ts": 4638, "6100-record-get-or-create.ts": 3173, "6101-record-get-or-create-unchecked/main.ts": 3228, - "6102-local-class-type-query.js": 1967, "6103-typeof-builtin-bindings.cjs": 1796, "6104-typeof-builtin-bindings.mjs": 1715, "6105-cjs-top-level-this/main.js": 3213, diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index c4e70294e..a6cdee2b5 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -73,7 +73,7 @@ const KNOWN: Record = { "node24-env": { run: "SC1090" }, "node24-esm-metadata": { run: "SC1090" }, "node24-event-emitter": { run: "SC2020" }, - "node24-file-url-conversion": { run: "SC2020" }, + "node24-file-url-conversion": { build: "SC1090" }, "node24-fs-descriptors": { run: "SC1090" }, "node24-mime": { run: "SC2020" }, "node24-mime-errors": { run: "SC1090" }, @@ -104,7 +104,7 @@ const KNOWN: Record = { "package-imports": { build: "SC2013" }, "promise-bridge": { build: "SC1090" }, "self-name": { build: "SC2013" }, - "stream-shims": { run: "SC1090" }, + "stream-shims": { build: "SC1090" }, "util-shims": { run: "SC2020" }, "web-streams": { build: "SC2013" }, "workspace-copied": { build: "SC2013" }, From c20c21b22cd6300e7a426d8628e0913117688c2c Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Sun, 18 Oct 2026 13:05:47 -0700 Subject: [PATCH 5/5] Declare the deferred fences of 6110 for the coverage contract The corpus program deliberately carries refused statements on untaken paths; the `// @deferred-fences: 9` header is the coverage sweep's exemption for such programs (9 failed statements across the speculative and final lowerings), so it still runs against Node in the differential oracle. Co-Authored-By: Claude Opus 5.5 --- tests/corpus/6110-cjs-speculative-wrapper-fences.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs b/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs index a26760710..6040f009c 100644 --- a/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs +++ b/tests/corpus/6110-cjs-speculative-wrapper-fences.cjs @@ -1,3 +1,4 @@ +// @deferred-fences: 9 // Function expressions lowered speculatively — the `.call(this)` and // `.apply` receivers of CommonJS wrappers, and a computed class base — keep // every statement of their bodies. A statement with no static lowering