@samitouri / QOS-React / commits / 7c908bcf4e

[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 = {