Report refused local classes from speculative type queries again (#1321)

This commit is contained in:
Chris Tate
2026-10-18 15:04:40 -05:00
committed by GitHub
8 changed files with 44 additions and 62 deletions
@@ -9872,27 +9872,21 @@ export class Lowerer {
* instance type. Collection and body lowering own a refusal of that * instance type. Collection and body lowering own a refusal of that
* class: the poison reaches the declaration or statement that asked, as * class: the poison reaches the declaration or statement that asked, as
* before. A speculative whole-program analysis owns none, so there the * before. A speculative whole-program analysis owns none, so there the
* class is unmappable for the query and its diagnostics are dropped; * refusal stays reported (the build fails with it, as it did where such
* lowering the class's own declaration collects it again and reports * an analysis already caught the poison) and the class is unmappable for
* them if the program reaches it. */ * the query instead of the poison escaping the compiler. */
collectLocalClassForType(decl: ts.ClassLikeDeclaration): boolean { collectLocalClassForType(decl: ts.ClassLikeDeclaration): boolean {
if (this.speculativeTypeQueryDepth === 0 || this.fnStack.length > 0) { if (this.speculativeTypeQueryDepth === 0 || this.fnStack.length > 0) {
this.lowerClassExpressionInfo(decl); this.lowerClassExpressionInfo(decl);
return true; return true;
} }
const sink: ScrDiagnostic[] = [];
const previousSink = this.diagSink;
this.diagSink = sink;
try { try {
this.lowerClassExpressionInfo(decl); this.lowerClassExpressionInfo(decl);
return true;
} catch (e) { } catch (e) {
if (e instanceof PoisonError) return false; if (e instanceof PoisonError) return false;
throw e; throw e;
} finally {
this.diagSink = previousSink;
} }
for (const d of sink) this.pushDiag(d);
return true;
} }
lowerClassExpression(expr: ts.ClassLikeDeclaration): IrExpr { lowerClassExpression(expr: ts.ClassLikeDeclaration): IrExpr {
@@ -1,6 +0,0 @@
{
"order": [
"<repo>/tests/corpus/6102-local-class-type-query.js"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/diagnostics/local-class-type-query-js.js"
],
"diags": []
}
@@ -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));
@@ -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();
@@ -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 | }
-2
View File
@@ -2210,7 +2210,6 @@
"605-closures-forward-capture-tdz.ts": 3277, "605-closures-forward-capture-tdz.ts": 3277,
"6100-record-get-or-create.ts": 3771, "6100-record-get-or-create.ts": 3771,
"6101-record-get-or-create-unchecked/main.ts": 2107, "6101-record-get-or-create-unchecked/main.ts": 2107,
"6102-local-class-type-query.js": 2600,
"6103-typeof-builtin-bindings.cjs": 2202, "6103-typeof-builtin-bindings.cjs": 2202,
"6104-typeof-builtin-bindings.mjs": 2031, "6104-typeof-builtin-bindings.mjs": 2031,
"6105-cjs-top-level-this/main.js": 1371, "6105-cjs-top-level-this/main.js": 1371,
@@ -5247,7 +5246,6 @@
"605-closures-forward-capture-tdz.ts": 4638, "605-closures-forward-capture-tdz.ts": 4638,
"6100-record-get-or-create.ts": 3173, "6100-record-get-or-create.ts": 3173,
"6101-record-get-or-create-unchecked/main.ts": 3228, "6101-record-get-or-create-unchecked/main.ts": 3228,
"6102-local-class-type-query.js": 1967,
"6103-typeof-builtin-bindings.cjs": 1796, "6103-typeof-builtin-bindings.cjs": 1796,
"6104-typeof-builtin-bindings.mjs": 1715, "6104-typeof-builtin-bindings.mjs": 1715,
"6105-cjs-top-level-this/main.js": 3213, "6105-cjs-top-level-this/main.js": 3213,
+2 -2
View File
@@ -73,7 +73,7 @@ const KNOWN: Record<string, Known> = {
"node24-env": { run: "SC1090" }, "node24-env": { run: "SC1090" },
"node24-esm-metadata": { run: "SC1090" }, "node24-esm-metadata": { run: "SC1090" },
"node24-event-emitter": { run: "SC2020" }, "node24-event-emitter": { run: "SC2020" },
"node24-file-url-conversion": { run: "SC2020" }, "node24-file-url-conversion": { build: "SC1090" },
"node24-fs-descriptors": { run: "SC1090" }, "node24-fs-descriptors": { run: "SC1090" },
"node24-mime": { run: "SC2020" }, "node24-mime": { run: "SC2020" },
"node24-mime-errors": { run: "SC1090" }, "node24-mime-errors": { run: "SC1090" },
@@ -104,7 +104,7 @@ const KNOWN: Record<string, Known> = {
"package-imports": { build: "SC2013" }, "package-imports": { build: "SC2013" },
"promise-bridge": { build: "SC1090" }, "promise-bridge": { build: "SC1090" },
"self-name": { build: "SC2013" }, "self-name": { build: "SC2013" },
"stream-shims": { run: "SC1090" }, "stream-shims": { build: "SC1090" },
"util-shims": { run: "SC2020" }, "util-shims": { run: "SC2020" },
"web-streams": { build: "SC2013" }, "web-streams": { build: "SC2013" },
"workspace-copied": { build: "SC2013" }, "workspace-copied": { build: "SC2013" },