fix(javascript): retain returned-reference mutation uncertainty (#1471)

This commit is contained in:
Ysopwnable
2026-10-10 15:46:24 +08:00
committed by GitHub
parent 9defed9725
commit 782e0147cd
6 changed files with 391 additions and 31 deletions
@@ -15,6 +15,8 @@ import {
semanticSlotAtPath,
} from "./javascriptSemanticSlots.js";
import { semanticIterationSources } from "./javascriptSemanticIterationSources.js";
import { semanticCallableResultExpressions } from "./javascriptSemanticCallableResults.js";
import { createSemanticReturnedReferences } from "./javascriptSemanticReturnedReferences.js";
import { semanticMutationInitializers } from "./javascriptSemanticMutationInitializers.js";
import {
semanticArrayIndex,
@@ -44,6 +46,7 @@ export const collectSemanticMemberMutations = (
},
});
const normalizePath = observableMutationPaths(state);
const returnedReferences = createSemanticReturnedReferences(state);
const recordedEffects = new Set<string>();
const selections = new Map<string, readonly string[]>();
const coveringPaths = new Map<string, SemanticPropertyPathCoverage>();
@@ -340,6 +343,8 @@ export const collectSemanticMemberMutations = (
t.isOptionalCallExpression(node) ||
t.isNewExpression(node)
) {
for (const source of returnedReferences(node.callee))
markEscaped(source.node, node, source.projection);
for (const argument of node.arguments)
if (t.isSpreadElement(argument))
deferIterable({
@@ -555,6 +560,11 @@ const referencedValues = (
path: PropertyPath,
effect: ValueEffect,
): readonly ReferencedValue[] => {
if (effect === "escape" && t.isFunction(node))
return semanticCallableResultExpressions(node).flatMap(
({ node, delegated }) =>
node === null ? [] : [{ node, path: delegated ? [null] : [] }],
);
// An initializer owns its slots; only deeper writes can affect shared children.
if (t.isObjectExpression(node) || t.isArrayExpression(node)) {
if (effect === "write" && path.length < 2) return [];
@@ -855,11 +865,11 @@ const objectReferencedValues = (
}
const name = semanticStaticPropertyKey(property.key, property.computed);
if (name !== null && overwritten.has(name)) continue;
if (
t.isObjectProperty(property) &&
(name === null || semanticPropertyPathKeyMatches(key ?? null, name))
)
references.push({ node: property.value, path: remaining });
if (name === null || semanticPropertyPathKeyMatches(key ?? null, name))
references.push({
node: t.isObjectProperty(property) ? property.value : property,
path: remaining,
});
// A prototype setter does not replace an own property from a spread.
if (
name !== null &&