From 39c820c90d4de0c9f1abd6defd6415e4e7e0c8a7 Mon Sep 17 00:00:00 2001 From: gensokyo-dev Date: Sat, 14 Nov 2026 13:26:17 +0000 Subject: [PATCH] fix(javascript): project getter class static work onto the captured receiver Static blocks and static field values run when a class is defined, so a getter that returns a class whose static work writes through an alias of the getter's receiver changes that receiver. The definition-time walk now visits static work too; `this` inside it stays the class. Refs #9229 Co-Authored-By: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> --- ...ptSemanticAnalysis.getter_receivers.test.ts | 18 ++++++++++++++++++ .../javascriptSemanticMemberMutations.ts | 17 ++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/domain/javascript/javascriptSemanticAnalysis.getter_receivers.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.getter_receivers.test.ts index 479639fe1c..c0a0cbda07 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.getter_receivers.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.getter_receivers.test.ts @@ -233,6 +233,24 @@ describe("getter receiver mutation aliases (#1665, #2654)", () => { ).toMatchObject({ status: "literal", value: 1 }); }); + it.each([ + ["static block", "class { static { self.x = 2; } }"], + ["static field value", "class { static s = (self.x = 2); }"], + [ + "static block of a nested heritage class", + "class extends (class { static { self.x = 2; } }) {}", + ], + ])( + "invalidates the receiver after a %s writes through a captured alias", + (_label, result) => { + expect( + resultValue( + `const box = { x: 1, get trigger() { const self = this; return ${result}; } }; box.trigger; return box.x;`, + ), + ).toMatchObject({ status: "unknown" }); + }, + ); + it("terminates recursive receiver getters", () => { expect( resultValue( diff --git a/src/domain/javascript/javascriptSemanticMemberMutations.ts b/src/domain/javascript/javascriptSemanticMemberMutations.ts index cc6a2d65a2..a8172a74ef 100644 --- a/src/domain/javascript/javascriptSemanticMemberMutations.ts +++ b/src/domain/javascript/javascriptSemanticMemberMutations.ts @@ -405,12 +405,27 @@ const computedDefinitionMembers = (node: t.Function | t.Class): t.Node[] => { ); }; -/** Expressions a definition runs immediately; deferred bodies and class-receiver initializers stay out. */ +/** + * Expressions a definition runs immediately: heritage, computed keys, static + * blocks and static field values. Deferred bodies and instance initializers + * stay out; `this` inside static work is the class, not the enclosing receiver. + */ const definitionTimeExpressions = (node: t.Function | t.Class): t.Node[] => [ ...(t.isClass(node) && node.superClass != null ? [node.superClass] : []), ...computedDefinitionMembers(node).flatMap((member) => "key" in member ? [member.key] : [], ), + ...(t.isClass(node) + ? node.body.body.flatMap((member): t.Node[] => + t.isStaticBlock(member) + ? [member] + : (t.isClassProperty(member) || t.isClassPrivateProperty(member)) && + member.static && + member.value != null + ? [member.value] + : [], + ) + : []), ]; /** Collect invocation operands once; receiver interpretation belongs to the getter owner. */