[compiler][optim] Add Effect.ConditionallyMutateIterator (#32698)
Adds Effect.ConditionallyMutateIterator, which has the following effects: - capture for known array, map, and sets - mutate for all other values An alternative to this approach could be to add polymorphic shape definitions
mofeiZ committed
Mar 23, 2025 at 23:25 UTC
7c908bcf4e6b46135164be961972f0d756378517
18 files changed
+398
-126
compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
+3
-5
@@ -65,8 +65,6 @@ const UNTYPED_GLOBALS: Set<string> = new Set([
65
'Int8Array',
66
'Int16Array',
67
'Int32Array',
68
- 'Map',
69
- 'Set',
68
'WeakMap',
69
'Uint8Array',
70
'Uint8ClampedArray',
@@ -140,7 +138,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
138
'from',
139
addFunction(DEFAULT_SHAPES, [], {
140
positionalParams: [
143
- Effect.ConditionallyMutate,
141
+ Effect.ConditionallyMutateIterator,
142
Effect.ConditionallyMutate,
143
Effect.ConditionallyMutate,
144
],
@@ -466,7 +464,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
464
DEFAULT_SHAPES,
465
[],
466
{
469
- positionalParams: [Effect.ConditionallyMutate],
467
+ positionalParams: [Effect.ConditionallyMutateIterator],
468
restParam: null,
469
returnType: {kind: 'Object', shapeId: BuiltInMapId},
470
calleeEffect: Effect.Read,
@@ -482,7 +480,7 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
480
DEFAULT_SHAPES,
481
[],
482
{
485
- positionalParams: [Effect.ConditionallyMutate],
483
+ positionalParams: [Effect.ConditionallyMutateIterator],
484
restParam: null,
485
returnType: {kind: 'Object', shapeId: BuiltInSetId},
486
calleeEffect: Effect.Read,
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+3
-1
@@ -1396,6 +1396,7 @@ export enum Effect {
1396
Read = 'read',
1397
// This reference reads and stores the value
1398
Capture = 'capture',
1399
+ ConditionallyMutateIterator = 'mutate-iterator?',
1400
/*
1401
* This reference *may* write to (mutate) the value. This covers two similar cases:
1402
* - The compiler is being conservative and assuming that a value *may* be mutated
@@ -1414,11 +1415,11 @@ export enum Effect {
1415
// This reference may alias to (mutate) the value
1416
Store = 'store',
1417
}
1417
-
1418
export const EffectSchema = z.enum([
1419
Effect.Read,
1420
Effect.Mutate,
1421
Effect.ConditionallyMutate,
1422
+ Effect.ConditionallyMutateIterator,
1423
Effect.Capture,
1424
Effect.Store,
1425
Effect.Freeze,
@@ -1432,6 +1433,7 @@ export function isMutableEffect(
1433
case Effect.Capture:
1434
case Effect.Store:
1435
case Effect.ConditionallyMutate:
1436
+ case Effect.ConditionallyMutateIterator:
1437
case Effect.Mutate: {
1438
return true;
1439
}
compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutableLifetimes.ts
+14
@@ -11,7 +11,10 @@ import {
11
Identifier,
12
InstructionId,
13
InstructionKind,
14
+ isArrayType,
15
+ isMapType,
16
isRefOrRefValue,
17
+ isSetType,
18
makeInstructionId,
19
Place,
20
} from '../HIR/HIR';
@@ -90,6 +93,17 @@ function inferPlace(
93
infer(place, instrId);
94
}
95
return;
96
+ case Effect.ConditionallyMutateIterator: {
97
+ const identifier = place.identifier;
98
+ if (
99
+ !isArrayType(identifier) &&
100
+ !isSetType(identifier) &&
101
+ !isMapType(identifier)
102
+ ) {
103
+ infer(place, instrId);
104
+ }
105
+ return;
106
+ }
107
case Effect.ConditionallyMutate:
108
case Effect.Mutate: {
109
infer(place, instrId);
compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts
+1
@@ -230,6 +230,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
230
case Effect.Capture:
231
case Effect.Store:
232
case Effect.ConditionallyMutate:
233
+ case Effect.ConditionallyMutateIterator:
234
case Effect.Mutate: {
235
if (isMutable(instruction, operand)) {
236
reactiveIdentifiers.markReactive(operand);
compiler/packages/babel-plugin-react-compiler/src/Inference/InferReferenceEffects.ts
+32
-6
@@ -29,8 +29,10 @@ import {
29
ValueKind,
30
ValueReason,
31
isArrayType,
32
+ isMapType,
33
isMutableEffect,
34
isObjectType,
35
+ isSetType,
36
} from '../HIR/HIR';
37
import {FunctionSignature} from '../HIR/ObjectShape';
38
import {
@@ -469,6 +471,25 @@ class InferenceState {
471
}
472
break;
473
}
474
+ case Effect.ConditionallyMutateIterator: {
475
+ if (
476
+ valueKind.kind === ValueKind.Mutable ||
477
+ valueKind.kind === ValueKind.Context
478
+ ) {
479
+ if (
480
+ isArrayType(place.identifier) ||
481
+ isSetType(place.identifier) ||
482
+ isMapType(place.identifier)
483
+ ) {
484
+ effect = Effect.Capture;
485
+ } else {
486
+ effect = Effect.ConditionallyMutate;
487
+ }
488
+ } else {
489
+ effect = Effect.Read;
490
+ }
491
+ break;
492
+ }
493
case Effect.Mutate: {
494
effect = Effect.Mutate;
495
break;
@@ -880,9 +901,7 @@ function inferBlock(
901
state.referenceAndRecordEffects(
902
freezeActions,
903
element.place,
883
- isArrayType(element.place.identifier)
884
- ? Effect.Capture
885
- : Effect.ConditionallyMutate,
904
+ Effect.ConditionallyMutateIterator,
905
ValueReason.Other,
906
);
907
} else if (element.kind === 'Identifier') {
@@ -1643,7 +1662,13 @@ function inferBlock(
1662
kind === ValueKind.Mutable || kind === ValueKind.Context;
1663
let effect;
1664
let valueKind: AbstractValue;
1646
- if (!isMutable || isArrayType(instrValue.collection.identifier)) {
1665
+ const iterator = instrValue.collection.identifier;
1666
+ if (
1667
+ !isMutable ||
1668
+ isArrayType(iterator) ||
1669
+ isMapType(iterator) ||
1670
+ isSetType(iterator)
1671
+ ) {
1672
// Case 1, assume iterator is a separate mutable object
1673
effect = {
1674
kind: Effect.Read,
@@ -1684,7 +1709,7 @@ function inferBlock(
1709
state.referenceAndRecordEffects(
1710
freezeActions,
1711
instrValue.iterator,
1687
- Effect.ConditionallyMutate,
1712
+ Effect.ConditionallyMutateIterator,
1713
ValueReason.Other,
1714
);
1715
/**
@@ -1846,6 +1871,7 @@ export function isKnownMutableEffect(effect: Effect): boolean {
1871
switch (effect) {
1872
case Effect.Store:
1873
case Effect.ConditionallyMutate:
1874
+ case Effect.ConditionallyMutateIterator:
1875
case Effect.Mutate: {
1876
return true;
1877
}
@@ -1949,7 +1975,7 @@ function getArgumentEffect(
1975
});
1976
}
1977
// effects[i] is Effect.Capture | Effect.Read | Effect.Store
1952
- return Effect.ConditionallyMutate;
1978
+ return Effect.ConditionallyMutateIterator;
1979
}
1980
} else {
1981
return Effect.ConditionallyMutate;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-arg1-captures-arg0.expect.md
+43
-16
@@ -50,28 +50,55 @@ import { useIdentity, Stringify } from "shared-runtime";
50
* (2) the 1st argument might mutate its callee
51
*/
52
function Component(t0) {
53
- const $ = _c(4);
53
+ const $ = _c(10);
54
const { value } = t0;
55
- const arr = [{ value: "foo" }, { value: "bar" }, { value }];
56
- useIdentity();
57
- const derived = Array.from(arr, _temp);
55
let t1;
59
- if ($[0] !== derived) {
60
- t1 = derived.at(-1);
61
- $[0] = derived;
62
- $[1] = t1;
56
+ let t2;
57
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
58
+ t1 = { value: "foo" };
59
+ t2 = { value: "bar" };
60
+ $[0] = t1;
61
+ $[1] = t2;
62
} else {
64
- t1 = $[1];
63
+ t1 = $[0];
64
+ t2 = $[1];
65
}
66
- let t2;
67
- if ($[2] !== t1) {
68
- t2 = <Stringify>{t1}</Stringify>;
69
- $[2] = t1;
70
- $[3] = t2;
66
+ let t3;
67
+ if ($[2] !== value) {
68
+ t3 = [t1, t2, { value }];
69
+ $[2] = value;
70
+ $[3] = t3;
71
+ } else {
72
+ t3 = $[3];
73
+ }
74
+ const arr = t3;
75
+ useIdentity();
76
+ let t4;
77
+ if ($[4] !== arr) {
78
+ t4 = Array.from(arr, _temp);
79
+ $[4] = arr;
80
+ $[5] = t4;
81
+ } else {
82
+ t4 = $[5];
83
+ }
84
+ const derived = t4;
85
+ let t5;
86
+ if ($[6] !== derived) {
87
+ t5 = derived.at(-1);
88
+ $[6] = derived;
89
+ $[7] = t5;
90
+ } else {
91
+ t5 = $[7];
92
+ }
93
+ let t6;
94
+ if ($[8] !== t5) {
95
+ t6 = <Stringify>{t5}</Stringify>;
96
+ $[8] = t5;
97
+ $[9] = t6;
98
} else {
72
- t2 = $[3];
99
+ t6 = $[9];
100
}
74
- return t2;
101
+ return t6;
102
}
103
function _temp(x, idx) {
104
return { ...x, id: idx };
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-captures-arg0.expect.md
+43
-16
@@ -50,28 +50,55 @@ import { useIdentity, Stringify } from "shared-runtime";
50
* (2) the 1st argument might mutate its callee
51
*/
52
function Component(t0) {
53
- const $ = _c(4);
53
+ const $ = _c(10);
54
const { value } = t0;
55
- const arr = [{ value: "foo" }, { value: "bar" }, { value }];
56
- useIdentity();
57
- const derived = Array.from(arr);
55
let t1;
59
- if ($[0] !== derived) {
60
- t1 = derived.at(-1);
61
- $[0] = derived;
62
- $[1] = t1;
56
+ let t2;
57
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
58
+ t1 = { value: "foo" };
59
+ t2 = { value: "bar" };
60
+ $[0] = t1;
61
+ $[1] = t2;
62
} else {
64
- t1 = $[1];
63
+ t1 = $[0];
64
+ t2 = $[1];
65
}
66
- let t2;
67
- if ($[2] !== t1) {
68
- t2 = <Stringify>{t1}</Stringify>;
69
- $[2] = t1;
70
- $[3] = t2;
66
+ let t3;
67
+ if ($[2] !== value) {
68
+ t3 = [t1, t2, { value }];
69
+ $[2] = value;
70
+ $[3] = t3;
71
+ } else {
72
+ t3 = $[3];
73
+ }
74
+ const arr = t3;
75
+ useIdentity();
76
+ let t4;
77
+ if ($[4] !== arr) {
78
+ t4 = Array.from(arr);
79
+ $[4] = arr;
80
+ $[5] = t4;
81
+ } else {
82
+ t4 = $[5];
83
+ }
84
+ const derived = t4;
85
+ let t5;
86
+ if ($[6] !== derived) {
87
+ t5 = derived.at(-1);
88
+ $[6] = derived;
89
+ $[7] = t5;
90
+ } else {
91
+ t5 = $[7];
92
+ }
93
+ let t6;
94
+ if ($[8] !== t5) {
95
+ t6 = <Stringify>{t5}</Stringify>;
96
+ $[8] = t5;
97
+ $[9] = t6;
98
} else {
72
- t2 = $[3];
99
+ t6 = $[9];
100
}
74
- return t2;
101
+ return t6;
102
}
103
104
export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.expect.md
+6
-5
@@ -7,7 +7,7 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
7
function Component({value}) {
8
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
9
useIdentity();
10
- const derived = Array.from(arr, mutateAndReturn);
10
+ const derived = Array.from(arr).map(mutateAndReturn);
11
return (
12
<Stringify>
13
{derived.at(0)}
@@ -19,7 +19,7 @@ function Component({value}) {
19
export const FIXTURE_ENTRYPOINT = {
20
fn: Component,
21
params: [{value: 5}],
22
- sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
22
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
23
};
24
25
```
@@ -35,7 +35,7 @@ function Component(t0) {
35
const { value } = t0;
36
const arr = [{ value: "foo" }, { value: "bar" }, { value }];
37
useIdentity();
38
- const derived = Array.from(arr, mutateAndReturn);
38
+ const derived = Array.from(arr).map(mutateAndReturn);
39
let t1;
40
if ($[0] !== derived) {
41
t1 = derived.at(0);
@@ -72,7 +72,7 @@ function Component(t0) {
72
export const FIXTURE_ENTRYPOINT = {
73
fn: Component,
74
params: [{ value: 5 }],
75
- sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }],
75
+ sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }],
76
};
77
78
```
@@ -80,4 +80,5 @@ export const FIXTURE_ENTRYPOINT = {
80
### Eval output
81
(kind: ok) <div>{"children":[{"value":"foo","wat0":"joe"},{"value":5,"wat0":"joe"}]}</div>
82
<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
83
-<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
\ No newline at end of file
83
+<div>{"children":[{"value":"foo","wat0":"joe"},{"value":6,"wat0":"joe"}]}</div>
84
+<div>{"children":[{"value":"foo","wat0":"joe"},{"value":7,"wat0":"joe"}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-from-maybemutates-arg0.js
+2
-2
@@ -3,7 +3,7 @@ import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
3
function Component({value}) {
4
const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
5
useIdentity();
6
- const derived = Array.from(arr, mutateAndReturn);
6
+ const derived = Array.from(arr).map(mutateAndReturn);
7
return (
8
<Stringify>
9
{derived.at(0)}
@@ -15,5 +15,5 @@ function Component({value}) {
15
export const FIXTURE_ENTRYPOINT = {
16
fn: Component,
17
params: [{value: 5}],
18
- sequentialRenders: [{value: 5}, {value: 6}, {value: 6}],
18
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
19
};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/call-spread-argument-set.expect.md
new
+66
@@ -0,0 +1,66 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useIdentity} from 'shared-runtime';
6
+
7
+/**
8
+ * Forked version of call-spread-argument-mutable-iterator that is known to not mutate
9
+ * the spread argument since it is a Set
10
+ */
11
+function useFoo() {
12
+ const s = new Set([1, 2]);
13
+ useIdentity(null);
14
+ return [Math.max(...s), s];
15
+}
16
+
17
+export const FIXTURE_ENTRYPOINT = {
18
+ fn: useFoo,
19
+ params: [{}],
20
+ sequentialRenders: [{}, {}],
21
+};
22
+
23
+```
24
+
25
+## Code
26
+
27
+```javascript
28
+import { c as _c } from "react/compiler-runtime";
29
+import { useIdentity } from "shared-runtime";
30
+
31
+/**
32
+ * Forked version of call-spread-argument-mutable-iterator that is known to not mutate
33
+ * the spread argument since it is a Set
34
+ */
35
+function useFoo() {
36
+ const $ = _c(2);
37
+ let t0;
38
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
39
+ t0 = new Set([1, 2]);
40
+ $[0] = t0;
41
+ } else {
42
+ t0 = $[0];
43
+ }
44
+ const s = t0;
45
+ useIdentity(null);
46
+ let t1;
47
+ if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
48
+ t1 = [Math.max(...s), s];
49
+ $[1] = t1;
50
+ } else {
51
+ t1 = $[1];
52
+ }
53
+ return t1;
54
+}
55
+
56
+export const FIXTURE_ENTRYPOINT = {
57
+ fn: useFoo,
58
+ params: [{}],
59
+ sequentialRenders: [{}, {}],
60
+};
61
+
62
+```
63
+
64
+### Eval output
65
+(kind: ok) [2,{"kind":"Set","value":[1,2]}]
66
+[2,{"kind":"Set","value":[1,2]}]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/call-spread-argument-set.ts
new
+17
@@ -0,0 +1,17 @@
1
+import {useIdentity} from 'shared-runtime';
2
+
3
+/**
4
+ * Forked version of call-spread-argument-mutable-iterator that is known to not mutate
5
+ * the spread argument since it is a Set
6
+ */
7
+function useFoo() {
8
+ const s = new Set([1, 2]);
9
+ useIdentity(null);
10
+ return [Math.max(...s), s];
11
+}
12
+
13
+export const FIXTURE_ENTRYPOINT = {
14
+ fn: useFoo,
15
+ params: [{}],
16
+ sequentialRenders: [{}, {}],
17
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.expect.md
+38
-28
@@ -4,7 +4,7 @@
4
```javascript
5
const MODULE_LOCAL = new Set([4, 5, 6]);
6
function useFoo({propArr}: {propArr: Array<number>}) {
7
- /* TODO: Array can be memoized separately of the Set */
7
+ /* Array can be memoized separately of the Set */
8
const s1 = new Set([1, 2, 3]);
9
s1.add(propArr[0]);
10
@@ -16,7 +16,7 @@ function useFoo({propArr}: {propArr: Array<number>}) {
16
s3.add(propArr[2]);
17
18
/**
19
- * TODO: s3 should be memoized separately of s4
19
+ * s4 should be memoized separately from s3
20
*/
21
const s4 = new Set(s3);
22
s4.add(propArr[3]);
@@ -37,52 +37,62 @@ export const FIXTURE_ENTRYPOINT = {
37
import { c as _c } from "react/compiler-runtime";
38
const MODULE_LOCAL = new Set([4, 5, 6]);
39
function useFoo(t0) {
40
- const $ = _c(13);
40
+ const $ = _c(15);
41
const { propArr } = t0;
42
+ let t1;
43
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
44
+ t1 = [1, 2, 3];
45
+ $[0] = t1;
46
+ } else {
47
+ t1 = $[0];
48
+ }
49
let s1;
43
- if ($[0] !== propArr[0]) {
44
- s1 = new Set([1, 2, 3]);
50
+ if ($[1] !== propArr[0]) {
51
+ s1 = new Set(t1);
52
s1.add(propArr[0]);
46
- $[0] = propArr[0];
47
- $[1] = s1;
53
+ $[1] = propArr[0];
54
+ $[2] = s1;
55
} else {
49
- s1 = $[1];
56
+ s1 = $[2];
57
}
58
let s2;
59
let s3;
53
- let s4;
54
- if ($[2] !== propArr[1] || $[3] !== propArr[2] || $[4] !== propArr[3]) {
60
+ if ($[3] !== propArr[1] || $[4] !== propArr[2]) {
61
s2 = new Set(MODULE_LOCAL.values());
62
s2.add(propArr[1]);
63
64
s3 = new Set(s2.values());
65
s3.add(propArr[2]);
60
-
61
- s4 = new Set(s3);
62
- s4.add(propArr[3]);
63
- $[2] = propArr[1];
64
- $[3] = propArr[2];
65
- $[4] = propArr[3];
66
+ $[3] = propArr[1];
67
+ $[4] = propArr[2];
68
$[5] = s2;
69
$[6] = s3;
68
- $[7] = s4;
70
} else {
71
s2 = $[5];
72
s3 = $[6];
72
- s4 = $[7];
73
}
74
- let t1;
75
- if ($[8] !== s1 || $[9] !== s2 || $[10] !== s3 || $[11] !== s4) {
76
- t1 = [s1, s2, s3, s4];
77
- $[8] = s1;
78
- $[9] = s2;
79
- $[10] = s3;
80
- $[11] = s4;
81
- $[12] = t1;
74
+ let s4;
75
+ if ($[7] !== propArr[3] || $[8] !== s3) {
76
+ s4 = new Set(s3);
77
+ s4.add(propArr[3]);
78
+ $[7] = propArr[3];
79
+ $[8] = s3;
80
+ $[9] = s4;
81
+ } else {
82
+ s4 = $[9];
83
+ }
84
+ let t2;
85
+ if ($[10] !== s1 || $[11] !== s2 || $[12] !== s3 || $[13] !== s4) {
86
+ t2 = [s1, s2, s3, s4];
87
+ $[10] = s1;
88
+ $[11] = s2;
89
+ $[12] = s3;
90
+ $[13] = s4;
91
+ $[14] = t2;
92
} else {
83
- t1 = $[12];
93
+ t2 = $[14];
94
}
85
- return t1;
95
+ return t2;
96
}
97
98
export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-constructor-arg.ts
+2
-2
@@ -1,6 +1,6 @@
1
const MODULE_LOCAL = new Set([4, 5, 6]);
2
function useFoo({propArr}: {propArr: Array<number>}) {
3
- /* TODO: Array can be memoized separately of the Set */
3
+ /* Array can be memoized separately of the Set */
4
const s1 = new Set([1, 2, 3]);
5
s1.add(propArr[0]);
6
@@ -12,7 +12,7 @@ function useFoo({propArr}: {propArr: Array<number>}) {
12
s3.add(propArr[2]);
13
14
/**
15
- * TODO: s3 should be memoized separately of s4
15
+ * s4 should be memoized separately from s3
16
*/
17
const s4 = new Set(s3);
18
s4.add(propArr[3]);
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-foreach-mutate.expect.md
new
+57
@@ -0,0 +1,57 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
6
+
7
+function Component({value}) {
8
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
9
+ useIdentity();
10
+ const derived = new Set(arr).forEach(mutateAndReturn);
11
+ return <Stringify>{[...derived]}</Stringify>;
12
+}
13
+
14
+export const FIXTURE_ENTRYPOINT = {
15
+ fn: Component,
16
+ params: [{value: 5}],
17
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
18
+};
19
+
20
+```
21
+
22
+## Code
23
+
24
+```javascript
25
+import { c as _c } from "react/compiler-runtime";
26
+import { mutateAndReturn, Stringify, useIdentity } from "shared-runtime";
27
+
28
+function Component(t0) {
29
+ const $ = _c(2);
30
+ const { value } = t0;
31
+ const arr = [{ value: "foo" }, { value: "bar" }, { value }];
32
+ useIdentity();
33
+ const derived = new Set(arr).forEach(mutateAndReturn);
34
+ let t1;
35
+ if ($[0] !== derived) {
36
+ t1 = <Stringify>{[...derived]}</Stringify>;
37
+ $[0] = derived;
38
+ $[1] = t1;
39
+ } else {
40
+ t1 = $[1];
41
+ }
42
+ return t1;
43
+}
44
+
45
+export const FIXTURE_ENTRYPOINT = {
46
+ fn: Component,
47
+ params: [{ value: 5 }],
48
+ sequentialRenders: [{ value: 5 }, { value: 6 }, { value: 6 }, { value: 7 }],
49
+};
50
+
51
+```
52
+
53
+### Eval output
54
+(kind: ok) [[ (exception in render) TypeError: derived is not iterable ]]
55
+[[ (exception in render) TypeError: derived is not iterable ]]
56
+[[ (exception in render) TypeError: derived is not iterable ]]
57
+[[ (exception in render) TypeError: derived is not iterable ]]
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/global-types/set-foreach-mutate.tsx
new
+14
@@ -0,0 +1,14 @@
1
+import {mutateAndReturn, Stringify, useIdentity} from 'shared-runtime';
2
+
3
+function Component({value}) {
4
+ const arr = [{value: 'foo'}, {value: 'bar'}, {value}];
5
+ useIdentity();
6
+ const derived = new Set(arr).forEach(mutateAndReturn);
7
+ return <Stringify>{[...derived]}</Stringify>;
8
+}
9
+
10
+export const FIXTURE_ENTRYPOINT = {
11
+ fn: Component,
12
+ params: [{value: 5}],
13
+ sequentialRenders: [{value: 5}, {value: 6}, {value: 6}, {value: 7}],
14
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md
+26
-20
@@ -5,7 +5,7 @@
5
import {useIdentity, ValidateMemoization} from 'shared-runtime';
6
7
/**
8
- * TODO fixture for granular iterator semantics:
8
+ * Fixture for granular iterator semantics:
9
* 1. ConditionallyMutate the iterator itself, depending on whether the iterator
10
* is a mutable iterator.
11
* 2. Capture effect on elements within the iterator.
@@ -26,7 +26,7 @@ function Validate({x, input}) {
26
function useFoo(input) {
27
'use memo';
28
/**
29
- * TODO: We should be able to memoize {} separately from `x`.
29
+ * We should be able to memoize {} separately from `x`.
30
*/
31
const x = Array.from([{}]);
32
useIdentity();
@@ -48,7 +48,7 @@ import { c as _c } from "react/compiler-runtime";
48
import { useIdentity, ValidateMemoization } from "shared-runtime";
49
50
/**
51
- * TODO fixture for granular iterator semantics:
51
+ * Fixture for granular iterator semantics:
52
* 1. ConditionallyMutate the iterator itself, depending on whether the iterator
53
* is a mutable iterator.
54
* 2. Capture effect on elements within the iterator.
@@ -68,29 +68,35 @@ function Validate({ x, input }) {
68
}
69
function useFoo(input) {
70
"use memo";
71
- const $ = _c(5);
72
-
73
- const x = Array.from([{}]);
74
- useIdentity();
71
+ const $ = _c(6);
72
let t0;
76
- if ($[0] !== input) {
77
- t0 = [input];
78
- $[0] = input;
79
- $[1] = t0;
73
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
74
+ t0 = [{}];
75
+ $[0] = t0;
76
} else {
81
- t0 = $[1];
77
+ t0 = $[0];
78
}
83
- x.push(t0);
79
+ const x = Array.from(t0);
80
+ useIdentity();
81
let t1;
85
- if ($[2] !== input || $[3] !== x) {
86
- t1 = <Validate x={x} input={input} />;
87
- $[2] = input;
88
- $[3] = x;
89
- $[4] = t1;
82
+ if ($[1] !== input) {
83
+ t1 = [input];
84
+ $[1] = input;
85
+ $[2] = t1;
86
+ } else {
87
+ t1 = $[2];
88
+ }
89
+ x.push(t1);
90
+ let t2;
91
+ if ($[3] !== input || $[4] !== x) {
92
+ t2 = <Validate x={x} input={input} />;
93
+ $[3] = input;
94
+ $[4] = x;
95
+ $[5] = t2;
96
} else {
91
- t1 = $[4];
97
+ t2 = $[5];
98
}
93
- return t1;
99
+ return t2;
100
}
101
102
export const FIXTURE_ENTRYPOINT = {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.js
+2
-2
@@ -1,7 +1,7 @@
1
import {useIdentity, ValidateMemoization} from 'shared-runtime';
2
3
/**
4
- * TODO fixture for granular iterator semantics:
4
+ * Fixture for granular iterator semantics:
5
* 1. ConditionallyMutate the iterator itself, depending on whether the iterator
6
* is a mutable iterator.
7
* 2. Capture effect on elements within the iterator.
@@ -22,7 +22,7 @@ function Validate({x, input}) {
22
function useFoo(input) {
23
'use memo';
24
/**
25
- * TODO: We should be able to memoize {} separately from `x`.
25
+ * We should be able to memoize {} separately from `x`.
26
*/
27
const x = Array.from([{}]);
28
useIdentity();
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/type-inference-array-from.expect.md
+29
-23
@@ -77,40 +77,46 @@ function Validate({ x, val1, val2 }) {
77
}
78
function useFoo(t0) {
79
"use memo";
80
- const $ = _c(8);
80
+ const $ = _c(9);
81
const { val1, val2 } = t0;
82
-
83
- const x = Array.from([]);
84
- useIdentity();
82
let t1;
86
- if ($[0] !== val1) {
87
- t1 = [val1];
88
- $[0] = val1;
89
- $[1] = t1;
83
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
84
+ t1 = [];
85
+ $[0] = t1;
86
} else {
91
- t1 = $[1];
87
+ t1 = $[0];
88
}
93
- x.push(t1);
89
+ const x = Array.from(t1);
90
+ useIdentity();
91
let t2;
95
- if ($[2] !== val2) {
96
- t2 = [val2];
97
- $[2] = val2;
98
- $[3] = t2;
92
+ if ($[1] !== val1) {
93
+ t2 = [val1];
94
+ $[1] = val1;
95
+ $[2] = t2;
96
} else {
100
- t2 = $[3];
97
+ t2 = $[2];
98
}
99
x.push(t2);
100
let t3;
104
- if ($[4] !== val1 || $[5] !== val2 || $[6] !== x) {
105
- t3 = <Validate x={x} val1={val1} val2={val2} />;
106
- $[4] = val1;
107
- $[5] = val2;
108
- $[6] = x;
109
- $[7] = t3;
101
+ if ($[3] !== val2) {
102
+ t3 = [val2];
103
+ $[3] = val2;
104
+ $[4] = t3;
105
+ } else {
106
+ t3 = $[4];
107
+ }
108
+ x.push(t3);
109
+ let t4;
110
+ if ($[5] !== val1 || $[6] !== val2 || $[7] !== x) {
111
+ t4 = <Validate x={x} val1={val1} val2={val2} />;
112
+ $[5] = val1;
113
+ $[6] = val2;
114
+ $[7] = x;
115
+ $[8] = t4;
116
} else {
111
- t3 = $[7];
117
+ t4 = $[8];
118
}
113
- return t3;
119
+ return t4;
120
}
121
122
export const FIXTURE_ENTRYPOINT = {