fix(javascript): converge cyclic aliases with explicit projection widening

Replace initializer-history and full-path enumeration with a finite projection
join at each source read. Retain conservative local returned-reference effects
and disclose precision loss through static, semantic and captured-web results.
Close full-binding callback cycles while preserving narrowed read semantics.

Fixes #4348
Fixes #5138
Refs #4349
Refs #4364

Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Fuxuan-CN <3072252442@qq.com>
This commit is contained in:
morluto
2026-10-19 12:26:54 +08:00
parent 9af0b6113c
commit e4c3d72396
19 changed files with 791 additions and 166 deletions
@@ -61,7 +61,8 @@ it("bounds shared partial callback alias expansion by AST size", () => {
);
try {
expect(
createSemanticReturnedReferences(context.state)(last.argument.callee),
createSemanticReturnedReferences(context.state)(last.argument.callee)
.references,
).toHaveLength(1);
return {
callReads,
@@ -154,7 +155,8 @@ return a${depth}.make();}`);
);
try {
expect(
createSemanticReturnedReferences(context.state)(last.argument.callee),
createSemanticReturnedReferences(context.state)(last.argument.callee)
.references,
).toHaveLength(1);
return lookups.reduce(
(total, lookup) => total + lookup.mock.calls.length,
@@ -231,3 +233,37 @@ it("does not memoize alias cycle cuts across entry paths", () => {
});
}
});
const meshLookups = (writes: number): number => {
const source = `function f(){} function g(){} let a=f,b=g;
function run(){${Array.from({ length: writes }, (_, index) =>
index % 2 === 0 ? "a=b;" : "b=a;",
).join("")} a();}`;
const context = indexed(source);
const run = context.state.program.body[3];
if (!t.isFunctionDeclaration(run)) throw new Error("Missing run");
const statement = run.body.body.at(-1);
if (
!t.isExpressionStatement(statement) ||
!t.isCallExpression(statement.expression)
)
throw new Error("Missing mesh call");
const lookups = vi.spyOn(context.bindingCache, "get");
const result = context.resolve(statement.expression.callee);
expect(result).toMatchObject({ complete: false, cycleDependent: true });
expect(
result.callableIds.map((id) => context.callableById.get(id)?.name).sort(),
).toEqual(["f", "g"]);
return lookups.mock.calls.length;
};
it("closes cycles at full-binding reads with bounded lookup growth", () => {
const six = meshLookups(6);
const eight = meshLookups(8);
const ten = meshLookups(10);
const twelve = meshLookups(12);
expect(six).toBeGreaterThan(0);
expect(eight).toBeLessThan(3 * six);
expect(ten).toBeLessThan(3 * eight);
expect(twelve).toBeLessThan(3 * ten);
});