mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
fix(javascript): bound semantic expansion and resolve lexical receivers
This commit is contained in:
@@ -5,6 +5,7 @@ import { invalidateSemanticMutationPath } from "./javascriptSemanticMutationValu
|
||||
import type {
|
||||
JavaScriptBindingProvenance,
|
||||
JavaScriptSemanticProperty,
|
||||
JavaScriptSemanticResourceLimit,
|
||||
JavaScriptSemanticValue,
|
||||
} from "./javascriptSemanticIr.js";
|
||||
import {
|
||||
@@ -23,36 +24,57 @@ import {
|
||||
uniqueSemanticOrigins,
|
||||
} from "./javascriptSemanticProvenance.js";
|
||||
import {
|
||||
MAX_SEMANTIC_PRIMITIVE_CANDIDATES,
|
||||
semanticPrimitiveCandidates as primitiveCandidates,
|
||||
semanticPrimitiveSet as primitiveSet,
|
||||
semanticPrimitiveValue as primitiveValue,
|
||||
} from "./javascriptSemanticPrimitives.js";
|
||||
import {
|
||||
SEMANTIC_EXPRESSION_DEPTH_LIMIT,
|
||||
SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT,
|
||||
semanticResourceLimitUnknown,
|
||||
semanticResourceLimitReason,
|
||||
} from "./javascriptSemanticResourceLimits.js";
|
||||
|
||||
interface EvaluationContext {
|
||||
readonly state: JavaScriptSemanticAnalysisState;
|
||||
readonly bindings: ReadonlySet<string>;
|
||||
readonly expressionDepth: number;
|
||||
}
|
||||
|
||||
// Cap expression recursion independently from graph nodes and primitive unions.
|
||||
const isSemanticResourceLimit = (
|
||||
value: JavaScriptSemanticValue,
|
||||
): value is Extract<
|
||||
JavaScriptSemanticValue,
|
||||
{ readonly status: "unknown" | "ambiguous" | "cycle" }
|
||||
> & { readonly resourceLimit: JavaScriptSemanticResourceLimit } =>
|
||||
value.status === "unknown" && value.resourceLimit !== undefined;
|
||||
|
||||
/** Evaluate one binding in the bounded constant-value lattice. */
|
||||
export const evaluateSemanticBinding = (
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): JavaScriptSemanticValue =>
|
||||
evaluateBinding(binding, { state, bindings: new Set() });
|
||||
evaluateBinding(binding, { state, bindings: new Set(), expressionDepth: 0 });
|
||||
|
||||
/** Evaluate one arbitrary inert expression in the established lexical state. */
|
||||
export const evaluateSemanticExpression = (
|
||||
node: t.Node,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): JavaScriptSemanticValue =>
|
||||
evaluateExpression(node, { state, bindings: new Set() });
|
||||
evaluateExpression(node, { state, bindings: new Set(), expressionDepth: 0 });
|
||||
|
||||
/** Follow module provenance through destructuring, members, and aliases. */
|
||||
export const evaluateSemanticProvenance = (
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): JavaScriptBindingProvenance =>
|
||||
provenanceForBinding(binding, { state, bindings: new Set() });
|
||||
provenanceForBinding(binding, {
|
||||
state,
|
||||
bindings: new Set(),
|
||||
expressionDepth: 0,
|
||||
});
|
||||
|
||||
const evaluateBinding = (
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
@@ -85,6 +107,8 @@ const evaluateExpression = (
|
||||
node: t.Node,
|
||||
context: EvaluationContext,
|
||||
): JavaScriptSemanticValue => {
|
||||
if (context.expressionDepth > SEMANTIC_EXPRESSION_DEPTH_LIMIT)
|
||||
return semanticResourceLimitUnknown("expression-depth");
|
||||
const literal = primitiveValue(node);
|
||||
if (literal.found) return { status: "literal", value: literal.value };
|
||||
if (t.isIdentifier(node)) {
|
||||
@@ -128,18 +152,23 @@ const evaluateTemplate = (
|
||||
let candidates = [""];
|
||||
for (let index = 0; index < node.quasis.length; index += 1) {
|
||||
const quasi = node.quasis[index];
|
||||
// Semantic template evaluation retains its established raw fallback for
|
||||
// synthetic AST inputs; exact atom readers remain cooked-only.
|
||||
const text = quasi?.value.cooked ?? quasi?.value.raw ?? "";
|
||||
candidates = candidates.map((prefix) => `${prefix}${text}`);
|
||||
const expression = node.expressions[index];
|
||||
if (expression === undefined) continue;
|
||||
const values = primitiveCandidates(
|
||||
evaluateExpression(expression, nestedContext(context)),
|
||||
);
|
||||
if (values === null)
|
||||
const evaluated = evaluateExpression(expression, nestedContext(context));
|
||||
const values = primitiveCandidates(evaluated);
|
||||
if (values === null) {
|
||||
if (isSemanticResourceLimit(evaluated)) return evaluated;
|
||||
return {
|
||||
status: "unknown",
|
||||
reason: "Template expression is not a bounded primitive.",
|
||||
};
|
||||
}
|
||||
if (candidates.length > SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT / values.length)
|
||||
return semanticResourceLimitUnknown("primitive-candidates");
|
||||
candidates = candidates.flatMap((prefix) =>
|
||||
values.map((value) => `${prefix}${String(value)}`),
|
||||
);
|
||||
@@ -311,8 +340,13 @@ const addPrimitiveValues = (
|
||||
): JavaScriptSemanticValue => {
|
||||
const left = primitiveCandidates(leftValue);
|
||||
const right = primitiveCandidates(rightValue);
|
||||
if (left === null || right === null)
|
||||
if (left === null || right === null) {
|
||||
if (isSemanticResourceLimit(leftValue)) return leftValue;
|
||||
if (isSemanticResourceLimit(rightValue)) return rightValue;
|
||||
return { status: "unknown", reason: "Non-primitive addition." };
|
||||
}
|
||||
if (left.length > MAX_SEMANTIC_PRIMITIVE_CANDIDATES / right.length)
|
||||
return semanticResourceLimitUnknown("primitive-candidates");
|
||||
const values = left.flatMap((leftValue) =>
|
||||
right.map((rightValue) =>
|
||||
typeof leftValue === "string" || typeof rightValue === "string"
|
||||
@@ -327,11 +361,12 @@ const evaluateUnary = (
|
||||
node: t.UnaryExpression,
|
||||
context: EvaluationContext,
|
||||
): JavaScriptSemanticValue => {
|
||||
const argument = primitiveCandidates(
|
||||
evaluateExpression(node.argument, nestedContext(context)),
|
||||
);
|
||||
if (argument === null)
|
||||
const evaluated = evaluateExpression(node.argument, nestedContext(context));
|
||||
const argument = primitiveCandidates(evaluated);
|
||||
if (argument === null) {
|
||||
if (isSemanticResourceLimit(evaluated)) return evaluated;
|
||||
return { status: "unknown", reason: "Non-primitive unary operand." };
|
||||
}
|
||||
if (node.operator === "!")
|
||||
return primitiveSet(argument.map((value) => !value));
|
||||
if (node.operator === "+")
|
||||
@@ -436,6 +471,11 @@ const provenanceForExpression = (
|
||||
node: t.Node,
|
||||
context: EvaluationContext,
|
||||
): JavaScriptBindingProvenance => {
|
||||
if (context.expressionDepth > SEMANTIC_EXPRESSION_DEPTH_LIMIT)
|
||||
return semanticUnresolvedProvenance(
|
||||
"unknown",
|
||||
semanticResourceLimitReason("expression-depth"),
|
||||
);
|
||||
const required = semanticRequireOrigin(node, context.state);
|
||||
if (required !== undefined) return semanticOriginsProvenance([required]);
|
||||
if (t.isIdentifier(node)) {
|
||||
@@ -506,6 +546,8 @@ const provenanceForExpression = (
|
||||
const mergeValues = (
|
||||
values: readonly JavaScriptSemanticValue[],
|
||||
): JavaScriptSemanticValue => {
|
||||
const resourceLimit = values.find(isSemanticResourceLimit);
|
||||
if (resourceLimit !== undefined) return resourceLimit;
|
||||
const primitives = values.flatMap(
|
||||
(value) => primitiveCandidates(value) ?? [],
|
||||
);
|
||||
@@ -521,6 +563,7 @@ const nestedContext = (
|
||||
bindingId?: string,
|
||||
): EvaluationContext => ({
|
||||
state: context.state,
|
||||
expressionDepth: context.expressionDepth + 1,
|
||||
bindings:
|
||||
bindingId === undefined
|
||||
? context.bindings
|
||||
|
||||
Reference in New Issue
Block a user