From 7be83d8e114c353faa92ffef8352f2e780fb0e31 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 15:47:57 -0500 Subject: [PATCH 1/2] Fix the merge and address review feedback --- .gitignore | 2 ++ packages/compiler/node_modules | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 120000 packages/compiler/node_modules diff --git a/.gitignore b/.gitignore index 81b92ddd5..88dfdc35b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ node_modules/ +# A symlinked node_modules is a file to git, which node_modules/ does not match. +/packages/*/node_modules .pnpm-store/ # test-fixture packages are TEST DATA, committed on purpose !tests/diagnostics/npm-imports/node_modules/ diff --git a/packages/compiler/node_modules b/packages/compiler/node_modules deleted file mode 120000 index 6b01b0eda..000000000 --- a/packages/compiler/node_modules +++ /dev/null @@ -1 +0,0 @@ -../../../shape-reg/packages/compiler/node_modules \ No newline at end of file From 6aaaea04e4c59ffa38f40e9cf44758c2d3b7b3bb Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 25 Oct 2026 20:31:37 -0500 Subject: [PATCH 2/2] Keep one source label per node in the shape analyzer --- .../src/frontend/shape-inference/analyze.ts | 98 ++++++++++--------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/packages/compiler/src/frontend/shape-inference/analyze.ts b/packages/compiler/src/frontend/shape-inference/analyze.ts index 2a0ee09a1..bde5e58dd 100644 --- a/packages/compiler/src/frontend/shape-inference/analyze.ts +++ b/packages/compiler/src/frontend/shape-inference/analyze.ts @@ -317,6 +317,14 @@ export function analyzeShapes(program: ts.Program, checker: ts.TypeChecker): Sha } class Analyzer { + /** locLabel per node: sink reasons and atom labels name the same sites + * over and over, and each label reads the file's text for its line. */ + private readonly locLabels = new Map(); + private loc(node: Node): string { + let label = this.locLabels.get(node); + if (label === undefined) this.locLabels.set(node, (label = locLabel(node))); + return label; + } readonly atoms = new AtomTable(); readonly nodes: ValueNode[] = []; readonly functions = new Map(); @@ -830,7 +838,7 @@ class Analyzer { if (this.traceTop) this.nodeLabel.set( id, - `binding ${(decl as { name?: { text?: string } }).name?.text ?? "?"}@${locLabel(decl)}${this.ctx !== "" ? ` [${this.ctx}]` : ""}`, + `binding ${(decl as { name?: { text?: string } }).name?.text ?? "?"}@${this.loc(decl)}${this.ctx !== "" ? ` [${this.ctx}]` : ""}`, ); } return id; @@ -908,7 +916,7 @@ class Analyzer { const nameNode = (fn as { name?: Node }).name; const name = nameNode !== undefined ? (propertyNameText(nameNode) ?? "") : null; const owner = ownerClass !== null ? this.className(ownerClass) : null; - const label = `${owner !== null ? `${owner}.` : ""}${name ?? (ts.isConstructorDeclaration(node) ? "constructor" : "")}@${locLabel(node)}`; + const label = `${owner !== null ? `${owner}.` : ""}${name ?? (ts.isConstructorDeclaration(node) ? "constructor" : "")}@${this.loc(node)}`; const info: FunctionInfo = { node, ctx, @@ -952,7 +960,7 @@ class Analyzer { if (p.dotDotDotToken !== undefined) { const arr = this.atoms.intern(`rest:${this.nodes.length}`, () => ({ kind: "array", - label: `rest@${locLabel(p)}`, + label: `rest@${this.loc(p)}`, node: p, })); this.add(this.binding(p), arr); @@ -998,7 +1006,7 @@ class Analyzer { const p = decl.parent; if (p !== undefined && ts.isVariableDeclaration(p) && ts.isIdentifier(p.name)) return p.name.text; - return ``; + return ``; } classAtoms(decl: Node): { cls: Atom; inst: Atom } { @@ -1039,7 +1047,7 @@ class Analyzer { ctx: "", root: null, walked: true, - label: `${label}.${suffix}@${locLabel(decl)}`, + label: `${label}.${suffix}@${this.loc(decl)}`, params: [], rest: -1, thisNode: this.newNode(), @@ -1187,7 +1195,7 @@ class Analyzer { if (this.isFactory(info.node)) { const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({ kind: "dictionary", - label: `Object.create@${locLabel(site)}`, + label: `Object.create@${this.loc(site)}`, node: site, })); this.add(dst, d); @@ -1253,7 +1261,7 @@ class Analyzer { syntheticArray(site: Node, tag: string): Atom { return this.atoms.intern(`arr:${tag}:${this.nodeKey(site)}`, () => ({ kind: "array", - label: `${tag}[]@${locLabel(site)}`, + label: `${tag}[]@${this.loc(site)}`, node: site, })); } @@ -1361,7 +1369,7 @@ class Analyzer { return; } if (ts.isThrowStatement(s)) { - this.sink(this.expr(s.expression), `thrown at ${locLabel(s)}`); + this.sink(this.expr(s.expression), `thrown at ${this.loc(s)}`); return; } if ( @@ -1437,7 +1445,7 @@ class Analyzer { Symbol.iterator member have a computed key; they are G sites. */ } else { this.add(dst, this.top); - if (a.kind !== "top") this.escape(a, `iterated at ${locLabel(site)}`); + if (a.kind !== "top") this.escape(a, `iterated at ${this.loc(site)}`); } }); } @@ -1453,7 +1461,7 @@ class Analyzer { if (this.traceTop && !this.nodeLabel.has(id)) this.nodeLabel.set( id, - `expr ${e.getText().slice(0, 40).replace(/\s+/g, " ")}@${locLabel(e)}`, + `expr ${e.getText().slice(0, 40).replace(/\s+/g, " ")}@${this.loc(e)}`, ); return id; } @@ -1586,7 +1594,7 @@ class Analyzer { if (ts.isTaggedTemplateExpression(e)) return this.taggedTemplate(e); if (ts.isYieldExpression(e)) { if (e.expression !== undefined) - this.sink(this.expr(e.expression), `yielded at ${locLabel(e)}`); + this.sink(this.expr(e.expression), `yielded at ${this.loc(e)}`); return this.constNode(this.top); } if (ts.isMetaProperty(e)) return this.constNode(this.top); @@ -1841,7 +1849,7 @@ class Analyzer { const spreads: number[] = []; const inPackage = isPackageJsFile(e.getSourceFile()); const label = (): string => - `{${keys.slice(0, 6).join(",")}${keys.length > 6 ? ",…" : ""}${keysKnown ? "" : ",…"}}@${locLabel(e)}`; + `{${keys.slice(0, 6).join(",")}${keys.length > 6 ? ",…" : ""}${keysKnown ? "" : ",…"}}@${this.loc(e)}`; // keys first, so the label is complete for (const p of list(e.properties)) { if (ts.isSpreadAssignment(p)) { @@ -2053,7 +2061,7 @@ class Analyzer { arrayLiteral(e: ts.ArrayLiteralExpression): number { const atom = this.atoms.intern(`arr:${this.nodeKey(e)}`, () => ({ kind: "array", - label: `[]@${locLabel(e)}`, + label: `[]@${this.loc(e)}`, node: e, })); const n = this.newNode(); @@ -2175,7 +2183,7 @@ class Analyzer { if (ts.isIdentifier(t)) { const decl = this.resolve(t); if (decl === undefined || decl.getSourceFile().isDeclarationFile) { - this.sink(value, `global write at ${locLabel(t)}`); + this.sink(value, `global write at ${this.loc(t)}`); return; } this.edge(value, this.binding(decl)); @@ -2421,7 +2429,7 @@ class Analyzer { switch (a.kind) { case "top": this.noteTopWrite(name, site); - this.sink(value, `stored into ⊤.${name} at ${locLabel(site)}`); + this.sink(value, `stored into ⊤.${name} at ${this.loc(site)}`); return; case "instance": { const member = this.lookupMember(a.node!, name, false); @@ -2448,7 +2456,7 @@ class Analyzer { case "prim": return; default: - this.sink(value, `stored into ${a.label} at ${locLabel(site)}`); + this.sink(value, `stored into ${a.label} at ${this.loc(site)}`); } }); } @@ -2497,7 +2505,7 @@ class Analyzer { switch (a.kind) { case "top": this.noteTopWrite(null, site); - this.sink(value, `stored into ⊤[k] at ${locLabel(site)}`); + this.sink(value, `stored into ⊤[k] at ${this.loc(site)}`); return; case "array": // An index, or a named property under an unknown key. @@ -2515,7 +2523,7 @@ class Analyzer { return; default: // As a store under a name: a builtin, promise or collection keeps it where it isn't tracked. - this.sink(value, `stored into ${a.label}[k] at ${locLabel(site)}`); + this.sink(value, `stored into ${a.label}[k] at ${this.loc(site)}`); } }); } @@ -2591,7 +2599,7 @@ class Analyzer { if (inst.kind === "instance") this.callConstructorChain(base, inst, args, spread, e); }); } else if (base === undefined) - for (const a of args) this.sink(a, `super(...) at ${locLabel(e)}`); + for (const a of args) this.sink(a, `super(...) at ${this.loc(e)}`); return dst; } // CommonJS `require("")` of an analyzed module: its exports (the @@ -2686,9 +2694,9 @@ class Analyzer { case "prim": return; default: - for (const x of args) this.sink(x, `argument of ⊤ call at ${locLabel(site)}`); + for (const x of args) this.sink(x, `argument of ⊤ call at ${this.loc(site)}`); if (recv !== null && a.kind === "top") - this.sink(recv, `receiver of ⊤ call at ${locLabel(site)}`); + this.sink(recv, `receiver of ⊤ call at ${this.loc(site)}`); this.add(dst, this.top); } }); @@ -2731,7 +2739,7 @@ class Analyzer { this.constructBuiltin(a.path ?? "", args, dst, e); return; default: - for (const x of args) this.sink(x, `argument of ⊤ constructor at ${locLabel(e)}`); + for (const x of args) this.sink(x, `argument of ⊤ constructor at ${this.loc(e)}`); this.add(dst, this.top); } }); @@ -2746,7 +2754,7 @@ class Analyzer { case "WeakSet": { const c = this.atoms.intern(`coll:${this.nodeKey(site)}`, () => ({ kind: "collection", - label: `${path}@${locLabel(site)}`, + label: `${path}@${this.loc(site)}`, node: site, })); this.add(dst, c); @@ -2760,7 +2768,7 @@ class Analyzer { this.add(dst, this.builtin("Error.instance")); return; case "Promise": { - const p = this.promiseAtom(site, locLabel(site)); + const p = this.promiseAtom(site, this.loc(site)); this.add(this.field(p, "value"), this.top); this.add(dst, p); if (args[0] !== undefined) { @@ -2793,11 +2801,11 @@ class Analyzer { // The handler's traps run on every operation with the target. if (args[0] !== undefined) this.ops.push({ kind: "proxy", recv: args[0], name: null, node: site }); - for (const x of args) this.sink(x, `argument of new Proxy at ${locLabel(site)}`); + for (const x of args) this.sink(x, `argument of new Proxy at ${this.loc(site)}`); this.add(dst, this.top); return; default: - for (const x of args) this.sink(x, `argument of new ${path} at ${locLabel(site)}`); + for (const x of args) this.sink(x, `argument of new ${path} at ${this.loc(site)}`); this.add(dst, this.top); } } @@ -2824,13 +2832,13 @@ class Analyzer { this.callFunction(this.infoOfAtom(f)!, null, cbArgs, false, ret ?? this.newNode(), site); this.forceLoop = saved; } else if (f.kind === "top" || f.kind === "builtin") { - for (const x of cbArgs) this.sink(x, `argument of ⊤ callback at ${locLabel(site)}`); + for (const x of cbArgs) this.sink(x, `argument of ⊤ callback at ${this.loc(site)}`); if (ret !== null) this.add(ret, this.top); } }); }; const escapeAll = (): void => { - for (const x of args) this.sink(x, `argument of ${path} at ${locLabel(site)}`); + for (const x of args) this.sink(x, `argument of ${path} at ${this.loc(site)}`); this.add(dst, this.top); }; // Methods of builtin receivers. @@ -2948,7 +2956,7 @@ class Analyzer { return; } escapeAll(); - this.escape(recv, `array.${method} at ${locLabel(site)}`); + this.escape(recv, `array.${method} at ${this.loc(site)}`); return; } if (path.startsWith("string.") || path.startsWith("number.") || path.startsWith("boolean.")) { @@ -2977,7 +2985,7 @@ class Analyzer { return; } if (path.startsWith("promise.")) { - const out = this.promiseAtom(site, locLabel(site)); + const out = this.promiseAtom(site, this.loc(site)); const value = this.field(recv, "value"); const ret = this.newNode(); if (method === "then") { @@ -3047,7 +3055,7 @@ class Analyzer { if (info !== undefined && args.length > 1) { this.activate(info); for (const p of info.params) this.add(p, this.top); - for (const x of args.slice(1)) this.sink(x, `bound argument at ${locLabel(site)}`); + for (const x of args.slice(1)) this.sink(x, `bound argument at ${this.loc(site)}`); } this.add(dst, recv); return; @@ -3145,7 +3153,7 @@ class Analyzer { for (const s of args.slice(1)) this.copyFields(s, t, site); if (t.kind === "top") for (const s of args.slice(1)) - this.sink(s, `Object.assign into ⊤ at ${locLabel(site)}`); + this.sink(s, `Object.assign into ⊤ at ${this.loc(site)}`); }); this.edge(arg(0), dst); return; @@ -3154,20 +3162,20 @@ class Analyzer { case "Object.defineProperties": case "Reflect.defineProperty": this.ops.push({ kind: "defineProperty", recv: arg(0), name: null, node: site }); - this.sink(arg(2), `property descriptor at ${locLabel(site)}`); - this.sink(arg(1), `property descriptor at ${locLabel(site)}`); + this.sink(arg(2), `property descriptor at ${this.loc(site)}`); + this.sink(arg(1), `property descriptor at ${this.loc(site)}`); this.edge(arg(0), dst); return; case "Object.setPrototypeOf": case "Reflect.setPrototypeOf": this.ops.push({ kind: "setPrototypeOf", recv: arg(0), name: null, node: site }); - this.sink(arg(1), `prototype at ${locLabel(site)}`); + this.sink(arg(1), `prototype at ${this.loc(site)}`); this.edge(arg(0), dst); return; case "Object.create": { const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({ kind: "dictionary", - label: `Object.create@${locLabel(site)}`, + label: `Object.create@${this.loc(site)}`, node: site, })); this.add(dst, d); @@ -3176,7 +3184,7 @@ class Analyzer { case "Object.fromEntries": { const d = this.atoms.intern(`dict:${this.nodeKey(site)}`, () => ({ kind: "dictionary", - label: `fromEntries@${locLabel(site)}`, + label: `fromEntries@${this.loc(site)}`, node: site, })); const pairs = this.newNode(); @@ -3225,7 +3233,7 @@ class Analyzer { site, ); else if (f.kind === "top") - this.sink(revived, `JSON.parse reviver at ${locLabel(site)}`); + this.sink(revived, `JSON.parse reviver at ${this.loc(site)}`); }); this.edge(revived, dst); return; @@ -3252,7 +3260,7 @@ class Analyzer { site, ); else if (f.kind === "top") - this.sink(serialized, `JSON.stringify replacer at ${locLabel(site)}`); + this.sink(serialized, `JSON.stringify replacer at ${this.loc(site)}`); }); prim("string"); prim("undefined"); @@ -3281,7 +3289,7 @@ class Analyzer { if (c.kind === "builtin" && c.path === "Proxy") this.ops.push({ kind: "proxy", recv: listed, name: null, node: site }); }); - this.sink(listed, `argument of ${path} at ${locLabel(site)}`); + this.sink(listed, `argument of ${path} at ${this.loc(site)}`); escapeAll(); return; } @@ -3310,23 +3318,23 @@ class Analyzer { prim("bigint"); return; case "Promise.resolve": { - const p = this.promiseAtom(site, locLabel(site)); + const p = this.promiseAtom(site, this.loc(site)); if (args[0] !== undefined) this.awaitInto(args[0], this.field(p, "value")); else this.add(this.field(p, "value"), this.prim("undefined")); this.add(dst, p); return; } case "Promise.reject": { - const p = this.promiseAtom(site, locLabel(site)); + const p = this.promiseAtom(site, this.loc(site)); this.add(dst, p); - if (args[0] !== undefined) this.sink(args[0], `rejection at ${locLabel(site)}`); + if (args[0] !== undefined) this.sink(args[0], `rejection at ${this.loc(site)}`); return; } case "Promise.all": case "Promise.allSettled": case "Promise.race": case "Promise.any": { - const p = this.promiseAtom(site, locLabel(site)); + const p = this.promiseAtom(site, this.loc(site)); const items = this.newNode(); if (args[0] !== undefined) this.iterateInto(args[0], items, site); if (path === "Promise.all") { @@ -3340,7 +3348,7 @@ class Analyzer { return; } case "promise.resolver": - for (const x of args) this.sink(x, `promise resolution at ${locLabel(site)}`); + for (const x of args) this.sink(x, `promise resolution at ${this.loc(site)}`); return; } if (path.startsWith("console.")) {