@samitouri / QOS-React / commits / 3456b6634a

[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',