fix(javascript): preserve uncertainty after references escape to calls (#1292)

* fix(javascript): preserve uncertainty after references escape to calls

* fix(javascript): track iterator root escapes and preserve static captures

* fix(javascript): scope comparison unknowns to their evidence

* fix(javascript): track parameter origins at function body entry

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
You Wishandmorluto authored and GitHub committed 2026-10-10 06:53:42 +08:00
1 parent d0e606930a
commit 355a292198
17 files changed
+2574 -220

No files matched your search

@@ -1,8 +1,17 @@
import * as t from "@babel/types";
import {
semanticCapturePosition,
semanticEscapeFollowsCapture,
semanticBindingPrecedesCapture,
} from "./javascriptSemanticMutationInitializers.js";
import { semanticSlotAtPath } from "./javascriptSemanticSlots.js";
import { invalidateSemanticMutationPath } from "./javascriptSemanticMutationValues.js";
import {
invalidateSemanticEscapedPath,
invalidateSemanticMutationPath,
} from "./javascriptSemanticMutationValues.js";
import type { JavaScriptBindingProvenance } from "./javascriptSemanticIr.js";
import type {
@@ -46,6 +55,7 @@ interface EvaluationContext {
readonly state: JavaScriptSemanticAnalysisState;
readonly bindings: ReadonlySet<string>;
readonly expressionDepth: number;
readonly capturePoint?: t.Node;
readonly primitiveBindingValues: Map<string, JavaScriptSemanticValue>;
}
@@ -54,6 +64,25 @@ const primitiveBindingValuesByState = new WeakMap<
Map<string, JavaScriptSemanticValue>
>();
const primitiveExpressionValuesByState = new WeakMap<
JavaScriptSemanticAnalysisState,
WeakMap<t.Node, JavaScriptSemanticValue>
>();
const capturedBindingValuesByState = new WeakMap<
JavaScriptSemanticAnalysisState,
WeakMap<t.Node, Map<string, JavaScriptSemanticValue>>
>();
/** Discard primitive projections evaluated before mutation collection finished. */
export const clearSemanticPrimitiveBindingValues = (
state: JavaScriptSemanticAnalysisState,
): void => {
primitiveBindingValuesByState.delete(state);
capturedBindingValuesByState.delete(state);
primitiveExpressionValuesByState.delete(state);
};
const primitiveBindingValuesFor = (
state: JavaScriptSemanticAnalysisState,
): Map<string, JavaScriptSemanticValue> => {
@@ -115,6 +144,20 @@ const evaluateBinding = (
): JavaScriptSemanticValue => {
if (context.bindings.has(binding.bindingId))
return { status: "cycle", reason: `Alias cycle at ${binding.name}.` };
if (
context.capturePoint !== undefined &&
!semanticBindingPrecedesCapture(
binding,
context.capturePoint,
context.state.parentsByNode,
)
) {
const { capturePoint: _capturePoint, ...liveContext } = context;
return evaluateBinding(binding, {
...liveContext,
primitiveBindingValues: primitiveBindingValuesFor(context.state),
});
}
const cached = context.primitiveBindingValues.get(binding.bindingId);
if (cached !== undefined) return cached;
if (binding.initializers.length === 0)
@@ -146,15 +189,41 @@ const evaluateBinding = (
if (initializer === undefined)
return { status: "unknown", reason: "Missing binding initializer." };
const nested = nestedContext(context, binding.bindingId);
const value = projectValue(
let value = projectValue(
evaluateExpression(initializer.node, nested),
initializer.projection,
);
const projected = binding.mutatedPaths.reduce(
if (!isPrimitive(value)) {
const captureContext = primitiveCaptureContext(initializer.node, nested);
if (captureContext !== undefined) {
const captured = projectValue(
evaluateExpression(initializer.node, captureContext),
initializer.projection,
);
if (isPrimitive(captured)) value = captured;
}
}
const mutated = binding.mutatedPaths.reduce(
invalidateSemanticMutationPath,
value,
);
const projected = binding.escapedPaths.reduce(
(value, escape) =>
context.capturePoint !== undefined &&
semanticEscapeFollowsCapture(
binding,
context.capturePoint,
escape.node,
context.state.parentsByNode,
)
? value
: invalidateSemanticEscapedPath(value, escape.path),
mutated,
);
// Point-local memoization also bounds failed speculative captures. Only a
// primitive result is allowed back into ordinary value evaluation.
if (
context.capturePoint !== undefined ||
projected.status === "literal" ||
projected.status === "union" ||
(isSemanticResourceLimit(projected) &&
@@ -301,7 +370,7 @@ const evaluateObject = (
name,
presence: "present",
value: t.isObjectProperty(property)
? evaluateExpression(property.value, nestedContext(context))
? evaluateCapturedExpression(property.value, nestedContext(context))
: {
status: "unknown",
reason: "Object method or accessor value is not a primitive.",
@@ -351,7 +420,7 @@ const evaluateArray = (
items.push({
name: String(items.length),
presence: "present",
value: evaluateExpression(element, nestedContext(context)),
value: evaluateCapturedExpression(element, nestedContext(context)),
});
}
return unknownItems
@@ -648,9 +717,68 @@ const nestedContext = (
): EvaluationContext => ({
state: context.state,
primitiveBindingValues: context.primitiveBindingValues,
...(context.capturePoint === undefined
? {}
: { capturePoint: context.capturePoint }),
expressionDepth: context.expressionDepth + 1,
bindings:
bindingId === undefined
? context.bindings
: new Set([...context.bindings, bindingId]),
});
const isPrimitive = (value: JavaScriptSemanticValue): boolean =>
value.status === "literal" || value.status === "union";
const primitiveCaptureContext = (
node: t.Node,
context: EvaluationContext,
): EvaluationContext | undefined => {
if (
context.capturePoint !== undefined ||
t.isObjectExpression(node) ||
t.isArrayExpression(node) ||
semanticCapturePosition(node, context.state.parentsByNode) === null
)
return undefined;
let captures = capturedBindingValuesByState.get(context.state);
if (captures === undefined) {
captures = new WeakMap();
capturedBindingValuesByState.set(context.state, captures);
}
let values = captures.get(node);
if (values === undefined) {
values = new Map();
captures.set(node, values);
}
return { ...context, capturePoint: node, primitiveBindingValues: values };
};
const evaluateCapturedExpression = (
node: t.Node,
context: EvaluationContext,
): JavaScriptSemanticValue => {
// A literal field captures its primitive at construction, even when its
// containing object is subsequently read at another capture point.
let values = primitiveExpressionValuesByState.get(context.state);
const cached = values?.get(node);
if (cached !== undefined) return cached;
let value = evaluateExpression(node, context);
if (!isPrimitive(value)) {
const captureContext = primitiveCaptureContext(node, context);
if (captureContext !== undefined) {
const captured = evaluateExpression(node, captureContext);
if (isPrimitive(captured)) value = captured;
}
}
// Never publish values evaluated at somebody else's capture point. Like
// binding caches, these entries are discarded whenever effects change.
if (context.capturePoint === undefined && isPrimitive(value)) {
if (values === undefined) {
values = new WeakMap();
primitiveExpressionValuesByState.set(context.state, values);
}
values.set(node, value);
}
return value;
};