mirror of
https://github.com/morluto/rea.git
synced 2026-10-21 21:32:26 -05:00
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:
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;
|
||||
};
|
||||
Reference in new issue
Block a user