mirror of
https://github.com/morluto/rea.git
synced 2026-11-02 21:06:04 +00:00
refactor(javascript): share computed-key callable ownership
Give calls, captures, object operations, promises and effects one callable-owner tracker. Suspend a method while its computed key is evaluated in the enclosing callable; remove the capture-only downstream repair. Co-authored-by: Alex Desir <adesir089@gmail.com>
This commit is contained in:
1 parent
6396851958
commit
76fa5304a8
8 files changed
+245
-107
No files matched your search
@@ -158,6 +158,121 @@ describe("JavaScript semantic analysis: calls 1", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("JavaScript semantic analysis: computed method keys", () => {
|
||||
it("evaluates computed method keys in the enclosing callable", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
function outer() {
|
||||
const name = "m";
|
||||
const pick = () => "n";
|
||||
const obj = { [name]() { return 1; }, [pick()]() { return name; } };
|
||||
class C { [name]() { return 2; } }
|
||||
}
|
||||
`);
|
||||
|
||||
const outer = onlyCallable(ir, "outer");
|
||||
const pickCall = ir.callSites.find(
|
||||
({ calleeCallableIds }) => calleeCallableIds.length === 1,
|
||||
);
|
||||
expect(pickCall).toMatchObject({
|
||||
callerCallableId: outer.callableId,
|
||||
resolution: "exact",
|
||||
});
|
||||
// Only the method body that reads `name` captures it; the computed keys
|
||||
// are evaluated outside every method environment.
|
||||
expect(
|
||||
ir.closureCaptures.map(({ callableId, bindingId }) => ({
|
||||
owner: ir.callables.find(
|
||||
(callable) => callable.callableId === callableId,
|
||||
)?.kind,
|
||||
binding: bindingById(ir, bindingId)?.name,
|
||||
})),
|
||||
).toEqual([{ owner: "method", binding: "name" }]);
|
||||
});
|
||||
|
||||
it("keeps callables nested in a computed method key in their own frames", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
function outer() {
|
||||
const name = "m";
|
||||
const pick = () => "n";
|
||||
return { [(() => pick())()]() { return name; } };
|
||||
}
|
||||
`);
|
||||
|
||||
const outer = onlyCallable(ir, "outer");
|
||||
const pick = onlyCallable(ir, "pick");
|
||||
const keyArrow = ir.callables.find(
|
||||
({ kind, name }) => kind === "function" && name === null,
|
||||
);
|
||||
const method = ir.callables.find(({ kind }) => kind === "method");
|
||||
expect(keyArrow).toBeDefined();
|
||||
expect(method).toBeDefined();
|
||||
const callerOf = (calleeCallableId: string | undefined) =>
|
||||
ir.callSites.find(({ calleeCallableIds }) =>
|
||||
calleeCallableIds.includes(calleeCallableId ?? ""),
|
||||
)?.callerCallableId;
|
||||
expect(callerOf(pick.callableId)).toBe(keyArrow?.callableId);
|
||||
expect(callerOf(keyArrow?.callableId)).toBe(outer.callableId);
|
||||
expect(
|
||||
ir.closureCaptures.map(({ callableId, bindingId }) => ({
|
||||
callableId,
|
||||
binding: bindingById(ir, bindingId)?.name,
|
||||
})),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ callableId: keyArrow?.callableId, binding: "pick" },
|
||||
{ callableId: method?.callableId, binding: "name" },
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("attributes effects in computed method keys to the enclosing callable", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
import { spawn } from "node:child_process";
|
||||
function outer(config) {
|
||||
return {
|
||||
[fetch("/x")]() {},
|
||||
[setTimeout(tick, 1)]() {},
|
||||
[Promise.resolve(1)]() {},
|
||||
[spawn("ls")]() {},
|
||||
[config.key]() {},
|
||||
};
|
||||
}
|
||||
class C { [fetch("/y")]() {} }
|
||||
`);
|
||||
|
||||
const outer = onlyCallable(ir, "outer");
|
||||
const owners = (
|
||||
operations: readonly { readonly ownerCallableId: string | null }[],
|
||||
) => operations.map(({ ownerCallableId }) => ownerCallableId);
|
||||
const methodIds = new Set(
|
||||
ir.callables
|
||||
.filter(({ kind }) => kind === "method")
|
||||
.map(({ callableId }) => callableId),
|
||||
);
|
||||
expect(methodIds.size).toBe(6);
|
||||
const classId = onlyCallable(ir, "C").callableId;
|
||||
expect(owners(ir.requestOperations).sort()).toEqual(
|
||||
[outer.callableId, classId].sort(),
|
||||
);
|
||||
expect(owners(ir.timerOperations)).toEqual([outer.callableId]);
|
||||
expect(owners(ir.promiseOperations)).toEqual([outer.callableId]);
|
||||
expect(owners(ir.childProcessSpawns)).toEqual([outer.callableId]);
|
||||
expect(
|
||||
ir.objectOperations.filter(({ propertyPath }) =>
|
||||
propertyPath?.includes("key"),
|
||||
),
|
||||
).toEqual([expect.objectContaining({ ownerCallableId: outer.callableId })]);
|
||||
for (const owner of [
|
||||
...owners(ir.requestOperations),
|
||||
...owners(ir.timerOperations),
|
||||
...owners(ir.promiseOperations),
|
||||
...owners(ir.childProcessSpawns),
|
||||
...owners(ir.objectOperations),
|
||||
])
|
||||
expect(methodIds.has(owner ?? "")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("JavaScript semantic analysis: calls 2", () => {
|
||||
it("does not treat a shadowed Promise binding as the intrinsic", () => {
|
||||
const ir = analyzeJavaScriptSemantics(`
|
||||
|
||||
Reference in new issue
Block a user