From e597e090b0c8e2e35584b9b3be680ecb8fd5680f Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 02:54:25 +0800 Subject: [PATCH] fix(javascript): share iteration and equality invocation facts --- ...anticAnalysis.language_invocations.test.ts | 23 ++++++++++++++++++- .../javascriptSemanticAsyncEffects.ts | 7 +++--- .../javascriptSemanticGetterResults.ts | 8 +++++++ .../javascriptSemanticLanguageInvocations.ts | 4 ++-- .../javascriptSemanticMemberMutations.ts | 5 ++-- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/domain/javascript/javascriptSemanticAnalysis.language_invocations.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.language_invocations.test.ts index ef03a001eb..aab2be357e 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.language_invocations.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.language_invocations.test.ts @@ -225,9 +225,30 @@ it("follows superclass getter effects through the selected receiver", () => { returned(` const shared = { value: 1 }; class Base { get trigger2() { this.state.value = 2; return 0; } } - class Box extends Base { state = shared; get trigger() { return super.trigger2; } } + class Box extends Base { state = shared; get trigger2() { return 0; } get trigger() { return super.trigger2; } } new Box().trigger; return shared.value; `)?.status, ).toBe("unknown"); }); + +it("summarizes per-iteration disposal inside invoked getters", () => { + expect( + returned(` + const shared = {value: 1, [Symbol.dispose]() {this.value = 2;}}; + const box = {state: shared, get trigger() {for (using owned of [this.state]) {} return 0;}}; + box.trigger; + return shared.value; + `)?.status, + ).toBe("unknown"); +}); + +it("keeps same-receiver equality in an inert getter precise", () => { + expect( + returned(` + const state = {value: 1, get trigger() {return this == this;}}; + state.trigger; + return state.value; + `), + ).toEqual({ status: "literal", value: 1 }); +}); diff --git a/src/domain/javascript/javascriptSemanticAsyncEffects.ts b/src/domain/javascript/javascriptSemanticAsyncEffects.ts index e9b9c92fa2..9bdff8ac9e 100644 --- a/src/domain/javascript/javascriptSemanticAsyncEffects.ts +++ b/src/domain/javascript/javascriptSemanticAsyncEffects.ts @@ -196,10 +196,9 @@ const emitterIdentity = ( ...read.binding.initializers, ...read.binding.referenceInitializers, ]; - const replaced = [ - ...binding.initializers, - ...binding.referenceInitializers, - ].some(({ assignment }) => assignment); + const replaced = binding.definitions.some( + ({ kind }) => kind === "assignment", + ); const origin = origins.length === 1 ? origins[0] : undefined; const complete = !replaced || diff --git a/src/domain/javascript/javascriptSemanticGetterResults.ts b/src/domain/javascript/javascriptSemanticGetterResults.ts index d1c01e813e..5b8433cd82 100644 --- a/src/domain/javascript/javascriptSemanticGetterResults.ts +++ b/src/domain/javascript/javascriptSemanticGetterResults.ts @@ -365,6 +365,14 @@ class GetterResultWalker { #visit(current: GetterWork): void { const expression = unwrapJavaScriptExpression(current.node).node; if (t.isIdentifier(expression) && this.#alias(current, expression)) return; + if (t.isForOfStatement(expression)) { + this.#enqueue(() => ({ + ...current, + node: expression.right, + path: [null, ...current.path], + })); + return; + } if (t.isThisExpression(expression) || t.isSuper(expression)) this.#thisValue(current, expression); else if ( diff --git a/src/domain/javascript/javascriptSemanticLanguageInvocations.ts b/src/domain/javascript/javascriptSemanticLanguageInvocations.ts index 419fe8f352..6c26a5d2ae 100644 --- a/src/domain/javascript/javascriptSemanticLanguageInvocations.ts +++ b/src/domain/javascript/javascriptSemanticLanguageInvocations.ts @@ -10,8 +10,8 @@ export const semanticCoercionOperands = ( if (node.operator === "instanceof") return [node.left, node.right]; if ( ["==", "!="].includes(node.operator) && - isObject(node.left) && - isObject(node.right) + ((t.isThisExpression(node.left) && t.isThisExpression(node.right)) || + (isObject(node.left) && isObject(node.right))) ) return []; return node.operator === "in" ? [node.left] : [node.left, node.right]; diff --git a/src/domain/javascript/javascriptSemanticMemberMutations.ts b/src/domain/javascript/javascriptSemanticMemberMutations.ts index 7086dcb6d5..d4c7befa80 100644 --- a/src/domain/javascript/javascriptSemanticMemberMutations.ts +++ b/src/domain/javascript/javascriptSemanticMemberMutations.ts @@ -323,11 +323,10 @@ const accessorEffectOperands = ( }); if ( t.isVariableDeclarator(node) && - node.init != null && t.isVariableDeclaration(parent) && (parent.kind === "using" || parent.kind === "await using") ) - operands.push({ kind: "escape", node: node.init }); + operands.push({ kind: "escape", node: node.init ?? node.id }); for (const child of childNodes(node).reverse()) pending.push({ node: child, parent: node }); } @@ -445,7 +444,7 @@ export function* collectSemanticMemberMutationsSteps( const target = receiverReference ? [...receiver, ...reference.path] : reference.path; - if (operand.kind === "escape") + if (operand.kind === "escape" || t.isSuper(operand.node)) effects.push({ node: targetRoot, path: target }); else pending.push({