mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
fix(javascript): preserve incoming callback target uncertainty (#4243)
Preserve incoming callback uncertainty and resolve proven preceding writes. Share read-position and initializer identity facts with returned-reference analysis; cache completed projected work with actual cycle dependencies. Fixes #3880
This commit is contained in:
@@ -558,6 +558,7 @@ export const semanticMutationInitializers = (
|
||||
return binding;
|
||||
const { retained, replacesEntry } = retainedInitializerOrigins(
|
||||
origins,
|
||||
binding,
|
||||
parents,
|
||||
);
|
||||
return {
|
||||
@@ -629,6 +630,8 @@ const bindingReadPosition = (
|
||||
/** Select a local alias at its read, before any later straight-line reassignment. */
|
||||
type SemanticBindingRead = {
|
||||
readonly status: "ordered" | "unresolved";
|
||||
/** A retained initializer executes on paths reaching this particular read. */
|
||||
readonly hasDefiniteInitializer: boolean;
|
||||
/** Unresolved reads retain all possible origins for conservative alias effects. */
|
||||
readonly binding: JavaScriptSemanticBindingState;
|
||||
};
|
||||
@@ -642,9 +645,10 @@ export const semanticBindingAtRead = (
|
||||
binding.initializers.length === 0 &&
|
||||
binding.referenceInitializers.length === 0
|
||||
)
|
||||
return { status: "ordered", binding };
|
||||
return { status: "ordered", binding, hasDefiniteInitializer: false };
|
||||
const initializer = binding.initializers[0];
|
||||
const reference = binding.referenceInitializers[0];
|
||||
const origin = initializer ?? reference;
|
||||
const sourcePosition =
|
||||
initializer === undefined
|
||||
? reference === undefined
|
||||
@@ -652,28 +656,55 @@ export const semanticBindingAtRead = (
|
||||
: initializerPosition(reference, parents, true)
|
||||
: initializerPosition(initializer, parents);
|
||||
const { position, repeating } = bindingReadPosition(read, parents);
|
||||
// Coverage and latest-value selection are independent. A preceding write
|
||||
// can cover entry even when another write's order remains unknown.
|
||||
const hasDefiniteInitializer =
|
||||
position !== null &&
|
||||
binding.initializers.some((initializer) => {
|
||||
const source = initializerPosition(initializer, parents);
|
||||
return (
|
||||
source !== null &&
|
||||
positionPrecedes(source, position) &&
|
||||
!initializerMayRetainEntry(initializer, binding, parents)
|
||||
);
|
||||
});
|
||||
if (
|
||||
!repeating &&
|
||||
origin !== undefined &&
|
||||
binding.initializers.length + binding.referenceInitializers.length === 1 &&
|
||||
sourcePosition !== null &&
|
||||
position !== null &&
|
||||
positionPrecedes(sourcePosition, position)
|
||||
)
|
||||
return { status: "ordered", binding };
|
||||
return {
|
||||
status: "ordered",
|
||||
binding,
|
||||
hasDefiniteInitializer,
|
||||
};
|
||||
const uncertainOrder =
|
||||
repeating ||
|
||||
sourcePosition === null ||
|
||||
position === null ||
|
||||
sourcePosition.body !== position.body;
|
||||
if (uncertainOrder) return { status: "unresolved", binding };
|
||||
if (uncertainOrder)
|
||||
return { status: "unresolved", binding, hasDefiniteInitializer };
|
||||
const selected = selectInitializers(binding, position, read, parents);
|
||||
if (selected === undefined) return { status: "unresolved", binding };
|
||||
if (selected === binding) return { status: "ordered", binding };
|
||||
if (selected === undefined)
|
||||
return { status: "unresolved", binding, hasDefiniteInitializer };
|
||||
if (selected.binding === binding)
|
||||
return {
|
||||
status: "ordered",
|
||||
binding,
|
||||
hasDefiniteInitializer:
|
||||
hasDefiniteInitializer || selected.hasDefiniteInitializer,
|
||||
};
|
||||
return {
|
||||
status: "ordered",
|
||||
hasDefiniteInitializer:
|
||||
hasDefiniteInitializer || selected.hasDefiniteInitializer,
|
||||
binding: {
|
||||
...binding,
|
||||
...selected,
|
||||
...selected.binding,
|
||||
// Later assignment definitions do not describe the value captured here.
|
||||
definitions: binding.definitions.filter(
|
||||
({ kind }) => kind !== "assignment",
|
||||
@@ -688,10 +719,13 @@ const selectInitializers = (
|
||||
read: t.Node,
|
||||
parents: WeakMap<t.Node, t.Node>,
|
||||
):
|
||||
| Pick<
|
||||
JavaScriptSemanticBindingState,
|
||||
"initializers" | "referenceInitializers"
|
||||
>
|
||||
| {
|
||||
readonly binding: Pick<
|
||||
JavaScriptSemanticBindingState,
|
||||
"initializers" | "referenceInitializers"
|
||||
>;
|
||||
readonly hasDefiniteInitializer: boolean;
|
||||
}
|
||||
| undefined => {
|
||||
if (position === null) return undefined;
|
||||
const origins = [
|
||||
@@ -725,7 +759,11 @@ const selectInitializers = (
|
||||
if (order === "before") preceding.push(origin);
|
||||
}
|
||||
}
|
||||
const { retained } = retainedInitializerOrigins(preceding, parents);
|
||||
const { retained, replacesEntry } = retainedInitializerOrigins(
|
||||
preceding,
|
||||
binding,
|
||||
parents,
|
||||
);
|
||||
// Preserve the owner's identity when selection removes no facts. Consumers
|
||||
// can then reuse the binding's dependency-aware provenance memo rather than
|
||||
// treating every equivalent read as a new temporal snapshot.
|
||||
@@ -735,32 +773,53 @@ const selectInitializers = (
|
||||
retained.has(initializer),
|
||||
)
|
||||
)
|
||||
return binding;
|
||||
return { binding, hasDefiniteInitializer: replacesEntry };
|
||||
return {
|
||||
initializers: binding.initializers.filter((initializer) =>
|
||||
retained.has(initializer),
|
||||
),
|
||||
referenceInitializers: binding.referenceInitializers.filter((initializer) =>
|
||||
retained.has(initializer),
|
||||
),
|
||||
hasDefiniteInitializer: replacesEntry,
|
||||
binding: {
|
||||
initializers: binding.initializers.filter((initializer) =>
|
||||
retained.has(initializer),
|
||||
),
|
||||
referenceInitializers: binding.referenceInitializers.filter(
|
||||
(initializer) => retained.has(initializer),
|
||||
),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/** Logical writes retain the preceding value as a possible alias. */
|
||||
const logicalInitializer = (
|
||||
node: t.Node,
|
||||
/** Logical writes may keep prior aliases. An RHS fact alone does not prove an
|
||||
* empty local: the complete binding must exclude incoming/default/other writes.
|
||||
*/
|
||||
const initializerMayRetainEntry = (
|
||||
initializer: JavaScriptSemanticBindingState["initializers"][number],
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
parents: WeakMap<t.Node, t.Node>,
|
||||
): boolean => {
|
||||
const parent = parents.get(node);
|
||||
const node = initializer.node,
|
||||
parent = parents.get(node);
|
||||
const assignment = t.isAssignmentExpression(node)
|
||||
? node
|
||||
: t.isAssignmentExpression(parent) && parent.right === node
|
||||
? parent
|
||||
: undefined;
|
||||
return (
|
||||
t.isAssignmentExpression(assignment) &&
|
||||
["&&=", "||=", "??="].includes(assignment.operator)
|
||||
);
|
||||
if (
|
||||
assignment === undefined ||
|
||||
!["&&=", "||=", "??="].includes(assignment.operator)
|
||||
)
|
||||
return false;
|
||||
const emptyLocal =
|
||||
node === assignment.right &&
|
||||
binding.kind === "variable" &&
|
||||
!binding.runtimeImport &&
|
||||
binding.initializers.length === 1 &&
|
||||
binding.initializers[0] === initializer &&
|
||||
binding.referenceInitializers.length === 0 &&
|
||||
binding.directOrigins.length === 0 &&
|
||||
binding.definitions.some(({ kind }) => kind === "variable") &&
|
||||
binding.definitions.every(
|
||||
({ kind }) => kind === "variable" || kind === "assignment",
|
||||
);
|
||||
return !emptyLocal;
|
||||
};
|
||||
|
||||
/** Certain writes replace prior origins; short-circuit writes retain both. */
|
||||
@@ -769,12 +828,13 @@ const retainedInitializerOrigins = (
|
||||
readonly initializer: JavaScriptSemanticBindingState["initializers"][number];
|
||||
readonly position: StatementPosition | null;
|
||||
}[],
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
parents: WeakMap<t.Node, t.Node>,
|
||||
) => {
|
||||
const latest = origins.reduce<StatementPosition | null>(
|
||||
(latest, { initializer, position }) =>
|
||||
position !== null &&
|
||||
!logicalInitializer(initializer.node, parents) &&
|
||||
!initializerMayRetainEntry(initializer, binding, parents) &&
|
||||
(latest === null || positionPrecedes(latest, position))
|
||||
? position
|
||||
: latest,
|
||||
|
||||
Reference in New Issue
Block a user