Skip to content

Commit f561d29

Browse files
committed
[compiler] Delete global mutation effects for objects passed as props
With the tracked list of effects from the previous PR, we delete the global mutation effects of object expressions that are passed as props. To make this deletion easier, this PR changes the FunctionEffects to be a Set. While using the identity of the effect for comparison may seem less than ideal, adding extra infrastructure for this purpose doesn't seem worthwhile for now. ghstack-source-id: 0ef1609 Pull Request resolved: #30458
1 parent 76e18d8 commit f561d29

File tree

6 files changed

+91
-74
lines changed

6 files changed

+91
-74
lines changed

compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ export type HIRFunction = {
286286
params: Array<Place | SpreadPattern>;
287287
returnType: t.FlowType | t.TSType | null;
288288
context: Array<Place>;
289-
effects: Array<FunctionEffect> | null;
289+
effects: Set<FunctionEffect> | null;
290290
body: HIR;
291291
generator: boolean;
292292
async: boolean;

compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,8 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
544544
.map(dep => printPlace(dep))
545545
.join(',');
546546
const effects =
547-
instrValue.loweredFunc.func.effects
548-
?.map(effect => {
547+
[...(instrValue.loweredFunc.func.effects ?? [])]
548+
.map(effect => {
549549
if (effect.kind === 'ContextMutation') {
550550
return `ContextMutation places=[${[...effect.places]
551551
.map(place => printPlace(place))

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

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ export default function inferReferenceEffects(
217217
}
218218
queue(fn.body.entry, initialState);
219219

220-
const functionEffects: Array<FunctionEffect> = fn.effects ?? [];
220+
const functionEffects: Set<FunctionEffect> = fn.effects ?? new Set();
221221

222222
while (queuedStates.size !== 0) {
223223
for (const [blockId, block] of fn.body.blocks) {
@@ -382,7 +382,7 @@ class InferenceState {
382382
place: Place,
383383
effectKind: Effect,
384384
reason: ValueReason,
385-
functionEffects: Array<FunctionEffect>,
385+
functionEffects: Set<FunctionEffect>,
386386
): void {
387387
const values = this.#variables.get(place.identifier.id);
388388
if (values === undefined) {
@@ -422,13 +422,13 @@ class InferenceState {
422422

423423
const functionEffect = this.reference(place, effectKind, reason);
424424
if (functionEffect !== null) {
425-
functionEffects.push(functionEffect);
425+
functionEffects.add(functionEffect);
426426
}
427427
}
428428

429429
propogateEffect(
430430
value: InstructionValue,
431-
functionEffects: Array<FunctionEffect>,
431+
functionEffects: Set<FunctionEffect>,
432432
reason: ValueReason,
433433
): void {
434434
if (value.kind !== 'FunctionExpression' && value.kind !== 'ObjectMethod') {
@@ -443,7 +443,7 @@ class InferenceState {
443443
for (const effect of effects) {
444444
if (effect.kind === 'GlobalMutation' || effect.kind === 'ReactMutation') {
445445
// Known effects are always propagated upwards
446-
functionEffects.push(effect);
446+
functionEffects.add(effect);
447447
} else {
448448
/**
449449
* Contextual effects need to be replayed against the current inference
@@ -467,10 +467,10 @@ class InferenceState {
467467
if (replayedEffect != null) {
468468
if (replayedEffect.kind === 'ContextMutation') {
469469
// Case 1, still a context value so propagate the original effect
470-
functionEffects.push(effect);
470+
functionEffects.add(effect);
471471
} else {
472472
// Case 3, immutable value so propagate the more precise effect
473-
functionEffects.push(replayedEffect);
473+
functionEffects.add(replayedEffect);
474474
}
475475
} // else case 2, local mutable value so this effect was fine
476476
}
@@ -525,7 +525,7 @@ class InferenceState {
525525
operand,
526526
Effect.Freeze,
527527
ValueReason.Other,
528-
[],
528+
new Set(),
529529
);
530530
}
531531
}
@@ -981,7 +981,7 @@ function mergeAbstractValues(
981981
*/
982982
function inferBlock(
983983
env: Environment,
984-
functionEffects: Array<FunctionEffect>,
984+
functionEffects: Set<FunctionEffect>,
985985
state: InferenceState,
986986
block: BasicBlock,
987987
): void {
@@ -1167,18 +1167,21 @@ function inferBlock(
11671167
functionEffects,
11681168
);
11691169
} else {
1170-
const propEffects: Array<FunctionEffect> = [];
1170+
const propEffects: Set<FunctionEffect> = new Set();
11711171
state.referenceAndRecordEffects(
11721172
attr.place,
11731173
Effect.Freeze,
11741174
ValueReason.JsxCaptured,
11751175
propEffects,
11761176
);
1177-
functionEffects.push(
1178-
...propEffects.filter(
1179-
propEffect => propEffect.kind !== 'GlobalMutation',
1180-
),
1181-
);
1177+
1178+
for (const effect of propEffects) {
1179+
if (effect.kind === 'GlobalMutation') {
1180+
functionEffects.delete(effect);
1181+
} else {
1182+
functionEffects.add(effect);
1183+
}
1184+
}
11821185
}
11831186
}
11841187

@@ -1278,7 +1281,7 @@ function inferBlock(
12781281
operand,
12791282
operand.effect === Effect.Unknown ? Effect.Read : operand.effect,
12801283
ValueReason.Other,
1281-
[],
1284+
new Set(),
12821285
);
12831286
hasMutableOperand ||= isMutableEffect(operand.effect, operand.loc);
12841287

@@ -1311,9 +1314,9 @@ function inferBlock(
13111314
value.kind === 'FunctionExpression') &&
13121315
value.loweredFunc.func.effects !== null
13131316
) {
1314-
instrValue.loweredFunc.func.effects ??= [];
1315-
instrValue.loweredFunc.func.effects.push(
1316-
...value.loweredFunc.func.effects,
1317+
instrValue.loweredFunc.func.effects ??= new Set();
1318+
value.loweredFunc.func.effects.forEach(e =>
1319+
instrValue.loweredFunc.func.effects!.add(e),
13171320
);
13181321
}
13191322
}
@@ -1357,7 +1360,7 @@ function inferBlock(
13571360
let hasCaptureArgument = false;
13581361
let isUseEffect = isEffectHook(instrValue.callee.identifier);
13591362
for (let i = 0; i < instrValue.args.length; i++) {
1360-
const argumentEffects: Array<FunctionEffect> = [];
1363+
const argumentEffects: Set<FunctionEffect> = new Set();
13611364
const arg = instrValue.args[i];
13621365
const place = arg.kind === 'Identifier' ? arg : arg.place;
13631366
if (effects !== null) {
@@ -1379,12 +1382,11 @@ function inferBlock(
13791382
* Join the effects of the argument with the effects of the enclosing function,
13801383
* unless the we're detecting a global mutation inside a useEffect hook
13811384
*/
1382-
functionEffects.push(
1383-
...argumentEffects.filter(
1384-
argEffect =>
1385-
!isUseEffect || i !== 0 || argEffect.kind !== 'GlobalMutation',
1386-
),
1387-
);
1385+
for (const effect of argumentEffects) {
1386+
if (!isUseEffect || i !== 0 || effect.kind !== 'GlobalMutation') {
1387+
functionEffects.add(effect);
1388+
}
1389+
}
13881390
hasCaptureArgument ||= place.effect === Effect.Capture;
13891391
}
13901392
if (signature !== null) {
@@ -1482,7 +1484,7 @@ function inferBlock(
14821484
let hasCaptureArgument = false;
14831485
let isUseEffect = isEffectHook(instrValue.property.identifier);
14841486
for (let i = 0; i < instrValue.args.length; i++) {
1485-
const argumentEffects: Array<FunctionEffect> = [];
1487+
const argumentEffects: Set<FunctionEffect> = new Set();
14861488
const arg = instrValue.args[i];
14871489
const place = arg.kind === 'Identifier' ? arg : arg.place;
14881490
if (effects !== null) {
@@ -1508,12 +1510,11 @@ function inferBlock(
15081510
* Join the effects of the argument with the effects of the enclosing function,
15091511
* unless the we're detecting a global mutation inside a useEffect hook
15101512
*/
1511-
functionEffects.push(
1512-
...argumentEffects.filter(
1513-
argEffect =>
1514-
!isUseEffect || i !== 0 || argEffect.kind !== 'GlobalMutation',
1515-
),
1516-
);
1513+
for (const effect of argumentEffects) {
1514+
if (!isUseEffect || i !== 0 || effect.kind !== 'GlobalMutation') {
1515+
functionEffects.add(effect);
1516+
}
1517+
}
15171518
hasCaptureArgument ||= place.effect === Effect.Capture;
15181519
}
15191520
if (signature !== null) {
@@ -1703,14 +1704,14 @@ function inferBlock(
17031704
val,
17041705
Effect.Freeze,
17051706
ValueReason.Other,
1706-
[],
1707+
new Set(),
17071708
);
17081709
} else {
17091710
state.referenceAndRecordEffects(
17101711
val,
17111712
Effect.Read,
17121713
ValueReason.Other,
1713-
[],
1714+
new Set(),
17141715
);
17151716
}
17161717
}
@@ -1735,7 +1736,7 @@ function inferBlock(
17351736
instrValue.place,
17361737
effect,
17371738
ValueReason.Other,
1738-
[],
1739+
new Set(),
17391740
);
17401741
lvalue.effect = Effect.ConditionallyMutate;
17411742
// direct aliasing: `a = b`;
@@ -1822,7 +1823,7 @@ function inferBlock(
18221823
instrValue.value,
18231824
effect,
18241825
ValueReason.Other,
1825-
[],
1826+
new Set(),
18261827
);
18271828

18281829
const lvalue = instr.lvalue;
@@ -1867,7 +1868,7 @@ function inferBlock(
18671868
const lvalue = instr.lvalue;
18681869
lvalue.effect = Effect.Store;
18691870

1870-
functionEffects.push({
1871+
functionEffects.add({
18711872
kind: 'GlobalMutation',
18721873
error: {
18731874
reason:

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.object-capture-global-mutation.expect.md

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
## Input
3+
4+
```javascript
5+
function Foo() {
6+
const x = () => {
7+
window.href = 'foo';
8+
};
9+
const y = {x};
10+
return <Bar y={y} />;
11+
}
12+
13+
export const FIXTURE_ENTRYPOINT = {
14+
fn: Foo,
15+
params: [],
16+
};
17+
18+
```
19+
20+
## Code
21+
22+
```javascript
23+
import { c as _c } from "react/compiler-runtime";
24+
function Foo() {
25+
const $ = _c(1);
26+
const x = _temp;
27+
let t0;
28+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
29+
const y = { x };
30+
t0 = <Bar y={y} />;
31+
$[0] = t0;
32+
} else {
33+
t0 = $[0];
34+
}
35+
return t0;
36+
}
37+
function _temp() {
38+
window.href = "foo";
39+
}
40+
41+
export const FIXTURE_ENTRYPOINT = {
42+
fn: Foo,
43+
params: [],
44+
};
45+
46+
```
47+
48+
### Eval output
49+
(kind: exception) Bar is not defined

0 commit comments

Comments
 (0)