mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 02:45:58 +00:00
fix(javascript): preserve initializer execution and lexical ownership
This commit is contained in:
1 parent
5cbcd58550
commit
fba4faf88a
8 files changed
+488
-41
No files matched your search
@@ -9,6 +9,7 @@ import * as t from "@babel/types";
|
||||
import {
|
||||
semanticComputedMethodKey,
|
||||
semanticBranchMayBeSkipped,
|
||||
semanticPriorStatementMayExit,
|
||||
semanticIterationCardinality,
|
||||
} from "./javascriptSemanticEvaluationOrder.js";
|
||||
|
||||
@@ -116,6 +117,7 @@ import type { JavaScriptModuleResolutionMode } from "./javascriptModuleResolutio
|
||||
interface BindPatternInput {
|
||||
readonly iterationSources?: JavaScriptSemanticBindingState["initializers"][number]["iterationSources"];
|
||||
readonly pattern: t.Node;
|
||||
readonly entryBody?: t.BlockStatement;
|
||||
readonly initializer: t.Node | null;
|
||||
readonly scope: JavaScriptSemanticScopeState;
|
||||
readonly state: JavaScriptSemanticAnalysisState;
|
||||
@@ -1067,6 +1069,19 @@ function* collectDefinitionsSteps(
|
||||
node: t.CallExpression;
|
||||
scopeId: string;
|
||||
}[] = [];
|
||||
const displacedVarInitializers: {
|
||||
node: t.VariableDeclarator;
|
||||
scope: JavaScriptSemanticScopeState;
|
||||
owner: JavaScriptSemanticScopeState;
|
||||
}[] = [];
|
||||
const thrownValues: {
|
||||
node: t.ThrowStatement;
|
||||
ancestors: readonly t.Node[];
|
||||
}[] = [];
|
||||
const legacyBlockFunctions: {
|
||||
node: t.FunctionDeclaration;
|
||||
scope: JavaScriptSemanticScopeState;
|
||||
}[] = [];
|
||||
let needsPrototypeFacts = false;
|
||||
const functionBodies: {
|
||||
readonly body: t.BlockStatement;
|
||||
@@ -1117,6 +1132,21 @@ function* collectDefinitionsSteps(
|
||||
parentScope = dynamicScope;
|
||||
}
|
||||
bindOuterDeclaration(node, parentScope, state);
|
||||
if (
|
||||
t.isFunctionDeclaration(node) &&
|
||||
node.id != null &&
|
||||
parentScope.kind === "block"
|
||||
)
|
||||
legacyBlockFunctions.push({ node, scope: parentScope });
|
||||
if (
|
||||
t.isVariableDeclarator(node) &&
|
||||
node.init != null &&
|
||||
t.isIdentifier(node.id)
|
||||
) {
|
||||
const owner = semanticVariableScope(parentScope, parent, state);
|
||||
if (owner !== parentScope)
|
||||
displacedVarInitializers.push({ node, scope: parentScope, owner });
|
||||
}
|
||||
const nested = nestedScope(node, parent, parentScope, state);
|
||||
if (nested !== undefined) {
|
||||
stack.push(nested);
|
||||
@@ -1141,6 +1171,8 @@ function* collectDefinitionsSteps(
|
||||
state,
|
||||
});
|
||||
bindInnerDeclaration(node, parent, scope, state);
|
||||
if (t.isThrowStatement(node))
|
||||
thrownValues.push({ node, ancestors: readAncestors() });
|
||||
// Every supported global eval origin starts with one of these decoded
|
||||
// identifiers, including computed properties and destructured aliases.
|
||||
if (t.isIdentifier(node) && globalEvalRoots.has(node.name))
|
||||
@@ -1180,6 +1212,94 @@ function* collectDefinitionsSteps(
|
||||
stack.pop();
|
||||
},
|
||||
});
|
||||
// A var declaration is instantiated in its variable environment, while its
|
||||
// initializer assigns through the lexical environment at the source site.
|
||||
for (const { node, scope, owner } of displacedVarInitializers) {
|
||||
if (!t.isIdentifier(node.id) || node.init == null) continue;
|
||||
const selected = resolveSemanticBindingFromScope(
|
||||
scope,
|
||||
node.id.name,
|
||||
state,
|
||||
);
|
||||
const declared = owner.bindings.get(node.id.name);
|
||||
if (
|
||||
selected === undefined ||
|
||||
selected === declared ||
|
||||
declared === undefined
|
||||
)
|
||||
continue;
|
||||
for (let index = declared.initializers.length - 1; index >= 0; index--)
|
||||
if (declared.initializers[index]?.node === node.init)
|
||||
declared.initializers.splice(index, 1);
|
||||
addAssignment(
|
||||
node.id,
|
||||
node.init,
|
||||
scope,
|
||||
state,
|
||||
state.conditionallySkippedExpressions.has(node.init),
|
||||
node,
|
||||
);
|
||||
}
|
||||
for (const { node, ancestors } of thrownValues) {
|
||||
let child: t.Node = node;
|
||||
for (let index = ancestors.length - 1; index >= 0; index--) {
|
||||
const parent = ancestors[index]!;
|
||||
if (t.isFunction(parent) || t.isClass(parent)) break;
|
||||
if (
|
||||
t.isTryStatement(parent) &&
|
||||
parent.handler != null &&
|
||||
child === parent.block
|
||||
) {
|
||||
const scope = state.scopeByNode.get(parent.handler);
|
||||
if (scope !== undefined && parent.handler.param != null)
|
||||
bindPattern({
|
||||
pattern: parent.handler.param,
|
||||
initializer: node.argument,
|
||||
scope,
|
||||
state,
|
||||
mutable: true,
|
||||
referenceOnly: true,
|
||||
entryBody: parent.handler.body,
|
||||
});
|
||||
break;
|
||||
}
|
||||
child = parent;
|
||||
}
|
||||
}
|
||||
for (const { node, scope } of legacyBlockFunctions) {
|
||||
if (
|
||||
node.id == null ||
|
||||
semanticStrictnessAt(state.parentsByNode.get(node) ?? node, state) ===
|
||||
"strict"
|
||||
)
|
||||
continue;
|
||||
let outer =
|
||||
scope.parentScopeId === null
|
||||
? undefined
|
||||
: state.scopesById.get(scope.parentScopeId);
|
||||
while (outer !== undefined) {
|
||||
const binding = outer.bindings.get(node.id.name);
|
||||
if (
|
||||
binding !== undefined &&
|
||||
!binding.declarationKinds.has("var") &&
|
||||
binding.kind !== "parameter"
|
||||
)
|
||||
break;
|
||||
if (
|
||||
outer.kind === "function" ||
|
||||
outer.kind === "program" ||
|
||||
outer.kind === "static-block"
|
||||
) {
|
||||
if (binding !== undefined)
|
||||
addAssignment(node.id, node, outer, state, false, node);
|
||||
break;
|
||||
}
|
||||
outer =
|
||||
outer.parentScopeId === null
|
||||
? undefined
|
||||
: state.scopesById.get(outer.parentScopeId);
|
||||
}
|
||||
}
|
||||
if (needsPrototypeFacts)
|
||||
yield* indexPrototypeUncertaintySteps(program, state);
|
||||
// All declarations must exist before resolving writes, including hoisted
|
||||
@@ -1365,7 +1485,11 @@ const writeMayBeSkipped = (
|
||||
t.isStaticBlock(parent)
|
||||
)
|
||||
return false;
|
||||
if (semanticBranchMayBeSkipped(parent, child)) return true;
|
||||
if (
|
||||
semanticBranchMayBeSkipped(parent, child) ||
|
||||
semanticPriorStatementMayExit(parent, child)
|
||||
)
|
||||
return true;
|
||||
child = parent;
|
||||
}
|
||||
return false;
|
||||
@@ -1421,7 +1545,7 @@ const bindInnerDeclaration = (
|
||||
? { declarationKind: parent.kind }
|
||||
: {}),
|
||||
});
|
||||
else if (t.isClassExpression(node) && t.isIdentifier(node.id))
|
||||
else if (t.isClass(node) && t.isIdentifier(node.id))
|
||||
addBinding({
|
||||
state,
|
||||
scope,
|
||||
@@ -1588,7 +1712,8 @@ const mappedArgumentsOwner = (
|
||||
}
|
||||
if (!t.isIdentifier(value)) return undefined;
|
||||
|
||||
if (semanticBindingLookupAt(state, value, "arguments").kind !== "ambient")
|
||||
const lookup = semanticBindingLookupAt(state, value, "arguments");
|
||||
if (lookup.kind !== "implicit-arguments" && lookup.kind !== "ambient")
|
||||
return undefined;
|
||||
let owner: t.Function | undefined;
|
||||
for (
|
||||
@@ -1634,12 +1759,12 @@ const mappedArgumentsOwner = (
|
||||
return {
|
||||
scope,
|
||||
parameters: mapped,
|
||||
strictness: mappedArgumentsStrictness(owner, state),
|
||||
strictness: semanticStrictnessAt(owner, state),
|
||||
};
|
||||
};
|
||||
|
||||
const mappedArgumentsStrictness = (
|
||||
owner: t.Function,
|
||||
const semanticStrictnessAt = (
|
||||
owner: t.Node,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
): MappedArgumentsOwner["strictness"] => {
|
||||
for (
|
||||
@@ -1737,6 +1862,9 @@ const nestedScope = (
|
||||
location: range(switchOwner ?? node),
|
||||
bindingsComplete: true,
|
||||
bindings: new Map(),
|
||||
...(t.isFunction(node) && !t.isArrowFunctionExpression(node)
|
||||
? { implicitArguments: true as const }
|
||||
: {}),
|
||||
};
|
||||
state.scopes.push(scope);
|
||||
state.scopesById.set(scope.scopeId, scope);
|
||||
@@ -1914,6 +2042,9 @@ const bindPattern = (input: BindPatternInput): void => {
|
||||
binding.referenceInitializers.push({
|
||||
node: initializer,
|
||||
projection,
|
||||
...(input.entryBody === undefined
|
||||
? {}
|
||||
: { entryBody: input.entryBody }),
|
||||
...(assignmentReference === undefined ? {} : { assignment: true }),
|
||||
...(copyKind === undefined ? {} : { copyKind }),
|
||||
...(copyProjectionOffset === undefined
|
||||
@@ -2136,7 +2267,8 @@ const addAssignment = (
|
||||
const mutation = writeSite ?? initializer;
|
||||
if (mutation === null) return;
|
||||
const lookup = semanticBindingLookupFromScope(scope, identifier.name, state);
|
||||
if (lookup.kind === "captured") return;
|
||||
if (lookup.kind === "captured" || lookup.kind === "implicit-arguments")
|
||||
return;
|
||||
const binding = "binding" in lookup ? lookup.binding : undefined;
|
||||
conditionallySkipped ||=
|
||||
lookup.kind !== "binding" && lookup.kind !== "ambient";
|
||||
|
||||
Reference in new issue
Block a user