Skip to content

Commit 9f2b4d7

Browse files
committed
babel fixes
1 parent 8ae014a commit 9f2b4d7

3 files changed

Lines changed: 123 additions & 4 deletions

File tree

compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ export type AliasingEffect =
6666
/**
6767
* Mutates any of the value, its direct aliases, and its transitive captures that are mutable.
6868
*/
69-
| {kind: 'MutateTransitiveConditionally'; value: Place}
69+
| {
70+
kind: 'MutateTransitiveConditionally';
71+
value: Place;
72+
noBackwardRangeExtension?: boolean;
73+
}
7074
/**
7175
* Records information flow from `from` to `into` in cases where local mutation of the destination
7276
* will *not* mutate the source:

compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,12 +1126,29 @@ function applyEffect(
11261126
}
11271127
const operand = arg.kind === 'Identifier' ? arg : arg.place;
11281128
if (operand !== effect.function || effect.mutatesFunction) {
1129+
/*
1130+
* For the receiver of a MethodCall (where receiver !== function),
1131+
* use noBackwardRangeExtension so that the receiver's mutable
1132+
* range is not extended backward through aliases. Without this,
1133+
* values like `expensiveProcessing(data)` get pulled into the
1134+
* same reactive scope as the method call result, preventing
1135+
* independent memoization. The receiver itself and any values
1136+
* it was captured into are still marked as mutated (forward
1137+
* propagation is preserved), maintaining correctness for
1138+
* patterns like `s.add(arr); arr.push(x)`. Known mutating
1139+
* methods (e.g. Array.push) have explicit aliasing signatures
1140+
* that bypass this default path entirely.
1141+
*/
1142+
const isMethodReceiver =
1143+
operand === effect.receiver &&
1144+
effect.receiver !== effect.function;
11291145
applyEffect(
11301146
context,
11311147
state,
11321148
{
11331149
kind: 'MutateTransitiveConditionally',
11341150
value: operand,
1151+
noBackwardRangeExtension: isMethodReceiver,
11351152
},
11361153
initialized,
11371154
effects,

compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts

Lines changed: 101 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ export function inferMutationAliasingRanges(
103103
kind: MutationKind;
104104
place: Place;
105105
reason: MutationReason | null;
106+
noBackwardRangeExtension: boolean;
106107
}> = [];
107108
const renders: Array<{index: number; place: Place}> = [];
108109

@@ -180,6 +181,9 @@ export function inferMutationAliasingRanges(
180181
: MutationKind.Conditional,
181182
reason: null,
182183
place: effect.value,
184+
noBackwardRangeExtension:
185+
effect.kind === 'MutateTransitiveConditionally' &&
186+
(effect.noBackwardRangeExtension ?? false),
183187
});
184188
} else if (
185189
effect.kind === 'Mutate' ||
@@ -195,6 +199,7 @@ export function inferMutationAliasingRanges(
195199
: MutationKind.Conditional,
196200
reason: effect.kind === 'Mutate' ? (effect.reason ?? null) : null,
197201
place: effect.value,
202+
noBackwardRangeExtension: false,
198203
});
199204
} else if (
200205
effect.kind === 'MutateFrozen' ||
@@ -249,6 +254,7 @@ export function inferMutationAliasingRanges(
249254
mutation.place.loc,
250255
mutation.reason,
251256
shouldRecordErrors ? fn.env : null,
257+
mutation.noBackwardRangeExtension,
252258
);
253259
}
254260
for (const render of renders) {
@@ -711,16 +717,85 @@ class AliasingState {
711717
loc: SourceLocation,
712718
reason: MutationReason | null,
713719
env: Environment | null,
720+
noBackwardRangeExtension: boolean = false,
714721
): void {
722+
/*
723+
* Only skip backward-alias range extension when ALL of these hold:
724+
* 1. The effect requested noBackwardRangeExtension (MethodCall receiver)
725+
* 2. The start node is not a PropertyLoad result (no createdFrom edges),
726+
* since PropertyLoad receivers need backward range extension for
727+
* correctness (e.g. `x.y.push(val)` must extend x and y).
728+
* 3. At least one of the start's backward alias targets was produced by
729+
* a function call (indicated by having maybeAliases from the default
730+
* Apply handler). This distinguishes `expensiveProcessing(data).map(fn)`
731+
* from `localArray.push(val)` — only the former should skip, since the
732+
* call result represents a separately-memoizable value.
733+
*/
734+
const startNode = this.nodes.get(start);
735+
let effectiveSkipAliases = false;
736+
if (
737+
noBackwardRangeExtension &&
738+
startNode != null &&
739+
startNode.createdFrom.size === 0
740+
) {
741+
/*
742+
* Walk backward through aliases from the start node to check if
743+
* any value in the chain was produced by a function call (Apply).
744+
* Apply results are identifiable by having maybeAliases, since the
745+
* default Apply handler records MaybeAlias from each argument to
746+
* the result. Only walk through alias edges (not maybeAliases or
747+
* captures) to avoid matching catch parameters and similar patterns
748+
* where range extension is required for correctness.
749+
*/
750+
const visited = new Set<Identifier>();
751+
const aliasQueue: Array<Identifier> = [start];
752+
while (aliasQueue.length > 0) {
753+
const id = aliasQueue.pop()!;
754+
if (visited.has(id)) {
755+
continue;
756+
}
757+
visited.add(id);
758+
const node = this.nodes.get(id);
759+
if (node == null) {
760+
continue;
761+
}
762+
if (node.maybeAliases.size > 0) {
763+
effectiveSkipAliases = true;
764+
break;
765+
}
766+
for (const [nextAliasId] of node.aliases) {
767+
aliasQueue.push(nextAliasId);
768+
}
769+
}
770+
}
715771
const seen = new Map<Identifier, MutationKind>();
716772
const queue: Array<{
717773
place: Identifier;
718774
transitive: boolean;
719775
direction: 'backwards' | 'forwards';
720776
kind: MutationKind;
721-
}> = [{place: start, transitive, direction: 'backwards', kind: startKind}];
777+
/*
778+
* When true, skip extending the mutable range for this node. This
779+
* is set for nodes reached via backward alias/maybeAlias edges when
780+
* effectiveSkipAliases is enabled, so that the receiver's backward
781+
* aliases are not pulled into the same reactive scope as the method
782+
* call. Nodes reached via capture edges reset this flag, since
783+
* captures represent structural containment (e.g. function closures)
784+
* where range extension is required for correctness.
785+
*/
786+
skipRangeExtension: boolean;
787+
}> = [
788+
{
789+
place: start,
790+
transitive,
791+
direction: 'backwards',
792+
kind: startKind,
793+
skipRangeExtension: false,
794+
},
795+
];
722796
while (queue.length !== 0) {
723-
const {place: current, transitive, direction, kind} = queue.pop()!;
797+
const {place: current, transitive, direction, kind, skipRangeExtension} =
798+
queue.pop()!;
724799
const previousKind = seen.get(current);
725800
if (previousKind != null && previousKind >= kind) {
726801
continue;
@@ -732,7 +807,7 @@ class AliasingState {
732807
}
733808
node.mutationReason ??= reason;
734809
node.lastMutated = Math.max(node.lastMutated, index);
735-
if (end != null) {
810+
if (end != null && !skipRangeExtension) {
736811
node.id.mutableRange.end = makeInstructionId(
737812
Math.max(node.id.mutableRange.end, end),
738813
);
@@ -768,6 +843,7 @@ class AliasingState {
768843
direction: 'forwards',
769844
// Traversing a maybeAlias edge always downgrades to conditional mutation
770845
kind: edge.kind === 'maybeAlias' ? MutationKind.Conditional : kind,
846+
skipRangeExtension: false,
771847
});
772848
}
773849
for (const [alias, when] of node.createdFrom) {
@@ -779,6 +855,8 @@ class AliasingState {
779855
transitive: true,
780856
direction: 'backwards',
781857
kind,
858+
// Propagate parent's skipRangeExtension but don't initiate it
859+
skipRangeExtension,
782860
});
783861
}
784862
if (direction === 'backwards' || node.value.kind !== 'Phi') {
@@ -801,6 +879,16 @@ class AliasingState {
801879
transitive,
802880
direction: 'backwards',
803881
kind,
882+
/*
883+
* Skip range extension when traversing backward through alias
884+
* edges from a conditional method-call mutation. This prevents
885+
* the receiver's creation-site values from having their ranges
886+
* extended to cover the method call instruction, which would
887+
* otherwise cause them to be merged into the same scope.
888+
*/
889+
skipRangeExtension:
890+
skipRangeExtension ||
891+
(effectiveSkipAliases && current === start),
804892
});
805893
}
806894
/**
@@ -819,6 +907,9 @@ class AliasingState {
819907
transitive,
820908
direction: 'backwards',
821909
kind: MutationKind.Conditional,
910+
skipRangeExtension:
911+
skipRangeExtension ||
912+
(effectiveSkipAliases && current === start),
822913
});
823914
}
824915
}
@@ -835,6 +926,13 @@ class AliasingState {
835926
transitive,
836927
direction: 'backwards',
837928
kind,
929+
/*
930+
* Reset skipRangeExtension for capture edges. Captures
931+
* represent structural containment (e.g. a function closing
932+
* over a value), and the captured value's range must be
933+
* extended to cover the mutation point for correctness.
934+
*/
935+
skipRangeExtension: false,
838936
});
839937
}
840938
}

0 commit comments

Comments
 (0)