fix(javascript): expand an escaped receiver's methods once (#1495) (#1517)

Since #1471, escaping an object also escapes every method's results. Each
call through the object escapes it as the receiver, and each escape
re-expanded all of its methods. N methods returning one shared object,
each called once, did N times N work: 2,000 calls took 3.0 s of semantic
analysis and 4,000 took 12 s.

Expansion below references depends only on syntax, so escapes now reuse
it per expression and path. Identifiers that resolve to the same binding
and path collapse to one, because the escape walk treats them alike.
Every escape site is still recorded. The semantic IR is byte-identical
on 50 sources, and the issue's case takes 253 ms at 2,000 and 638 ms at
4,000.

Closes #1495

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-10 02:16:30 -07:00
1 parent b4db35f092
commit 46c9cba03a
2 files changed
+112 -13

No files matched your search

@@ -611,3 +611,41 @@ describe("property mutation collection on minified bundles", () => {
});
}, 30000);
});
describe("results of methods on an escaped receiver", () => {
it("makes every method's distinct result uncertain, not only the called one", () => {
expect(
resultValue(
'const first = { t: "A" }; const second = { t: "B" }; const kept = { t: "C" }; const box = { a() { return first; }, b() { return second; }, c() { return first; } }; box.a(); return [first.t, second.t, kept.t];',
),
).toMatchObject({
status: "array",
items: [
{ value: { status: "unknown" } },
{ value: { status: "unknown" } },
{ value: { status: "literal", value: "C" } },
],
});
});
it("expands a receiver's methods once rather than once per call (#1495)", () => {
const count = 2000;
const methods = Array.from(
{ length: count },
(_, index) => `m${index}() { return shared; },`,
).join(" ");
const calls = Array.from(
{ length: count },
(_, index) => `box.m${index}().p${index} = ${index};`,
).join(" ");
const start = performance.now();
const value = resultValue(
`const shared = { token: "TOKEN" }; const box = { ${methods} }; ${calls} return [shared.token];`,
);
expect(performance.now() - start).toBeLessThan(2000);
expect(value).toMatchObject({
status: "array",
items: [{ value: { status: "unknown" } }],
});
}, 30000);
});