mirror of
https://github.com/morluto/rea
synced 2026-10-26 22:28:27 -04:00
0cc56493be
Select binding facts at reads without mixing whole-binding and temporal values. Share conditional activation rules, preserve partial expression order, and bound immutable ancestry work. Memoize temporal results within each query using initializer identity, capture point, budget and dependency proofs; preserve depth limits and source Evidence. Fixes #3881
391 lines
14 KiB
TypeScript
391 lines
14 KiB
TypeScript
import { expect, it, vi } from "vitest";
|
|
|
|
import {
|
|
analyzeJavaScriptSemantics,
|
|
indexJavaScriptBindingsSteps,
|
|
} from "./javascriptSemanticAnalysis.js";
|
|
import { SemanticDependencyProofs } from "./javascriptSemanticDependencies.js";
|
|
import { SEMANTIC_EXPRESSION_DEPTH_LIMIT } from "./javascriptSemanticResourceLimits.js";
|
|
import type { JavaScriptSemanticValue } from "./javascriptSemanticValueTypes.js";
|
|
import {
|
|
clearSemanticValueCaches,
|
|
evaluateSemanticBinding,
|
|
evaluateSemanticProvenance,
|
|
} from "./javascriptSemanticValues.js";
|
|
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
|
|
|
|
const property = (
|
|
value: JavaScriptSemanticValue,
|
|
name: string,
|
|
): JavaScriptSemanticValue => {
|
|
if (value.status !== "object")
|
|
throw new Error(`Expected object at ${name}, got ${value.status}`);
|
|
const result = value.properties.find((entry) => entry.name === name)?.value;
|
|
if (result === undefined) throw new Error(`Missing ${name}`);
|
|
return result;
|
|
};
|
|
|
|
it("reuses dependency-aware read snapshots across a reconvergent value chain", () => {
|
|
const measure = (depth: number) => {
|
|
const source = `let a0=1;${Array.from(
|
|
{ length: depth },
|
|
(_, index) => `let a${index + 1}=a${index}+a${index};`,
|
|
).join(
|
|
"",
|
|
)}const saved=a${depth};${Array.from({ length: depth + 1 }, (_, index) => `a${index}=0;`).join("")}`;
|
|
const parsed = parseJavaScriptSource(source, "saved-chain.js");
|
|
if (parsed === null) throw new Error("Expected parsed source");
|
|
const steps = indexJavaScriptBindingsSteps(parsed.program);
|
|
let step = steps.next();
|
|
while (step.done !== true) step = steps.next();
|
|
const state = step.value;
|
|
const saved = [...state.bindingsById.values()].find(
|
|
({ name }) => name === "saved",
|
|
);
|
|
if (saved === undefined) throw new Error("Missing saved binding");
|
|
const binding = vi.spyOn(SemanticDependencyProofs.prototype, "binding");
|
|
try {
|
|
expect(evaluateSemanticBinding(saved, state)).toEqual({
|
|
status: "literal",
|
|
value: 2 ** depth,
|
|
});
|
|
return binding.mock.calls.length;
|
|
} finally {
|
|
binding.mockRestore();
|
|
}
|
|
};
|
|
const small = measure(8);
|
|
const large = measure(12);
|
|
expect(small).toBeGreaterThan(0);
|
|
expect(large).toBeLessThan(3 * small);
|
|
});
|
|
|
|
it("preserves the remaining depth budget when reusing a read snapshot", () => {
|
|
const depth = 12;
|
|
const nesting = SEMANTIC_EXPRESSION_DEPTH_LIMIT - depth;
|
|
const source = `let a0=1;${Array.from({ length: depth }, (_, index) => `let a${index + 1}=a${index}+a${index};`).join("")}
|
|
const root={shallow:a${depth},deep:${"[".repeat(nesting)}a${depth}${"]".repeat(nesting)},after:a${depth}};
|
|
${Array.from({ length: depth + 1 }, (_, index) => `a${index}=0;`).join("")}`;
|
|
const parsed = parseJavaScriptSource(source, "snapshot-depth.js");
|
|
if (parsed === null) throw new Error("Expected parsed source");
|
|
const steps = indexJavaScriptBindingsSteps(parsed.program);
|
|
let step = steps.next();
|
|
while (step.done !== true) step = steps.next();
|
|
const state = step.value;
|
|
const root = [...state.bindingsById.values()].find(
|
|
({ name }) => name === "root",
|
|
);
|
|
if (root === undefined) throw new Error("Missing root binding");
|
|
const result = evaluateSemanticBinding(root, state);
|
|
for (const key of ["shallow", "after"])
|
|
expect(property(result, key)).toEqual({
|
|
status: "literal",
|
|
value: 2 ** depth,
|
|
});
|
|
let leaf = property(result, "deep");
|
|
while (leaf.status === "array") {
|
|
const item = leaf.items[0];
|
|
if (item === undefined) throw new Error("Missing nested item");
|
|
leaf = item.value;
|
|
}
|
|
expect(leaf).toMatchObject({
|
|
status: "unknown",
|
|
resourceLimit: "expression-depth",
|
|
});
|
|
});
|
|
|
|
it("bounds reconvergent read work when the expression depth budget is exhausted", () => {
|
|
const depth = SEMANTIC_EXPRESSION_DEPTH_LIMIT;
|
|
const source = `let a0=1;${Array.from({ length: depth }, (_, index) => `let a${index + 1}=a${index}+a${index};`).join("")}
|
|
const saved=a${depth};${Array.from({ length: depth + 1 }, (_, index) => `a${index}=0;`).join("")}`;
|
|
const parsed = parseJavaScriptSource(source, "snapshot-limit.js");
|
|
if (parsed === null) throw new Error("Expected parsed source");
|
|
const steps = indexJavaScriptBindingsSteps(parsed.program);
|
|
let step = steps.next();
|
|
while (step.done !== true) step = steps.next();
|
|
const state = step.value;
|
|
const saved = [...state.bindingsById.values()].find(
|
|
({ name }) => name === "saved",
|
|
);
|
|
if (saved === undefined) throw new Error("Missing saved binding");
|
|
const original = SemanticDependencyProofs.prototype.binding;
|
|
let visits = 0;
|
|
const binding = vi
|
|
.spyOn(SemanticDependencyProofs.prototype, "binding")
|
|
.mockImplementation(function (this: SemanticDependencyProofs, name) {
|
|
if (++visits > 16 * depth)
|
|
throw new Error("Unbounded snapshot dependency evaluation");
|
|
return original.call(this, name);
|
|
});
|
|
try {
|
|
expect(evaluateSemanticBinding(saved, state)).toMatchObject({
|
|
status: "unknown",
|
|
resourceLimit: "expression-depth",
|
|
});
|
|
} finally {
|
|
binding.mockRestore();
|
|
}
|
|
});
|
|
|
|
it.each([false, true])(
|
|
"isolates captured parameter values from the assigned binding cache (prewarmed: %s)",
|
|
(prewarmed) => {
|
|
const parsed = parseJavaScriptSource(
|
|
`export function read(x) {
|
|
const earlier = x;
|
|
x = 2;
|
|
const later = x;
|
|
return { earlier, later };
|
|
}`,
|
|
"captured-parameter.js",
|
|
);
|
|
if (parsed === null) throw new Error("Expected parsed source");
|
|
const steps = indexJavaScriptBindingsSteps(parsed.program);
|
|
let step = steps.next();
|
|
while (step.done !== true) step = steps.next();
|
|
const state = step.value;
|
|
const bindings = new Map(
|
|
[...state.bindingsById.values()].map((binding) => [
|
|
binding.name,
|
|
binding,
|
|
]),
|
|
);
|
|
const assigned = bindings.get("x");
|
|
const earlier = bindings.get("earlier");
|
|
const later = bindings.get("later");
|
|
if (assigned === undefined || earlier === undefined || later === undefined)
|
|
throw new Error("Missing capture bindings");
|
|
if (prewarmed)
|
|
expect(evaluateSemanticBinding(assigned, state)).toEqual({
|
|
status: "literal",
|
|
value: 2,
|
|
});
|
|
expect(evaluateSemanticBinding(earlier, state).status).toBe("unknown");
|
|
expect(evaluateSemanticBinding(later, state)).toEqual({
|
|
status: "literal",
|
|
value: 2,
|
|
});
|
|
expect(evaluateSemanticBinding(assigned, state)).toEqual({
|
|
status: "literal",
|
|
value: 2,
|
|
});
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
[false, false],
|
|
[true, false],
|
|
[false, true],
|
|
[true, true],
|
|
])(
|
|
"recomputes a shared cyclic value under a different active ancestor (prewarmed: %s, rebound: %s)",
|
|
(prewarmed, rebound) => {
|
|
const parsed = parseJavaScriptSource(
|
|
`
|
|
${rebound ? "let" : "const"} a = { shared: common, own: "a" };
|
|
${rebound ? "let" : "const"} b = { shared: common, own: "b" };
|
|
const common = { a, b };
|
|
const result = { first: a, second: b };
|
|
${rebound ? "a=0; b=0;" : ""}
|
|
`,
|
|
"cycles.js",
|
|
);
|
|
if (parsed === null) throw new Error("Expected parsed source");
|
|
const steps = indexJavaScriptBindingsSteps(parsed.program);
|
|
let step = steps.next();
|
|
while (step.done !== true) step = steps.next();
|
|
const state = step.value;
|
|
const binding = [...state.bindingsById.values()].find(
|
|
(entry) => entry.name === "result",
|
|
);
|
|
if (binding === undefined) throw new Error("Missing result binding");
|
|
if (prewarmed)
|
|
for (const entry of state.bindingsById.values())
|
|
if (entry.name === "a" || entry.name === "b")
|
|
evaluateSemanticBinding(entry, state);
|
|
const value = evaluateSemanticBinding(binding, state);
|
|
const firstCommon = property(property(value, "first"), "shared");
|
|
const secondCommon = property(property(value, "second"), "shared");
|
|
expect(property(firstCommon, "a").status).toBe("cycle");
|
|
expect(property(property(firstCommon, "b"), "own")).toEqual({
|
|
status: "literal",
|
|
value: "b",
|
|
});
|
|
expect(property(secondCommon, "b").status).toBe("cycle");
|
|
expect(property(property(secondCommon, "a"), "own")).toEqual({
|
|
status: "literal",
|
|
value: "a",
|
|
});
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
["const", ""],
|
|
["let", "target={value:2};"],
|
|
])(
|
|
"invalidates retained %s alias values at the producing mutation boundary",
|
|
(kind, later) => {
|
|
const parsed = parseJavaScriptSource(
|
|
`${kind} target = { value: 1 }; const alias = target; const root = { alias }; ${later}`,
|
|
"mutation.js",
|
|
);
|
|
if (parsed === null) throw new Error("Expected parsed source");
|
|
const steps = indexJavaScriptBindingsSteps(parsed.program);
|
|
let step = steps.next();
|
|
while (step.done !== true) step = steps.next();
|
|
const state = step.value;
|
|
const target = [...state.bindingsById.values()].find(
|
|
({ name }) => name === "target",
|
|
);
|
|
const root = [...state.bindingsById.values()].find(
|
|
({ name }) => name === "root",
|
|
);
|
|
if (target === undefined || root === undefined)
|
|
throw new Error("Missing binding");
|
|
expect(
|
|
property(
|
|
property(evaluateSemanticBinding(root, state), "alias"),
|
|
"value",
|
|
),
|
|
).toEqual({ status: "literal", value: 1 });
|
|
target.memberWrites.push({ path: ["value"], node: undefined });
|
|
clearSemanticValueCaches(state);
|
|
expect(
|
|
property(property(evaluateSemanticBinding(root, state), "alias"), "value")
|
|
.status,
|
|
).toBe("unknown");
|
|
},
|
|
);
|
|
|
|
const balancedOr = (names: string[]): string => {
|
|
if (names.length === 1) return names[0]!;
|
|
const middle = Math.floor(names.length / 2);
|
|
return `(${balancedOr(names.slice(0, middle))} || ${balancedOr(names.slice(middle))})`;
|
|
};
|
|
|
|
it("shares dependency proofs across a shallow wide reconvergent graph", () => {
|
|
const measure = (width: number) => {
|
|
const imports = Array.from({ length: width }, (_, index) => `l${index}`);
|
|
const aliases = Array.from({ length: width }, (_, index) => `a${index}`);
|
|
const source = `${imports.map((name) => `import * as ${name} from "fs";`).join("\n")}
|
|
const shared = ${balancedOr(imports)};
|
|
function consumer() {
|
|
${aliases.map((name) => `const ${name} = shared;`).join("\n")}
|
|
const root = ${balancedOr(aliases)};
|
|
}`;
|
|
const parsed = parseJavaScriptSource(source, "wide.js");
|
|
if (parsed === null) throw new Error("Expected parsed source");
|
|
const steps = indexJavaScriptBindingsSteps(parsed.program);
|
|
let step = steps.next();
|
|
while (step.done !== true) step = steps.next();
|
|
const state = step.value;
|
|
const rootBinding = [...state.bindingsById.values()].find(
|
|
({ name }) => name === "root",
|
|
);
|
|
if (rootBinding === undefined) throw new Error("Missing root binding");
|
|
// One root query simultaneously retains every alias memo in this family.
|
|
// Separate per-binding provenance queries have baseline repeated traversal;
|
|
// they must not be confused with this cache's retained closure expansion.
|
|
// Count representation construction and union work, not elapsed time/heap.
|
|
// Binding-ID additions also discriminate the superseded flattened Sets.
|
|
let bindingMemberships = 0;
|
|
let proofBranches = 0;
|
|
let unionCalls = 0;
|
|
const add = Set.prototype.add;
|
|
const freeze = Object.freeze;
|
|
const union = SemanticDependencyProofs.prototype.union;
|
|
const addSpy = vi.spyOn(Set.prototype, "add").mockImplementation(function (
|
|
this: Set<unknown>,
|
|
value: unknown,
|
|
) {
|
|
if (typeof value === "string" && value.includes("binding:"))
|
|
bindingMemberships++;
|
|
return add.call(this, value);
|
|
});
|
|
const freezeSpy = vi.spyOn(Object, "freeze").mockImplementation((value) => {
|
|
if (
|
|
typeof value === "object" &&
|
|
value !== null &&
|
|
Object.hasOwn(value, "zero") &&
|
|
Object.hasOwn(value, "one") &&
|
|
Object.keys(value).length === 2
|
|
)
|
|
proofBranches++;
|
|
return freeze(value);
|
|
});
|
|
const unionSpy = vi
|
|
.spyOn(SemanticDependencyProofs.prototype, "union")
|
|
.mockImplementation(function (
|
|
this: SemanticDependencyProofs,
|
|
first,
|
|
second,
|
|
) {
|
|
unionCalls++;
|
|
return union.call(this, first, second);
|
|
});
|
|
try {
|
|
expect(evaluateSemanticBinding(rootBinding, state).status).toBe(
|
|
"ambiguous",
|
|
);
|
|
expect(evaluateSemanticProvenance(rootBinding, state).origins).toEqual([
|
|
{ specifier: "fs", importedPath: [] },
|
|
]);
|
|
} finally {
|
|
unionSpy.mockRestore();
|
|
freezeSpy.mockRestore();
|
|
addSpy.mockRestore();
|
|
}
|
|
const result = analyzeJavaScriptSemantics(source);
|
|
expect(result.coverage).toEqual({ status: "complete", omittedCount: 0 });
|
|
for (const name of [...imports, ...aliases, "shared", "root"])
|
|
expect(
|
|
result.bindings.filter((entry) => entry.name === name),
|
|
).toHaveLength(1);
|
|
const root = result.bindings.find((entry) => entry.name === "root");
|
|
expect(root?.value.status).toBe("ambiguous");
|
|
expect(root?.provenance.origins).toEqual([
|
|
{ specifier: "fs", importedPath: [] },
|
|
]);
|
|
return {
|
|
storage: bindingMemberships + proofBranches,
|
|
work: bindingMemberships + unionCalls,
|
|
};
|
|
};
|
|
// Balanced depths remain small; doubling both independent widths must not
|
|
// quadruple proof construction/work while all meaningful aliases survive.
|
|
const small = measure(32);
|
|
const large = measure(64);
|
|
expect(small.storage).toBeGreaterThan(0);
|
|
expect(small.work).toBeGreaterThan(0);
|
|
expect(large.storage).toBeLessThan(small.storage * 3);
|
|
expect(large.work).toBeLessThan(small.work * 3);
|
|
});
|
|
|
|
it("uses immutable exact identity proofs and shares unchanged union branches", () => {
|
|
const proofs = new SemanticDependencyProofs(["a", "b", "c", "d", "e"]);
|
|
const a = proofs.binding("a");
|
|
const b = proofs.binding("b");
|
|
const c = proofs.binding("c");
|
|
const ab = proofs.union(a, b);
|
|
const abc = proofs.union(ab, c);
|
|
expect(proofs.union(abc, ab)).toBe(abc);
|
|
expect(proofs.binding("a")).toBe(a);
|
|
expect(Object.isFrozen(abc)).toBe(true);
|
|
expect(["a", "b", "c", "d", "e"].map((id) => proofs.has(abc, id))).toEqual([
|
|
true,
|
|
true,
|
|
true,
|
|
false,
|
|
false,
|
|
]);
|
|
expect(proofs.has(undefined, "a")).toBe(false);
|
|
expect(proofs.intersects(abc, new Map([["d", 0]]))).toBe(false);
|
|
expect(proofs.intersects(abc, new Map([["c", 0]]))).toBe(true);
|
|
const single = new SemanticDependencyProofs(["only"]);
|
|
expect(single.has(single.binding("only"), "only")).toBe(true);
|
|
expect(single.has(undefined, "only")).toBe(false);
|
|
expect(() => proofs.binding("unindexed")).toThrow(
|
|
"Unindexed semantic dependency",
|
|
);
|
|
});
|