mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 10:55:57 +00:00
fix(javascript): bound member-mutation paths through reassignment loops
Fixes #1401. Refs #1065. Preserve exact member facts and equal-depth write effects while bounding the observed reassignment-loop expansion. Validated with the JavaScript semantic/application suite, focused alias regressions, full static checks, real compiled CLI and SDK MCP Notion workflows, and all 20 checks on the final PR head. Co-Authored-By: Marnix Pluim <286664873+marnixpluim@users.noreply.github.com> Co-Authored-By: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
4 files changed
+524
-14
No files matched your search
@@ -9,6 +9,163 @@ const resultValue = (body: string) =>
|
||||
"result",
|
||||
).returnSites[0]?.value;
|
||||
|
||||
describe("bounded member path propagation", () => {
|
||||
it("bounds cyclic member paths with no observed container origin", () => {
|
||||
const branches = Array.from(
|
||||
{ length: 16 },
|
||||
(_, index) => `if (flag === ${index}) node = node.m${index};`,
|
||||
).join(" else ");
|
||||
const start = performance.now();
|
||||
const callable = onlyCallable(
|
||||
analyzeJavaScriptSemantics(
|
||||
`export function result(seed) { let node=seed; while(node) { ${branches} use(node.state); } return seed.keep; }`,
|
||||
),
|
||||
"result",
|
||||
);
|
||||
expect(callable.returnSites[0]?.value.status).toBe("unknown");
|
||||
expect(performance.now() - start).toBeLessThan(2000);
|
||||
}, 30000);
|
||||
|
||||
it("preserves Array prototype effects through an otherwise unknown alias", () => {
|
||||
expect(
|
||||
resultValue(
|
||||
"const values=[1]; const prototype=Array.prototype; prototype[Symbol.iterator]=custom; for(const item of values) { mutate(item); } return values[0];",
|
||||
)?.status,
|
||||
).toBe("unknown");
|
||||
});
|
||||
|
||||
it("bounds loops derived from a scalar member while preserving sibling facts", () => {
|
||||
const branches = Array.from(
|
||||
{ length: 16 },
|
||||
(_, index) => `if (flag === ${index}) node = node.m${index};`,
|
||||
).join(" else ");
|
||||
const start = performance.now();
|
||||
expect(
|
||||
resultValue(
|
||||
`const source={child:1,keep:{value:3}}; let node=source.child; while(node) { ${branches} use(node.state); } return source.keep.value;`,
|
||||
),
|
||||
).toEqual({ status: "literal", value: 3 });
|
||||
expect(performance.now() - start).toBeLessThan(2000);
|
||||
}, 30000);
|
||||
|
||||
it.each([
|
||||
["child:external,", "child", "present"],
|
||||
["", "missing", "unknown-coverage"],
|
||||
])(
|
||||
"retains slot presence below an unobserved container: %s",
|
||||
(property, name, presence) => {
|
||||
expect(
|
||||
resultValue(
|
||||
`const source={${property}keep:3}; const alias=source.${name}; alias.deep.value=2; return source;`,
|
||||
),
|
||||
).toMatchObject({
|
||||
status: "object",
|
||||
properties: expect.arrayContaining([
|
||||
{
|
||||
name,
|
||||
presence,
|
||||
value: {
|
||||
status: "unknown",
|
||||
reason: "This property may have been mutated.",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "keep",
|
||||
presence: "present",
|
||||
value: { status: "literal", value: 3 },
|
||||
},
|
||||
]),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
["use(node);", "m0.value", "unknown"],
|
||||
["node.value = 2;", "m0.value", "unknown"],
|
||||
["node.value = 2;", "mode", "literal"],
|
||||
])(
|
||||
"bounds a loop that reassigns one reference through many members: %s seed.%s",
|
||||
(effect, read, status) => {
|
||||
const branches = Array.from(
|
||||
{ length: 9 },
|
||||
(_, index) => `if (flag === ${index}) node = node.m${index};`,
|
||||
).join(" else ");
|
||||
const start = performance.now();
|
||||
const value = resultValue(
|
||||
`const seed = { mode: "initial", m0: { value: 1 } }; let node = seed; while (node) { ${branches} ${effect} } return seed.${read};`,
|
||||
);
|
||||
expect(performance.now() - start).toBeLessThan(2000);
|
||||
expect(value?.status).toBe(status);
|
||||
},
|
||||
30000,
|
||||
);
|
||||
|
||||
it.each([
|
||||
["const alias = flag ? owner.child : owner; alias.value = 2;", "value"],
|
||||
["const alias = flag ? owner : owner.child; alias.value = 2;", "value"],
|
||||
["const alias = flag ? owner.child : owner; alias[key] = 2;", "value"],
|
||||
[
|
||||
"const alias = flag ? owner.child : owner; alias.child = 2;",
|
||||
"child.value",
|
||||
],
|
||||
[
|
||||
"let alias = owner; let i = 0; while (i++ < 1) { alias = flag ? alias.child : alias; alias.value = 2; }",
|
||||
"value",
|
||||
],
|
||||
])(
|
||||
"keeps a write that reaches a child through a longer path: %s",
|
||||
(body, read) => {
|
||||
expect(
|
||||
resultValue(
|
||||
`const shared = { value: 1, child: { value: 1 } }; const owner = { child: shared }; ${body} return shared.${read};`,
|
||||
)?.status,
|
||||
).toBe("unknown");
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
"mutate(source.left, source.right);",
|
||||
"const alias = flag ? source.left : source.right; mutate(alias);",
|
||||
"const copy = flag ? [source.left] : [source.right]; mutate(copy);",
|
||||
"const alias = flag ? source.left : source.right; alias.value = 2;",
|
||||
])("keeps members outside the mutated paths: %s", (body) => {
|
||||
expect(
|
||||
resultValue(
|
||||
`const source = { left: { value: 1 }, right: { value: 2 }, keep: { value: 3 } }; ${body} return source.keep.value;`,
|
||||
),
|
||||
).toEqual({ status: "literal", value: 3 });
|
||||
});
|
||||
});
|
||||
|
||||
describe("distinct observable mutation paths", () => {
|
||||
it.each([
|
||||
["alias.value = 2;", "left"],
|
||||
["alias.value = 2;", "right"],
|
||||
["mutate(alias);", "left"],
|
||||
["mutate(alias);", "right"],
|
||||
])(
|
||||
"propagates both conditional paths to shared bindings: %s %s",
|
||||
(effect, target) => {
|
||||
expect(
|
||||
resultValue(
|
||||
`const left={value:1}; const right={value:2}; const source={left,right}; const alias=flag?source.left:source.right; ${effect} return ${target}.value;`,
|
||||
)?.status,
|
||||
).toBe("unknown");
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["mutate(alias);", "alias.value = 2;"])(
|
||||
"preserves sibling facts below a shared prefix: %s",
|
||||
(effect) => {
|
||||
expect(
|
||||
resultValue(
|
||||
`const shared={left:{value:1},right:{value:2},keep:{value:3}}; const source={nested:shared}; const alias=flag?source.nested.left:source.nested.right; ${effect} return shared.keep.value;`,
|
||||
),
|
||||
).toEqual({ status: "literal", value: 3 });
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("reference lifetimes across calls and shallow copies", () => {
|
||||
it("bounds dynamic destructuring across shared alias branches", () => {
|
||||
const declarations = Array.from(
|
||||
|
||||
Reference in new issue
Block a user