[compiler] Repro for object spread and Array.from with mutable iterators (#32520)
See newly added test fixtures. Repros fixed in later prs of this stack --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/32520). * #32522 * #32521 * __->__ #32520
mofeiZ committed
Mar 12, 2025 at 15:08 UTC
3456b6634abb4a8a2ef500397b94606ce4b6dda7
7 files changed
+390
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-array-spread-mutable-iterator.expect.md
new
+87
@@ -0,0 +1,87 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+/**
6
+ * TODO: object spreads should have conditionally mutate semantics
7
+ * Found differences in evaluator results
8
+ * Non-forget (expected):
9
+ * (kind: ok) [3,1,5,4]
10
+ * [3,1,5,4]
11
+ * [4,1,5,4]
12
+ * Forget:
13
+ * (kind: ok) [3,1,5,4]
14
+ * [3,1,5,4]
15
+ * [4]
16
+ */
17
+
18
+function useBar({arg}) {
19
+ 'use memo';
20
+
21
+ /**
22
+ * Note that mutableIterator is mutated by the later object spread. Therefore,
23
+ * `s.values()` should be memoized within the same block as the object spread.
24
+ * In terms of compiler internals, they should have the same reactive scope.
25
+ */
26
+ const s = new Set([1, 5, 4]);
27
+ const mutableIterator = s.values();
28
+
29
+ return [arg, ...mutableIterator];
30
+}
31
+
32
+export const FIXTURE_ENTRYPOINT = {
33
+ fn: useBar,
34
+ params: [{arg: 3}],
35
+ sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}],
36
+};
37
+
38
+```
39
+
40
+## Code
41
+
42
+```javascript
43
+import { c as _c } from "react/compiler-runtime"; /**
44
+ * TODO: object spreads should have conditionally mutate semantics
45
+ * Found differences in evaluator results
46
+ * Non-forget (expected):
47
+ * (kind: ok) [3,1,5,4]
48
+ * [3,1,5,4]
49
+ * [4,1,5,4]
50
+ * Forget:
51
+ * (kind: ok) [3,1,5,4]
52
+ * [3,1,5,4]
53
+ * [4]
54
+ */
55
+
56
+function useBar(t0) {
57
+ "use memo";
58
+ const $ = _c(3);
59
+ const { arg } = t0;
60
+ let t1;
61
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
62
+ const s = new Set([1, 5, 4]);
63
+ t1 = s.values();
64
+ $[0] = t1;
65
+ } else {
66
+ t1 = $[0];
67
+ }
68
+ const mutableIterator = t1;
69
+ let t2;
70
+ if ($[1] !== arg) {
71
+ t2 = [arg, ...mutableIterator];
72
+ $[1] = arg;
73
+ $[2] = t2;
74
+ } else {
75
+ t2 = $[2];
76
+ }
77
+ return t2;
78
+}
79
+
80
+export const FIXTURE_ENTRYPOINT = {
81
+ fn: useBar,
82
+ params: [{ arg: 3 }],
83
+ sequentialRenders: [{ arg: 3 }, { arg: 3 }, { arg: 4 }],
84
+};
85
+
86
+```
87
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-array-spread-mutable-iterator.js
new
+32
@@ -0,0 +1,32 @@
1
+/**
2
+ * TODO: object spreads should have conditionally mutate semantics
3
+ * Found differences in evaluator results
4
+ * Non-forget (expected):
5
+ * (kind: ok) [3,1,5,4]
6
+ * [3,1,5,4]
7
+ * [4,1,5,4]
8
+ * Forget:
9
+ * (kind: ok) [3,1,5,4]
10
+ * [3,1,5,4]
11
+ * [4]
12
+ */
13
+
14
+function useBar({arg}) {
15
+ 'use memo';
16
+
17
+ /**
18
+ * Note that mutableIterator is mutated by the later object spread. Therefore,
19
+ * `s.values()` should be memoized within the same block as the object spread.
20
+ * In terms of compiler internals, they should have the same reactive scope.
21
+ */
22
+ const s = new Set([1, 5, 4]);
23
+ const mutableIterator = s.values();
24
+
25
+ return [arg, ...mutableIterator];
26
+}
27
+
28
+export const FIXTURE_ENTRYPOINT = {
29
+ fn: useBar,
30
+ params: [{arg: 3}],
31
+ sequentialRenders: [{arg: 3}, {arg: 3}, {arg: 4}],
32
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.expect.md
new
+96
@@ -0,0 +1,96 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useIdentity, ValidateMemoization} from 'shared-runtime';
6
+
7
+/**
8
+ * TODO 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.
12
+ */
13
+function Validate({x, input}) {
14
+ 'use no memo';
15
+ return (
16
+ <>
17
+ <ValidateMemoization inputs={[]} output={x[0]} onlyCheckCompiled={true} />
18
+ <ValidateMemoization
19
+ inputs={[input]}
20
+ output={x[1]}
21
+ onlyCheckCompiled={true}
22
+ />
23
+ </>
24
+ );
25
+}
26
+function useFoo(input) {
27
+ 'use memo';
28
+ /**
29
+ * TODO: We should be able to memoize {} separately from `x`.
30
+ */
31
+ const x = Array.from([{}]);
32
+ useIdentity();
33
+ x.push([input]);
34
+ return <Validate x={x} input={input} />;
35
+}
36
+
37
+export const FIXTURE_ENTRYPOINT = {
38
+ fn: useFoo,
39
+ params: [1],
40
+};
41
+
42
+```
43
+
44
+## Code
45
+
46
+```javascript
47
+import { c as _c } from "react/compiler-runtime";
48
+import { useIdentity, ValidateMemoization } from "shared-runtime";
49
+
50
+/**
51
+ * TODO 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.
55
+ */
56
+function Validate({ x, input }) {
57
+ "use no memo";
58
+ return (
59
+ <>
60
+ <ValidateMemoization inputs={[]} output={x[0]} onlyCheckCompiled={true} />
61
+ <ValidateMemoization
62
+ inputs={[input]}
63
+ output={x[1]}
64
+ onlyCheckCompiled={true}
65
+ />
66
+ </>
67
+ );
68
+}
69
+function useFoo(input) {
70
+ "use memo";
71
+ const $ = _c(3);
72
+
73
+ const x = Array.from([{}]);
74
+ useIdentity();
75
+ x.push([input]);
76
+ let t0;
77
+ if ($[0] !== input || $[1] !== x) {
78
+ t0 = <Validate x={x} input={input} />;
79
+ $[0] = input;
80
+ $[1] = x;
81
+ $[2] = t0;
82
+ } else {
83
+ t0 = $[2];
84
+ }
85
+ return t0;
86
+}
87
+
88
+export const FIXTURE_ENTRYPOINT = {
89
+ fn: useFoo,
90
+ params: [1],
91
+};
92
+
93
+```
94
+
95
+### Eval output
96
+(kind: ok) <div>{"inputs":[],"output":{}}</div><div>{"inputs":[1],"output":[1]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-granular-iterator-semantics.js
new
+36
@@ -0,0 +1,36 @@
1
+import {useIdentity, ValidateMemoization} from 'shared-runtime';
2
+
3
+/**
4
+ * TODO 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.
8
+ */
9
+function Validate({x, input}) {
10
+ 'use no memo';
11
+ return (
12
+ <>
13
+ <ValidateMemoization inputs={[]} output={x[0]} onlyCheckCompiled={true} />
14
+ <ValidateMemoization
15
+ inputs={[input]}
16
+ output={x[1]}
17
+ onlyCheckCompiled={true}
18
+ />
19
+ </>
20
+ );
21
+}
22
+function useFoo(input) {
23
+ 'use memo';
24
+ /**
25
+ * TODO: We should be able to memoize {} separately from `x`.
26
+ */
27
+ const x = Array.from([{}]);
28
+ useIdentity();
29
+ x.push([input]);
30
+ return <Validate x={x} input={input} />;
31
+}
32
+
33
+export const FIXTURE_ENTRYPOINT = {
34
+ fn: useFoo,
35
+ params: [1],
36
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.expect.md
new
+102
@@ -0,0 +1,102 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useIdentity, ValidateMemoization} from 'shared-runtime';
6
+
7
+/**
8
+ * Fixture to assert that we can infer the type and effects of an array created
9
+ * with `Array.from`.
10
+ */
11
+function Validate({x, val1, val2}) {
12
+ 'use no memo';
13
+ return (
14
+ <>
15
+ <ValidateMemoization
16
+ inputs={[val1]}
17
+ output={x[0]}
18
+ onlyCheckCompiled={true}
19
+ />
20
+ <ValidateMemoization
21
+ inputs={[val2]}
22
+ output={x[1]}
23
+ onlyCheckCompiled={true}
24
+ />
25
+ </>
26
+ );
27
+}
28
+function useFoo({val1, val2}) {
29
+ 'use memo';
30
+ const x = Array.from([]);
31
+ useIdentity();
32
+ x.push([val1]);
33
+ x.push([val2]);
34
+ return <Validate x={x} val1={val1} val2={val2} />;
35
+}
36
+
37
+export const FIXTURE_ENTRYPOINT = {
38
+ fn: useFoo,
39
+ params: [{val1: 1, val2: 2}],
40
+};
41
+
42
+```
43
+
44
+## Code
45
+
46
+```javascript
47
+import { c as _c } from "react/compiler-runtime";
48
+import { useIdentity, ValidateMemoization } from "shared-runtime";
49
+
50
+/**
51
+ * Fixture to assert that we can infer the type and effects of an array created
52
+ * with `Array.from`.
53
+ */
54
+function Validate({ x, val1, val2 }) {
55
+ "use no memo";
56
+ return (
57
+ <>
58
+ <ValidateMemoization
59
+ inputs={[val1]}
60
+ output={x[0]}
61
+ onlyCheckCompiled={true}
62
+ />
63
+
64
+ <ValidateMemoization
65
+ inputs={[val2]}
66
+ output={x[1]}
67
+ onlyCheckCompiled={true}
68
+ />
69
+ </>
70
+ );
71
+}
72
+function useFoo(t0) {
73
+ "use memo";
74
+ const $ = _c(4);
75
+ const { val1, val2 } = t0;
76
+
77
+ const x = Array.from([]);
78
+ useIdentity();
79
+ x.push([val1]);
80
+ x.push([val2]);
81
+ let t1;
82
+ if ($[0] !== val1 || $[1] !== val2 || $[2] !== x) {
83
+ t1 = <Validate x={x} val1={val1} val2={val2} />;
84
+ $[0] = val1;
85
+ $[1] = val2;
86
+ $[2] = x;
87
+ $[3] = t1;
88
+ } else {
89
+ t1 = $[3];
90
+ }
91
+ return t1;
92
+}
93
+
94
+export const FIXTURE_ENTRYPOINT = {
95
+ fn: useFoo,
96
+ params: [{ val1: 1, val2: 2 }],
97
+};
98
+
99
+```
100
+
101
+### Eval output
102
+(kind: ok) <div>{"inputs":[1],"output":[1]}</div><div>{"inputs":[2],"output":[2]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo-type-inference-array-from.js
new
+36
@@ -0,0 +1,36 @@
1
+import {useIdentity, ValidateMemoization} from 'shared-runtime';
2
+
3
+/**
4
+ * Fixture to assert that we can infer the type and effects of an array created
5
+ * with `Array.from`.
6
+ */
7
+function Validate({x, val1, val2}) {
8
+ 'use no memo';
9
+ return (
10
+ <>
11
+ <ValidateMemoization
12
+ inputs={[val1]}
13
+ output={x[0]}
14
+ onlyCheckCompiled={true}
15
+ />
16
+ <ValidateMemoization
17
+ inputs={[val2]}
18
+ output={x[1]}
19
+ onlyCheckCompiled={true}
20
+ />
21
+ </>
22
+ );
23
+}
24
+function useFoo({val1, val2}) {
25
+ 'use memo';
26
+ const x = Array.from([]);
27
+ useIdentity();
28
+ x.push([val1]);
29
+ x.push([val2]);
30
+ return <Validate x={x} val1={val1} val2={val2} />;
31
+}
32
+
33
+export const FIXTURE_ENTRYPOINT = {
34
+ fn: useFoo,
35
+ params: [{val1: 1, val2: 2}],
36
+};
compiler/packages/snap/src/SproutTodoFilter.ts
+1
@@ -462,6 +462,7 @@ const skipFilter = new Set([
462
463
// bugs
464
'bug-object-expression-computed-key-modified-during-after-construction-hoisted-sequence-expr',
465
+ 'bug-array-spread-mutable-iterator',
466
`bug-capturing-func-maybealias-captured-mutate`,
467
'bug-aliased-capture-aliased-mutate',
468
'bug-aliased-capture-mutate',