Skip to content

Commit f4cfb20

Browse files
committed
babel fixes
1 parent 860b6fd commit f4cfb20

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
@@ -102,6 +102,7 @@ export function inferMutationAliasingRanges(
102102
kind: MutationKind;
103103
place: Place;
104104
reason: MutationReason | null;
105+
noBackwardRangeExtension: boolean;
105106
}> = [];
106107
const renders: Array<{index: number; place: Place}> = [];
107108

@@ -179,6 +180,9 @@ export function inferMutationAliasingRanges(
179180
: MutationKind.Conditional,
180181
reason: null,
181182
place: effect.value,
183+
noBackwardRangeExtension:
184+
effect.kind === 'MutateTransitiveConditionally' &&
185+
(effect.noBackwardRangeExtension ?? false),
182186
});
183187
} else if (
184188
effect.kind === 'Mutate' ||
@@ -194,6 +198,7 @@ export function inferMutationAliasingRanges(
194198
: MutationKind.Conditional,
195199
reason: effect.kind === 'Mutate' ? (effect.reason ?? null) : null,
196200
place: effect.value,
201+
noBackwardRangeExtension: false,
197202
});
198203
} else if (
199204
effect.kind === 'MutateFrozen' ||
@@ -246,6 +251,7 @@ export function inferMutationAliasingRanges(
246251
mutation.place.loc,
247252
mutation.reason,
248253
errors,
254+
mutation.noBackwardRangeExtension,
249255
);
250256
}
251257
for (const render of renders) {
@@ -707,16 +713,85 @@ class AliasingState {
707713
loc: SourceLocation,
708714
reason: MutationReason | null,
709715
errors: CompilerError,
716+
noBackwardRangeExtension: boolean = false,
710717
): void {
718+
/*
719+
* Only skip backward-alias range extension when ALL of these hold:
720+
* 1. The effect requested noBackwardRangeExtension (MethodCall receiver)
721+
* 2. The start node is not a PropertyLoad result (no createdFrom edges),
722+
* since PropertyLoad receivers need backward range extension for
723+
* correctness (e.g. `x.y.push(val)` must extend x and y).
724+
* 3. At least one of the start's backward alias targets was produced by
725+
* a function call (indicated by having maybeAliases from the default
726+
* Apply handler). This distinguishes `expensiveProcessing(data).map(fn)`
727+
* from `localArray.push(val)` — only the former should skip, since the
728+
* call result represents a separately-memoizable value.
729+
*/
730+
const startNode = this.nodes.get(start);
731+
let effectiveSkipAliases = false;
732+
if (
733+
noBackwardRangeExtension &&
734+
startNode != null &&
735+
startNode.createdFrom.size === 0
736+
) {
737+
/*
738+
* Walk backward through aliases from the start node to check if
739+
* any value in the chain was produced by a function call (Apply).
740+
* Apply results are identifiable by having maybeAliases, since the
741+
* default Apply handler records MaybeAlias from each argument to
742+
* the result. Only walk through alias edges (not maybeAliases or
743+
* captures) to avoid matching catch parameters and similar patterns
744+
* where range extension is required for correctness.
745+
*/
746+
const visited = new Set<Identifier>();
747+
const aliasQueue: Array<Identifier> = [start];
748+
while (aliasQueue.length > 0) {
749+
const id = aliasQueue.pop()!;
750+
if (visited.has(id)) {
751+
continue;
752+
}
753+
visited.add(id);
754+
const node = this.nodes.get(id);
755+
if (node == null) {
756+
continue;
757+
}
758+
if (node.maybeAliases.size > 0) {
759+
effectiveSkipAliases = true;
760+
break;
761+
}
762+
for (const [nextAliasId] of node.aliases) {
763+
aliasQueue.push(nextAliasId);
764+
}
765+
}
766+
}
711767
const seen = new Map<Identifier, MutationKind>();
712768
const queue: Array<{
713769
place: Identifier;
714770
transitive: boolean;
715771
direction: 'backwards' | 'forwards';
716772
kind: MutationKind;
717-
}> = [{place: start, transitive, direction: 'backwards', kind: startKind}];
773+
/*
774+
* When true, skip extending the mutable range for this node. This
775+
* is set for nodes reached via backward alias/maybeAlias edges when
776+
* effectiveSkipAliases is enabled, so that the receiver's backward
777+
* aliases are not pulled into the same reactive scope as the method
778+
* call. Nodes reached via capture edges reset this flag, since
779+
* captures represent structural containment (e.g. function closures)
780+
* where range extension is required for correctness.
781+
*/
782+
skipRangeExtension: boolean;
783+
}> = [
784+
{
785+
place: start,
786+
transitive,
787+
direction: 'backwards',
788+
kind: startKind,
789+
skipRangeExtension: false,
790+
},
791+
];
718792
while (queue.length !== 0) {
719-
const {place: current, transitive, direction, kind} = queue.pop()!;
793+
const {place: current, transitive, direction, kind, skipRangeExtension} =
794+
queue.pop()!;
720795
const previousKind = seen.get(current);
721796
if (previousKind != null && previousKind >= kind) {
722797
continue;
@@ -728,7 +803,7 @@ class AliasingState {
728803
}
729804
node.mutationReason ??= reason;
730805
node.lastMutated = Math.max(node.lastMutated, index);
731-
if (end != null) {
806+
if (end != null && !skipRangeExtension) {
732807
node.id.mutableRange.end = makeInstructionId(
733808
Math.max(node.id.mutableRange.end, end),
734809
);
@@ -764,6 +839,7 @@ class AliasingState {
764839
direction: 'forwards',
765840
// Traversing a maybeAlias edge always downgrades to conditional mutation
766841
kind: edge.kind === 'maybeAlias' ? MutationKind.Conditional : kind,
842+
skipRangeExtension: false,
767843
});
768844
}
769845
for (const [alias, when] of node.createdFrom) {
@@ -775,6 +851,8 @@ class AliasingState {
775851
transitive: true,
776852
direction: 'backwards',
777853
kind,
854+
// Propagate parent's skipRangeExtension but don't initiate it
855+
skipRangeExtension,
778856
});
779857
}
780858
if (direction === 'backwards' || node.value.kind !== 'Phi') {
@@ -797,6 +875,16 @@ class AliasingState {
797875
transitive,
798876
direction: 'backwards',
799877
kind,
878+
/*
879+
* Skip range extension when traversing backward through alias
880+
* edges from a conditional method-call mutation. This prevents
881+
* the receiver's creation-site values from having their ranges
882+
* extended to cover the method call instruction, which would
883+
* otherwise cause them to be merged into the same scope.
884+
*/
885+
skipRangeExtension:
886+
skipRangeExtension ||
887+
(effectiveSkipAliases && current === start),
800888
});
801889
}
802890
/**
@@ -815,6 +903,9 @@ class AliasingState {
815903
transitive,
816904
direction: 'backwards',
817905
kind: MutationKind.Conditional,
906+
skipRangeExtension:
907+
skipRangeExtension ||
908+
(effectiveSkipAliases && current === start),
818909
});
819910
}
820911
}
@@ -831,6 +922,13 @@ class AliasingState {
831922
transitive,
832923
direction: 'backwards',
833924
kind,
925+
/*
926+
* Reset skipRangeExtension for capture edges. Captures
927+
* represent structural containment (e.g. a function closing
928+
* over a value), and the captured value's range must be
929+
* extended to cover the mutation point for correctness.
930+
*/
931+
skipRangeExtension: false,
834932
});
835933
}
836934
}

0 commit comments

Comments
 (0)