mirror of
https://github.com/morluto/rea.git
synced 2026-10-27 09:55:46 +00:00
fix(javascript): retain stored receiver capture authority
This commit is contained in:
1 parent
d3a33fcfca
commit
eb721ebbf1
3 files changed
+284
-23
No files matched your search
@@ -33,6 +33,13 @@ import {
|
||||
evaluateSemanticExpression,
|
||||
} from "./javascriptSemanticValues.js";
|
||||
import { semanticContainer } from "./javascriptSemanticSlots.js";
|
||||
import {
|
||||
semanticProjectedSourceFactsAtRead,
|
||||
semanticShiftCapturedSelections,
|
||||
semanticNormalizeCapturedSelections,
|
||||
type SemanticCapturedSelection,
|
||||
} from "./javascriptSemanticProjectedSources.js";
|
||||
|
||||
import {
|
||||
semanticArrayPropertyReferences,
|
||||
semanticClassCandidates,
|
||||
@@ -84,6 +91,13 @@ import {
|
||||
} from "./javascriptAstValues.js";
|
||||
|
||||
type PropertyPath = JavaScriptSemanticPropertyPath;
|
||||
interface MutationSelectionContext {
|
||||
readonly memberReadAt?: t.Node | undefined;
|
||||
readonly capturedSelections?:
|
||||
| readonly SemanticCapturedSelection[]
|
||||
| undefined;
|
||||
}
|
||||
|
||||
type ValueEffect = "write" | "escape";
|
||||
|
||||
/** Only replacing or possibly selecting an iterator-bearing prototype slot changes iteration. */
|
||||
@@ -570,7 +584,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
// A capture key already ends the array; a path stays one nested element.
|
||||
const lifetimeKey = (captureKey: string, path: PropertyPath): string =>
|
||||
`${captureKey.slice(0, -1)},${JSON.stringify(path)}]`;
|
||||
const pendingReferences: {
|
||||
const pendingReferences: (MutationSelectionContext & {
|
||||
readonly initializer: JavaScriptSemanticBindingState["referenceInitializers"][number];
|
||||
readonly path: PropertyPath;
|
||||
readonly bindings: ReadonlySet<string>;
|
||||
@@ -580,7 +594,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
readonly widenedGetterResult?: true;
|
||||
readonly definiteReceiver: boolean;
|
||||
readonly assignedSources?: readonly SemanticIterationSource[];
|
||||
}[] = [];
|
||||
})[] = [];
|
||||
const iterableReferences: IterableReference[] = [];
|
||||
const iterableReferenceKeys = new WeakMap<t.Node, Set<string>>();
|
||||
const deferIterable = (reference: IterableReference): void => {
|
||||
@@ -594,6 +608,14 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
reference.mutation?.end,
|
||||
reference.originAt?.start,
|
||||
reference.originAt?.end,
|
||||
reference.memberReadAt?.start,
|
||||
reference.memberReadAt?.end,
|
||||
reference.capturedSelections?.map(({ at, depth, ownEnumerable }) => [
|
||||
at.start,
|
||||
at.end,
|
||||
depth,
|
||||
ownEnumerable,
|
||||
]),
|
||||
reference.iterationOnly,
|
||||
reference.widenedGetterResult === true,
|
||||
reference.definiteReceiver === true,
|
||||
@@ -637,26 +659,41 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
widenedGetterResult = false,
|
||||
assignedSources?: readonly SemanticIterationSource[],
|
||||
definiteReceiver = true,
|
||||
selection?: MutationSelectionContext,
|
||||
): void => {
|
||||
const pending: (ReferencedValue & {
|
||||
readonly bindings: ReadonlySet<string>;
|
||||
readonly originAt: t.Node | undefined;
|
||||
readonly definiteReceiver: boolean;
|
||||
})[] = [
|
||||
const pending: (ReferencedValue &
|
||||
MutationSelectionContext & {
|
||||
readonly bindings: ReadonlySet<string>;
|
||||
readonly originAt: t.Node | undefined;
|
||||
readonly definiteReceiver: boolean;
|
||||
})[] = [
|
||||
{
|
||||
node,
|
||||
path,
|
||||
bindings,
|
||||
originAt,
|
||||
definiteReceiver,
|
||||
memberReadAt: selection?.memberReadAt ?? mutation ?? originAt,
|
||||
capturedSelections: selection?.capturedSelections,
|
||||
...(ownerInitializer === undefined ? {} : { ownerInitializer }),
|
||||
},
|
||||
];
|
||||
// Visit shallower alias traversals first so their exact prefixes suppress
|
||||
// longer loop paths before those paths enumerate every assignment order.
|
||||
for (let cursor = 0; cursor < pending.length; cursor++) {
|
||||
const current = pending[cursor];
|
||||
if (current === undefined) break;
|
||||
const candidate = pending[cursor];
|
||||
if (candidate === undefined) break;
|
||||
// A capture deeper than the selected path is no longer applicable. Keeping
|
||||
// it would manufacture new contexts on a repeated source alias cycle.
|
||||
const current = candidate.capturedSelections?.length
|
||||
? {
|
||||
...candidate,
|
||||
capturedSelections: semanticNormalizeCapturedSelections(
|
||||
candidate.capturedSelections,
|
||||
candidate.path.length,
|
||||
),
|
||||
}
|
||||
: candidate;
|
||||
const expression = unwrapJavaScriptExpression(current.node).node;
|
||||
const required = semanticRequireOrigin(expression, state);
|
||||
if (required !== undefined) {
|
||||
@@ -719,11 +756,23 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
// An alias restored from a saved reference can revisit the same binding
|
||||
// at an earlier capture or a shorter getter-receiver path. Only a
|
||||
// repeated capture and path closes a cycle.
|
||||
const captureKey = captureKeyOf(
|
||||
binding,
|
||||
current.originAt,
|
||||
current.ownerInitializer,
|
||||
);
|
||||
const captureSelectionIdentity = JSON.stringify([
|
||||
current.memberReadAt?.start,
|
||||
current.memberReadAt?.end,
|
||||
current.capturedSelections?.map(({ at, depth, ownEnumerable }) => [
|
||||
at.start,
|
||||
at.end,
|
||||
depth,
|
||||
ownEnumerable,
|
||||
]),
|
||||
]);
|
||||
// Ancestry closes repeated source identities before recursively selected
|
||||
// copy vectors grow; independent contexts remain distinct in coverage.
|
||||
const captureKey = JSON.stringify([
|
||||
captureKeyOf(binding, current.originAt, current.ownerInitializer),
|
||||
current.memberReadAt?.start,
|
||||
current.memberReadAt?.end,
|
||||
]);
|
||||
const lifetime = lifetimeKey(captureKey, current.path);
|
||||
if (current.bindings.has(lifetime)) continue;
|
||||
// A self alias such as `node = node[key]` revisits its capture with the
|
||||
@@ -756,6 +805,9 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
current.originAt,
|
||||
current.ownerInitializer,
|
||||
widenedGetterResult,
|
||||
undefined,
|
||||
undefined,
|
||||
current,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
@@ -788,6 +840,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
current.originAt?.end,
|
||||
current.ownerInitializer?.start,
|
||||
current.ownerInitializer?.end,
|
||||
captureSelectionIdentity,
|
||||
]);
|
||||
const previous = selections.get(selectionIdentity);
|
||||
const excludedKeys =
|
||||
@@ -844,6 +897,10 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
mutation,
|
||||
current.originAt,
|
||||
current.ownerInitializer,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
current,
|
||||
);
|
||||
}
|
||||
// Keep an antichain of exact paths. Widening distinct member keys would
|
||||
@@ -860,7 +917,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
current.originAt?.start ?? -1
|
||||
}\u0001${current.originAt?.end ?? -1}\u0001${
|
||||
current.ownerInitializer?.start ?? -1
|
||||
}\u0001${current.ownerInitializer?.end ?? -1}\u0001${widenedGetterResult}\u0001${current.definiteReceiver}\u0001${assignedSources?.[0]?.target?.start ?? -1}`;
|
||||
}\u0001${current.ownerInitializer?.end ?? -1}\u0001${widenedGetterResult}\u0001${current.definiteReceiver}\u0001${assignedSources?.[0]?.target?.start ?? -1}\u0001${captureSelectionIdentity}`;
|
||||
let covering = coveringPaths.get(pathState);
|
||||
if (covering === undefined) {
|
||||
covering = new SemanticPropertyPathCoverage();
|
||||
@@ -883,6 +940,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
nested.add(captureKey);
|
||||
// Returning a literal's receiver reaches that allocation, not the
|
||||
// binding's older values read while evaluating its initializer.
|
||||
let retainsInitializers = true;
|
||||
if (!primitiveWrite) {
|
||||
for (const origin of origins.directOrigins) {
|
||||
const shared = semanticMutableBuiltinOrigin(origin, path);
|
||||
@@ -898,6 +956,27 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
...origins.initializers,
|
||||
...origins.referenceInitializers,
|
||||
];
|
||||
const receiver =
|
||||
effect === "write" &&
|
||||
current.path.length > 1 &&
|
||||
binding.memberWrites.some(
|
||||
(write) =>
|
||||
write.assignedSources !== undefined &&
|
||||
write.path.length < current.path.length,
|
||||
)
|
||||
? semanticProjectedSourceFactsAtRead(
|
||||
binding,
|
||||
current.node,
|
||||
current.path.slice(0, -1),
|
||||
state,
|
||||
false,
|
||||
current.memberReadAt ?? current.originAt ?? current.node,
|
||||
current.capturedSelections,
|
||||
)
|
||||
: undefined;
|
||||
// A defined owner result already includes every eligible initializer;
|
||||
// replaying them separately duplicates selection/capture histories.
|
||||
retainsInitializers = receiver === undefined;
|
||||
if (effect === "escape")
|
||||
binding.escapedPaths.push({
|
||||
path,
|
||||
@@ -905,7 +984,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
receiverInitializers,
|
||||
...(current.originAt === undefined
|
||||
? {}
|
||||
: { receiverAt: current.originAt }),
|
||||
: { receiverAt: current.memberReadAt ?? current.originAt }),
|
||||
});
|
||||
else
|
||||
binding.memberWrites.push({
|
||||
@@ -917,11 +996,34 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
receiverInitializers,
|
||||
...(current.originAt === undefined
|
||||
? {}
|
||||
: { receiverAt: current.originAt }),
|
||||
: { receiverAt: current.memberReadAt ?? current.originAt }),
|
||||
...(assignedSources === undefined ? {} : { assignedSources }),
|
||||
});
|
||||
clearSemanticValueCaches(state);
|
||||
for (const initializer of origins.initializers)
|
||||
// Resolve a stored child at the receiver capture, before selecting
|
||||
// the final written slot. The source owner preserves replacements
|
||||
// and captures; a local container does not isolate its children.
|
||||
if (receiver !== undefined) {
|
||||
for (const source of receiver.sources)
|
||||
pending.push({
|
||||
node: source.node,
|
||||
path: [...source.path, current.path.at(-1)!],
|
||||
bindings: nested,
|
||||
definiteReceiver: false,
|
||||
originAt: source.node,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticNormalizeCapturedSelections([
|
||||
...(semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
source.path.length - (current.path.length - 1),
|
||||
) ?? []),
|
||||
...(source.capturedSelections ?? []),
|
||||
]),
|
||||
});
|
||||
}
|
||||
for (const initializer of retainsInitializers
|
||||
? origins.initializers
|
||||
: [])
|
||||
pending.push({
|
||||
node: initializer.node,
|
||||
path: [...initializer.projection, ...path],
|
||||
@@ -932,9 +1034,16 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
origins.referenceInitializers.length ===
|
||||
1,
|
||||
originAt: initializer.node,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
initializer.projection.length,
|
||||
),
|
||||
});
|
||||
}
|
||||
for (const initializer of origins.referenceInitializers) {
|
||||
for (const initializer of retainsInitializers
|
||||
? origins.referenceInitializers
|
||||
: []) {
|
||||
const copiedPath = semanticCopiedReferencePath(
|
||||
initializer,
|
||||
path,
|
||||
@@ -957,6 +1066,11 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
effect,
|
||||
...(widenedGetterResult ? { widenedGetterResult: true } : {}),
|
||||
originAt: initializer.node,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
copiedPath.length - path.length,
|
||||
),
|
||||
...(mutation === undefined ? {} : { mutation }),
|
||||
});
|
||||
}
|
||||
@@ -974,6 +1088,17 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
bindings: current.bindings,
|
||||
definiteReceiver: current.definiteReceiver && key !== null,
|
||||
originAt: current.originAt,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticNormalizeCapturedSelections([
|
||||
...(semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
1,
|
||||
) ?? []),
|
||||
...(current.originAt !== undefined &&
|
||||
unwrapJavaScriptExpression(current.originAt).node === expression
|
||||
? [{ at: current.originAt, depth: 1 }]
|
||||
: []),
|
||||
]),
|
||||
});
|
||||
} else {
|
||||
const references =
|
||||
@@ -985,6 +1110,13 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
)
|
||||
: expandedValues(expression, current.path, effect);
|
||||
for (const value of references) {
|
||||
if (
|
||||
value.inherited &&
|
||||
current.capturedSelections?.some(
|
||||
(capture) => capture.depth === 1 && capture.ownEnumerable,
|
||||
)
|
||||
)
|
||||
continue;
|
||||
if (
|
||||
value.escapes &&
|
||||
(effect !== "escape" ||
|
||||
@@ -999,6 +1131,9 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
current.originAt,
|
||||
value.ownerInitializer,
|
||||
widenedGetterResult || value.widenedGetterResult === true,
|
||||
undefined,
|
||||
undefined,
|
||||
current,
|
||||
);
|
||||
else if (value.iterableFallbackPath !== undefined)
|
||||
deferIterable({
|
||||
@@ -1011,6 +1146,11 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
...(assignedSources === undefined ? {} : { assignedSources }),
|
||||
effect,
|
||||
originAt: current.originAt,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
value.path.length - current.path.length,
|
||||
),
|
||||
...(widenedGetterResult ? { widenedGetterResult: true } : {}),
|
||||
...(mutation === undefined ? {} : { mutation }),
|
||||
});
|
||||
@@ -1034,6 +1174,24 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
(key) => key !== null && typeof key !== "object",
|
||||
),
|
||||
originAt: current.originAt,
|
||||
memberReadAt: current.memberReadAt,
|
||||
capturedSelections: semanticNormalizeCapturedSelections([
|
||||
...(semanticShiftCapturedSelections(
|
||||
current.capturedSelections,
|
||||
value.path.length - current.path.length,
|
||||
) ?? []),
|
||||
...(value.capturedSelectionAt === undefined
|
||||
? []
|
||||
: [
|
||||
{
|
||||
at: value.capturedSelectionAt,
|
||||
depth: value.path.length - current.path.length + 1,
|
||||
...(value.ownEnumerable
|
||||
? { ownEnumerable: true as const }
|
||||
: {}),
|
||||
},
|
||||
]),
|
||||
]),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1369,6 +1527,8 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
? { widenedGetterResult: true }
|
||||
: {}),
|
||||
originAt: initializer.node,
|
||||
memberReadAt: reference.memberReadAt,
|
||||
capturedSelections: reference.capturedSelections,
|
||||
...(reference.mutation === undefined
|
||||
? {}
|
||||
: { mutation: reference.mutation }),
|
||||
@@ -1394,6 +1554,8 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
? { widenedGetterResult: true }
|
||||
: {}),
|
||||
originAt: reference.originAt,
|
||||
memberReadAt: reference.memberReadAt,
|
||||
capturedSelections: reference.capturedSelections,
|
||||
...(reference.mutation === undefined
|
||||
? {}
|
||||
: { mutation: reference.mutation }),
|
||||
@@ -1410,6 +1572,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
reference.widenedGetterResult === true,
|
||||
reference.assignedSources,
|
||||
reference.definiteReceiver,
|
||||
reference,
|
||||
);
|
||||
}
|
||||
for (const [index, reference] of [...iterableReferences].entries()) {
|
||||
@@ -1438,6 +1601,7 @@ export function* collectSemanticMemberMutationsSteps(
|
||||
reference.widenedGetterResult === true,
|
||||
reference.assignedSources,
|
||||
reference.definiteReceiver === true,
|
||||
reference,
|
||||
);
|
||||
}
|
||||
// Native Map lookup methods do not expose their receiver to user code.
|
||||
@@ -1470,7 +1634,7 @@ interface ReferencedValue extends JavaScriptSemanticObjectReference {
|
||||
readonly iterableFallbackPath?: PropertyPath;
|
||||
}
|
||||
|
||||
interface IterableReference extends ReferencedValue {
|
||||
interface IterableReference extends ReferencedValue, MutationSelectionContext {
|
||||
readonly projection: PropertyPath;
|
||||
readonly fallbackPath: PropertyPath;
|
||||
readonly bindings: ReadonlySet<string>;
|
||||
|
||||
Reference in new issue
Block a user