mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 14:56:32 +00:00
fix(javascript): deduplicate shared projected source facts
This commit is contained in:
1 parent
d81d6b6707
commit
446333cb7f
3 files changed
+98
-9
No files matched your search
@@ -363,6 +363,27 @@ const projectedSourceFactsAtRead = (
|
||||
)
|
||||
return undefined;
|
||||
const sources: ProjectedSource[] = [];
|
||||
const sourceKeys = new WeakMap<t.Node, Set<string>>();
|
||||
const captureIds = new WeakMap<t.Node, number>();
|
||||
let nextCaptureId = 0;
|
||||
const appendSource = (source: ProjectedSource): void => {
|
||||
const captures = semanticNormalizeCapturedSelections(
|
||||
source.capturedSelections,
|
||||
)?.map(({ at, depth, ownEnumerable }) => {
|
||||
let id = captureIds.get(at);
|
||||
if (id === undefined) {
|
||||
id = nextCaptureId++;
|
||||
captureIds.set(at, id);
|
||||
}
|
||||
return [id, depth, ownEnumerable];
|
||||
});
|
||||
const key = JSON.stringify([source.path, captures]);
|
||||
const keys = sourceKeys.get(source.node) ?? new Set<string>();
|
||||
if (keys.has(key)) return;
|
||||
keys.add(key);
|
||||
sourceKeys.set(source.node, keys);
|
||||
sources.push(source);
|
||||
};
|
||||
for (const effect of writes) {
|
||||
if (!retainEligibility && superseded(effect)) continue;
|
||||
if (
|
||||
@@ -389,7 +410,7 @@ const projectedSourceFactsAtRead = (
|
||||
choice === undefined ? [] : [choice],
|
||||
);
|
||||
if (selection !== null)
|
||||
sources.push({
|
||||
appendSource({
|
||||
node: source.node,
|
||||
path: [...selection.projection, ...suffix],
|
||||
...(selection.capturedSelectionDepth === undefined
|
||||
@@ -415,7 +436,7 @@ const projectedSourceFactsAtRead = (
|
||||
for (const initializer of read.binding.initializers) {
|
||||
if (semanticReferenceIterationUnknown(initializer, state)) return undefined;
|
||||
if (semanticReferenceSourcesInactive(initializer, state)) continue;
|
||||
sources.push({
|
||||
appendSource({
|
||||
node: initializer.node,
|
||||
path: [...initializer.projection, ...path],
|
||||
});
|
||||
@@ -433,7 +454,7 @@ const projectedSourceFactsAtRead = (
|
||||
"identity",
|
||||
);
|
||||
if (projection !== null)
|
||||
sources.push({
|
||||
appendSource({
|
||||
node: initializer.node,
|
||||
path: [...projection, ...suffix],
|
||||
capturedSelections:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createContext, runInContext, runInNewContext } from "node:vm";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
|
||||
@@ -120,15 +121,34 @@ it.each(
|
||||
.replaceAll("SLOT", value.slot)
|
||||
.replaceAll("READ", value.read),
|
||||
expression: shape.read.replaceAll("READ", value.read),
|
||||
// The public module/delegate owner retains unknown after the copy's child
|
||||
// escapes into the new parent. Factory result selection can exclude it.
|
||||
expected: shared ? !value.host || value.slot !== "make" : value.host,
|
||||
// The new local parent shares the same descendant. Its own slot write
|
||||
// replaces the host value; an independent descendant keeps the old one.
|
||||
expected: shared ? !value.host : value.host,
|
||||
localCall: value.slot === "load",
|
||||
})),
|
||||
),
|
||||
),
|
||||
)(
|
||||
"distinguishes shared and newly allocated descendant receivers: $source",
|
||||
({ source, expression, expected }) => {
|
||||
({ source, expression, expected, localCall }) => {
|
||||
const hostCalls: string[] = [];
|
||||
const module = {
|
||||
require: (specifier: string) => {
|
||||
hostCalls.push(specifier);
|
||||
return {};
|
||||
},
|
||||
};
|
||||
const context = createContext({ module });
|
||||
expect(source.endsWith(expression + ";")).toBe(true);
|
||||
runInContext(source.slice(0, -expression.length - 1), context);
|
||||
if (expected || localCall) {
|
||||
runInContext(expression, context);
|
||||
} else {
|
||||
expect(() => runInContext(expression, context)).toThrow(
|
||||
expect.objectContaining({ name: "TypeError" }),
|
||||
);
|
||||
}
|
||||
expect(hostCalls).toEqual(expected ? ["./shared.cjs"] : []);
|
||||
const result = analyzeJavaScriptStaticSource(source);
|
||||
expect(result.parse_error_count).toBe(0);
|
||||
expect(
|
||||
@@ -143,6 +163,54 @@ it.each(
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{
|
||||
kind: "external call",
|
||||
iterator: false,
|
||||
source:
|
||||
"const child={loader:module};const box={child};external(child);box.child.loader.require('./uncertain.cjs');",
|
||||
},
|
||||
{
|
||||
kind: "custom iterator",
|
||||
iterator: true,
|
||||
source:
|
||||
"const child={loader:module};const box={child};const values=[child];values[Symbol.iterator]=external;for(const selected of values)selected.loader={};box.child.loader.require('./uncertain.cjs');",
|
||||
},
|
||||
])(
|
||||
"retains host eligibility across an opaque shared receiver: $kind",
|
||||
({ source, iterator }) => {
|
||||
const analysis = analyzeJavaScriptStaticSource(source);
|
||||
expect(analysis.references).toContainEqual(
|
||||
expect.objectContaining({ kind: "require", specifier: null }),
|
||||
);
|
||||
// Both executions fit the same unknown input: one retains the host loader,
|
||||
// the other replaces it. Neither outcome can prove the static alternative absent.
|
||||
for (const preserveHost of [true, false]) {
|
||||
const calls: string[] = [];
|
||||
const module = {
|
||||
require: (specifier: string) => {
|
||||
calls.push(specifier);
|
||||
return {};
|
||||
},
|
||||
};
|
||||
const external = iterator
|
||||
? function* (this: readonly { loader: unknown }[]) {
|
||||
yield preserveHost ? { loader: {} } : this[0]!;
|
||||
}
|
||||
: (child: { loader: unknown }) => {
|
||||
if (!preserveHost) child.loader = {};
|
||||
};
|
||||
try {
|
||||
runInNewContext(source, { module, external });
|
||||
} catch (error) {
|
||||
expect(error).toMatchObject({ name: "TypeError" });
|
||||
expect(preserveHost).toBe(false);
|
||||
}
|
||||
expect(calls).toEqual(preserveHost ? ["./uncertain.cjs"] : []);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["module", "{}"])(
|
||||
"selects the later write to a shared projected receiver: %s",
|
||||
(next) => {
|
||||
|
||||
Reference in new issue
Block a user