mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
perf(javascript): defer ancestry for member identifier roles
This commit is contained in:
1 parent
aeeb4b72c1
commit
3e6ac23b50
3 files changed
+87
-5
No files matched your search
@@ -335,7 +335,8 @@ const collectCapture = (
|
||||
owner.bodyScopeId === null
|
||||
)
|
||||
return;
|
||||
if (semanticIdentifierRole(node, parent, context.ancestors) === null) return;
|
||||
if (semanticIdentifierRole(node, parent, () => context.ancestors) === null)
|
||||
return;
|
||||
const binding = resolveSemanticBindingState(state, node, node.name);
|
||||
if (
|
||||
binding === undefined ||
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import * as t from "@babel/types";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { semanticIdentifierRole } from "./javascriptSemanticProjection.js";
|
||||
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
||||
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
|
||||
|
||||
const rolesFor = (source: string) => {
|
||||
const parsed = parseJavaScriptSource(source);
|
||||
if (parsed === null) throw new Error("Expected parsed source");
|
||||
const references: { name: string; role: string }[] = [];
|
||||
let snapshots = 0;
|
||||
traverseJavaScriptAst(parsed.program, {
|
||||
enter(node, parent, readAncestors) {
|
||||
if (!t.isIdentifier(node) || parent === null) return;
|
||||
const role = semanticIdentifierRole(node, parent, () => {
|
||||
snapshots++;
|
||||
return readAncestors();
|
||||
});
|
||||
if (role !== null) references.push({ name: node.name, role });
|
||||
},
|
||||
});
|
||||
return { references, snapshots };
|
||||
};
|
||||
|
||||
it.each([256, 512])(
|
||||
"classifies a %s-member chain without copying or scanning its ancestry",
|
||||
(depth) => {
|
||||
expect(rolesFor(`object${".property".repeat(depth)};`)).toEqual({
|
||||
references: [{ name: "object", role: "read" }],
|
||||
snapshots: 0,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("retains optional and computed member reads without ancestor snapshots", () => {
|
||||
expect(
|
||||
rolesFor("object.property; object[key]; object?.property; object?.[key];"),
|
||||
).toEqual({
|
||||
references: [
|
||||
{ name: "object", role: "read" },
|
||||
{ name: "object", role: "read" },
|
||||
{ name: "key", role: "read" },
|
||||
{ name: "object", role: "read" },
|
||||
{ name: "object", role: "read" },
|
||||
{ name: "key", role: "read" },
|
||||
],
|
||||
snapshots: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("still reads ancestry to distinguish bound patterns from default-value reads", () => {
|
||||
const result = rolesFor(
|
||||
"const {key: value = compute()} = object; ({key: target} = source);",
|
||||
);
|
||||
expect(result.references).toEqual([
|
||||
{ name: "compute", role: "read" },
|
||||
{ name: "object", role: "read" },
|
||||
{ name: "target", role: "write" },
|
||||
{ name: "source", role: "read" },
|
||||
]);
|
||||
expect(result.snapshots).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("keeps member receivers and computed keys as reads in assignment patterns and updates", () => {
|
||||
expect(
|
||||
rolesFor("({field: object[key]} = source); object[key]++;").references,
|
||||
).toEqual([
|
||||
{ name: "object", role: "read" },
|
||||
{ name: "key", role: "read" },
|
||||
{ name: "source", role: "read" },
|
||||
{ name: "object", role: "read" },
|
||||
{ name: "key", role: "read" },
|
||||
]);
|
||||
});
|
||||
@@ -180,8 +180,9 @@ export function* collectSemanticReferencesSteps(
|
||||
(name !== undefined && node.name !== name)
|
||||
)
|
||||
return;
|
||||
const ancestors = readAncestors();
|
||||
const role = semanticIdentifierRole(node, parent, ancestors);
|
||||
let ancestors: readonly t.Node[] | undefined;
|
||||
const ancestry = () => (ancestors ??= readAncestors());
|
||||
const role = semanticIdentifierRole(node, parent, ancestry);
|
||||
if (role === null) return;
|
||||
// A compound assignment or update reads before it writes; emit both so
|
||||
// reference consumers see the read they actually execute.
|
||||
@@ -189,7 +190,7 @@ export function* collectSemanticReferencesSteps(
|
||||
role === "write" &&
|
||||
semanticReadsBeforeWrite(
|
||||
node,
|
||||
semanticWrappedWriteSite(node, parent, ancestors) ?? parent,
|
||||
semanticWrappedWriteSite(node, parent, ancestry()) ?? parent,
|
||||
)
|
||||
? (["read", "write"] as const)
|
||||
: ([role] as const);
|
||||
@@ -631,8 +632,12 @@ export const semanticReadsBeforeWrite = (
|
||||
export const semanticIdentifierRole = (
|
||||
node: t.Identifier,
|
||||
parent: t.Node,
|
||||
ancestors: readonly t.Node[],
|
||||
readAncestors: () => readonly t.Node[],
|
||||
): JavaScriptSemanticReference["role"] | null => {
|
||||
// Member identifiers are either object/computed-key reads or fixed property
|
||||
// names. None is a bound destructuring slot, so no ancestor snapshot is needed.
|
||||
if (t.isMemberExpression(parent) || t.isOptionalMemberExpression(parent))
|
||||
return semanticReferenceRole(node, parent);
|
||||
if (
|
||||
t.isObjectProperty(parent) &&
|
||||
parent.key === node &&
|
||||
@@ -640,6 +645,7 @@ export const semanticIdentifierRole = (
|
||||
parent.value !== node
|
||||
)
|
||||
return null;
|
||||
const ancestors = readAncestors();
|
||||
const patternAncestor = enclosingPatternAncestor(node, parent, ancestors);
|
||||
if (patternAncestor !== null) {
|
||||
if (isPatternOwnerDeclaration(patternAncestor)) return null;
|
||||
|
||||
Reference in new issue
Block a user