fix(javascript): resolve child ownership at receiver reads (#3819)

* fix(javascript): resolve child ownership at receiver reads

* fix(javascript): preserve deferred field child candidates

---------

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
星闌
2026-10-17 00:22:26 +08:00
committed by GitHub
parent 48c3d2d170
commit 6d2eeeaed0
4 changed files with 560 additions and 26 deletions
@@ -1,11 +1,74 @@
import * as t from "@babel/types";
import { describe, expect, it } from "vitest";
import { semanticMutationInitializers } from "./javascriptSemanticMutationInitializers.js";
import {
semanticBindingAtRead,
semanticMutationInitializers,
semanticTopLevelEffectFollowsCapture,
} from "./javascriptSemanticMutationInitializers.js";
import type { JavaScriptSemanticBindingState } from "./javascriptSemanticState.js";
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
it.each([
["public value", false, false],
["private value", false, false],
["accessor value", false, false],
["public value", true, true],
["private value", true, true],
["accessor value", true, true],
["public key", false, true],
["accessor key", false, true],
] as const)(
"orders %s reads with static=%s only when evaluated at class definition",
(kind, isStatic, ordered) => {
const initializer = t.numericLiteral(1);
const read = t.identifier("child");
const inKey = kind.endsWith("key");
const key = inKey ? read : t.identifier("field");
const value = inKey ? t.numericLiteral(0) : read;
const field = kind.startsWith("private")
? t.classPrivateProperty(t.privateName(t.identifier("field")), value)
: kind.startsWith("accessor")
? t.classAccessorProperty(key, value, null, null, inKey)
: t.classProperty(key, value, null, null, inKey);
field.static = isStatic;
const program = t.program([
t.variableDeclaration("let", [
t.variableDeclarator(t.identifier("child"), initializer),
]),
t.classDeclaration(t.identifier("Stop"), null, t.classBody([field])),
]);
const parents = new WeakMap<t.Node, t.Node>();
traverseJavaScriptAst(program, {
enter: (node, parent) => {
if (parent !== null) parents.set(node, parent);
},
});
const binding: JavaScriptSemanticBindingState = {
bindingId: "program:child",
scopeId: "program",
name: "child",
kind: "variable",
declarationKinds: new Set(["let"]),
mutable: true,
memberWrites: [],
escapedPaths: [],
definitions: [],
initializers: [{ node: initializer, projection: [] }],
referenceInitializers: [],
directOrigins: [],
runtimeImport: false,
};
expect(semanticBindingAtRead(binding, read, parents).status).toBe(
ordered ? "ordered" : "unresolved",
);
expect(
semanticTopLevelEffectFollowsCapture(initializer, read, parents),
).toBe(ordered);
},
);
describe("parameter references copied into function body bindings", () => {
it.each([
["var arg = []; mutate(arg);", false],