[compiler] Tests for different orders of createfrom/capture w/wo function expressions (#33543)
Adds some typed helpers to represent aliasing, assign, capture, createfrom, and mutate effects along with representative runtime behavior, and then adds tests to demonstrate that we model capture->createfrom and createfrom->capture correctly. There is one case (createfrom->capture in a lambda) where we infer a less precise effect, but in the more conservative direction (we include more code/deps than necesssary rather than fewer). --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33543). * #33571 * #33558 * #33547 * __->__ #33543
Joseph Savona committed
Jun 18, 2025 at 15:56 UTC
cc3806377a43f0bd339d54ceaf2e1e16b0b113bf
20 files changed
+1289
-7
compiler/packages/babel-plugin-react-compiler/src/Inference/MUTABILITY_ALIASING_MODEL.md
+22
-2
@@ -271,9 +271,9 @@ a.property = value // a _is_ b, this mutates b
271
272
```
273
CreateFrom a <- b
274
-Mutate A
274
+Mutate a
275
=>
276
-MutateTransitive b
276
+Mutate b
277
```
278
279
Example:
@@ -301,6 +301,26 @@ a.b = b;
301
a.property = value; // mutates a, not b
302
```
303
304
+### Mutation of Source Affects Alias, Assignment, CreateFrom, and Capture
305
+
306
+```
307
+Alias a <- b OR Assign a <- b OR CreateFrom a <- b OR Capture a <- b
308
+Mutate b
309
+=>
310
+Mutate a
311
+```
312
+
313
+A derived value changes when it's source value is mutated.
314
+
315
+Example:
316
+
317
+```js
318
+const x = {};
319
+const y = [x];
320
+x.y = true; // this changes the value within `y` ie mutates y
321
+```
322
+
323
+
324
### TransitiveMutation of Alias, Assignment, CreateFrom, or Capture Mutates the Source
325
326
```
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.expect.md
new
+166
@@ -0,0 +1,166 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useMemo} from 'react';
6
+import {
7
+ mutate,
8
+ typedCapture,
9
+ typedCreateFrom,
10
+ typedMutate,
11
+ ValidateMemoization,
12
+} from 'shared-runtime';
13
+
14
+function Component({a, b, c}: {a: number; b: number; c: number}) {
15
+ const x = useMemo(() => [{value: a}], [a, b, c]);
16
+ if (b === 0) {
17
+ // This object should only depend on c, it cannot be affected by the later mutation
18
+ x.push({value: c});
19
+ } else {
20
+ // This mutation shouldn't affect the object in the consequent
21
+ mutate(x);
22
+ }
23
+
24
+ return (
25
+ <>
26
+ <ValidateMemoization inputs={[a, b, c]} output={x} alwaysCheck={true} />;
27
+ {/* TODO: should only depend on c */}
28
+ <ValidateMemoization
29
+ inputs={[a, b, c]}
30
+ output={x[0]}
31
+ alwaysCheck={true}
32
+ />
33
+ ;
34
+ </>
35
+ );
36
+}
37
+
38
+export const FIXTURE_ENTRYPOINT = {
39
+ fn: Component,
40
+ params: [{a: 0, b: 0, c: 0}],
41
+ sequentialRenders: [
42
+ {a: 0, b: 0, c: 0},
43
+ {a: 0, b: 1, c: 0},
44
+ {a: 1, b: 1, c: 0},
45
+ {a: 1, b: 1, c: 1},
46
+ {a: 1, b: 1, c: 0},
47
+ {a: 1, b: 0, c: 0},
48
+ {a: 0, b: 0, c: 0},
49
+ ],
50
+};
51
+
52
+```
53
+
54
+## Code
55
+
56
+```javascript
57
+import { c as _c } from "react/compiler-runtime";
58
+import { useMemo } from "react";
59
+import {
60
+ mutate,
61
+ typedCapture,
62
+ typedCreateFrom,
63
+ typedMutate,
64
+ ValidateMemoization,
65
+} from "shared-runtime";
66
+
67
+function Component(t0) {
68
+ const $ = _c(22);
69
+ const { a, b, c } = t0;
70
+ let t1;
71
+ let x;
72
+ if ($[0] !== a || $[1] !== b || $[2] !== c) {
73
+ t1 = [{ value: a }];
74
+ x = t1;
75
+ if (b === 0) {
76
+ x.push({ value: c });
77
+ } else {
78
+ mutate(x);
79
+ }
80
+ $[0] = a;
81
+ $[1] = b;
82
+ $[2] = c;
83
+ $[3] = x;
84
+ $[4] = t1;
85
+ } else {
86
+ x = $[3];
87
+ t1 = $[4];
88
+ }
89
+ let t2;
90
+ if ($[5] !== a || $[6] !== b || $[7] !== c) {
91
+ t2 = [a, b, c];
92
+ $[5] = a;
93
+ $[6] = b;
94
+ $[7] = c;
95
+ $[8] = t2;
96
+ } else {
97
+ t2 = $[8];
98
+ }
99
+ let t3;
100
+ if ($[9] !== t2 || $[10] !== x) {
101
+ t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
102
+ $[9] = t2;
103
+ $[10] = x;
104
+ $[11] = t3;
105
+ } else {
106
+ t3 = $[11];
107
+ }
108
+ let t4;
109
+ if ($[12] !== a || $[13] !== b || $[14] !== c) {
110
+ t4 = [a, b, c];
111
+ $[12] = a;
112
+ $[13] = b;
113
+ $[14] = c;
114
+ $[15] = t4;
115
+ } else {
116
+ t4 = $[15];
117
+ }
118
+ let t5;
119
+ if ($[16] !== t4 || $[17] !== x[0]) {
120
+ t5 = <ValidateMemoization inputs={t4} output={x[0]} alwaysCheck={true} />;
121
+ $[16] = t4;
122
+ $[17] = x[0];
123
+ $[18] = t5;
124
+ } else {
125
+ t5 = $[18];
126
+ }
127
+ let t6;
128
+ if ($[19] !== t3 || $[20] !== t5) {
129
+ t6 = (
130
+ <>
131
+ {t3};{t5};
132
+ </>
133
+ );
134
+ $[19] = t3;
135
+ $[20] = t5;
136
+ $[21] = t6;
137
+ } else {
138
+ t6 = $[21];
139
+ }
140
+ return t6;
141
+}
142
+
143
+export const FIXTURE_ENTRYPOINT = {
144
+ fn: Component,
145
+ params: [{ a: 0, b: 0, c: 0 }],
146
+ sequentialRenders: [
147
+ { a: 0, b: 0, c: 0 },
148
+ { a: 0, b: 1, c: 0 },
149
+ { a: 1, b: 1, c: 0 },
150
+ { a: 1, b: 1, c: 1 },
151
+ { a: 1, b: 1, c: 0 },
152
+ { a: 1, b: 0, c: 0 },
153
+ { a: 0, b: 0, c: 0 },
154
+ ],
155
+};
156
+
157
+```
158
+
159
+### Eval output
160
+(kind: ok) <div>{"inputs":[0,0,0],"output":[{"value":0},{"value":0}]}</div>;<div>{"inputs":[0,0,0],"output":{"value":0}}</div>;
161
+<div>{"inputs":[0,1,0],"output":[{"value":0},"joe"]}</div>;<div>{"inputs":[0,1,0],"output":{"value":0}}</div>;
162
+<div>{"inputs":[1,1,0],"output":[{"value":1},"joe"]}</div>;<div>{"inputs":[1,1,0],"output":{"value":1}}</div>;
163
+<div>{"inputs":[1,1,1],"output":[{"value":1},"joe"]}</div>;<div>{"inputs":[1,1,1],"output":{"value":1}}</div>;
164
+<div>{"inputs":[1,1,0],"output":[{"value":1},"joe"]}</div>;<div>{"inputs":[1,1,0],"output":{"value":1}}</div>;
165
+<div>{"inputs":[1,0,0],"output":[{"value":1},{"value":0}]}</div>;<div>{"inputs":[1,0,0],"output":{"value":1}}</div>;
166
+<div>{"inputs":[0,0,0],"output":[{"value":0},{"value":0}]}</div>;<div>{"inputs":[0,0,0],"output":{"value":0}}</div>;
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-control-flow-sensitive-mutation.tsx
new
+46
@@ -0,0 +1,46 @@
1
+import {useMemo} from 'react';
2
+import {
3
+ mutate,
4
+ typedCapture,
5
+ typedCreateFrom,
6
+ typedMutate,
7
+ ValidateMemoization,
8
+} from 'shared-runtime';
9
+
10
+function Component({a, b, c}: {a: number; b: number; c: number}) {
11
+ const x = useMemo(() => [{value: a}], [a, b, c]);
12
+ if (b === 0) {
13
+ // This object should only depend on c, it cannot be affected by the later mutation
14
+ x.push({value: c});
15
+ } else {
16
+ // This mutation shouldn't affect the object in the consequent
17
+ mutate(x);
18
+ }
19
+
20
+ return (
21
+ <>
22
+ <ValidateMemoization inputs={[a, b, c]} output={x} alwaysCheck={true} />;
23
+ {/* TODO: should only depend on c */}
24
+ <ValidateMemoization
25
+ inputs={[a, b, c]}
26
+ output={x[0]}
27
+ alwaysCheck={true}
28
+ />
29
+ ;
30
+ </>
31
+ );
32
+}
33
+
34
+export const FIXTURE_ENTRYPOINT = {
35
+ fn: Component,
36
+ params: [{a: 0, b: 0, c: 0}],
37
+ sequentialRenders: [
38
+ {a: 0, b: 0, c: 0},
39
+ {a: 0, b: 1, c: 0},
40
+ {a: 1, b: 1, c: 0},
41
+ {a: 1, b: 1, c: 1},
42
+ {a: 1, b: 1, c: 0},
43
+ {a: 1, b: 0, c: 0},
44
+ {a: 0, b: 0, c: 0},
45
+ ],
46
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.expect.md
new
+116
@@ -0,0 +1,116 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useMemo} from 'react';
6
+import {
7
+ typedCapture,
8
+ typedCreateFrom,
9
+ typedMutate,
10
+ ValidateMemoization,
11
+} from 'shared-runtime';
12
+
13
+function Component({a, b}) {
14
+ const x = useMemo(() => [{a}], [a]);
15
+ const f = () => {
16
+ const y = typedCreateFrom(x);
17
+ const z = typedCapture(y);
18
+ return z;
19
+ };
20
+ const z = f();
21
+ // does not mutate x, so x should not depend on b
22
+ typedMutate(z, b);
23
+
24
+ // TODO: this *should* only depend on `a`
25
+ return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
26
+}
27
+
28
+export const FIXTURE_ENTRYPOINT = {
29
+ fn: Component,
30
+ params: [{a: 0, b: 0}],
31
+ sequentialRenders: [
32
+ {a: 0, b: 0},
33
+ {a: 0, b: 1},
34
+ {a: 1, b: 1},
35
+ {a: 0, b: 0},
36
+ ],
37
+};
38
+
39
+```
40
+
41
+## Code
42
+
43
+```javascript
44
+import { c as _c } from "react/compiler-runtime";
45
+import { useMemo } from "react";
46
+import {
47
+ typedCapture,
48
+ typedCreateFrom,
49
+ typedMutate,
50
+ ValidateMemoization,
51
+} from "shared-runtime";
52
+
53
+function Component(t0) {
54
+ const $ = _c(10);
55
+ const { a, b } = t0;
56
+ let t1;
57
+ let x;
58
+ if ($[0] !== a || $[1] !== b) {
59
+ t1 = [{ a }];
60
+ x = t1;
61
+ const f = () => {
62
+ const y = typedCreateFrom(x);
63
+ const z = typedCapture(y);
64
+ return z;
65
+ };
66
+
67
+ const z_0 = f();
68
+
69
+ typedMutate(z_0, b);
70
+ $[0] = a;
71
+ $[1] = b;
72
+ $[2] = x;
73
+ $[3] = t1;
74
+ } else {
75
+ x = $[2];
76
+ t1 = $[3];
77
+ }
78
+ let t2;
79
+ if ($[4] !== a || $[5] !== b) {
80
+ t2 = [a, b];
81
+ $[4] = a;
82
+ $[5] = b;
83
+ $[6] = t2;
84
+ } else {
85
+ t2 = $[6];
86
+ }
87
+ let t3;
88
+ if ($[7] !== t2 || $[8] !== x) {
89
+ t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
90
+ $[7] = t2;
91
+ $[8] = x;
92
+ $[9] = t3;
93
+ } else {
94
+ t3 = $[9];
95
+ }
96
+ return t3;
97
+}
98
+
99
+export const FIXTURE_ENTRYPOINT = {
100
+ fn: Component,
101
+ params: [{ a: 0, b: 0 }],
102
+ sequentialRenders: [
103
+ { a: 0, b: 0 },
104
+ { a: 0, b: 1 },
105
+ { a: 1, b: 1 },
106
+ { a: 0, b: 0 },
107
+ ],
108
+};
109
+
110
+```
111
+
112
+### Eval output
113
+(kind: ok) <div>{"inputs":[0,0],"output":[{"a":0}]}</div>
114
+<div>{"inputs":[0,1],"output":[{"a":0}]}</div>
115
+<div>{"inputs":[1,1],"output":[{"a":1}]}</div>
116
+<div>{"inputs":[0,0],"output":[{"a":0}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/todo-transitivity-createfrom-capture-lambda.tsx
new
+33
@@ -0,0 +1,33 @@
1
+import {useMemo} from 'react';
2
+import {
3
+ typedCapture,
4
+ typedCreateFrom,
5
+ typedMutate,
6
+ ValidateMemoization,
7
+} from 'shared-runtime';
8
+
9
+function Component({a, b}) {
10
+ const x = useMemo(() => [{a}], [a]);
11
+ const f = () => {
12
+ const y = typedCreateFrom(x);
13
+ const z = typedCapture(y);
14
+ return z;
15
+ };
16
+ const z = f();
17
+ // does not mutate x, so x should not depend on b
18
+ typedMutate(z, b);
19
+
20
+ // TODO: this *should* only depend on `a`
21
+ return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
22
+}
23
+
24
+export const FIXTURE_ENTRYPOINT = {
25
+ fn: Component,
26
+ params: [{a: 0, b: 0}],
27
+ sequentialRenders: [
28
+ {a: 0, b: 0},
29
+ {a: 0, b: 1},
30
+ {a: 1, b: 1},
31
+ {a: 0, b: 0},
32
+ ],
33
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.expect.md
new
+153
@@ -0,0 +1,153 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useMemo} from 'react';
6
+import {
7
+ typedCapture,
8
+ typedCreateFrom,
9
+ typedMutate,
10
+ ValidateMemoization,
11
+} from 'shared-runtime';
12
+
13
+function Component({a, b}) {
14
+ const o: any = useMemo(() => ({a}), [a]);
15
+ const x: Array<any> = useMemo(() => [o], [o, b]);
16
+ const y = typedCapture(x);
17
+ const z = typedCapture(y);
18
+ x.push(z);
19
+ x.push(b);
20
+
21
+ return (
22
+ <>
23
+ <ValidateMemoization inputs={[a]} output={o} alwaysCheck={true} />;
24
+ <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
25
+ </>
26
+ );
27
+}
28
+
29
+export const FIXTURE_ENTRYPOINT = {
30
+ fn: Component,
31
+ params: [{a: 0, b: 0}],
32
+ sequentialRenders: [
33
+ {a: 0, b: 0},
34
+ {a: 0, b: 1},
35
+ {a: 1, b: 1},
36
+ {a: 0, b: 0},
37
+ ],
38
+};
39
+
40
+```
41
+
42
+## Code
43
+
44
+```javascript
45
+import { c as _c } from "react/compiler-runtime";
46
+import { useMemo } from "react";
47
+import {
48
+ typedCapture,
49
+ typedCreateFrom,
50
+ typedMutate,
51
+ ValidateMemoization,
52
+} from "shared-runtime";
53
+
54
+function Component(t0) {
55
+ const $ = _c(20);
56
+ const { a, b } = t0;
57
+ let t1;
58
+ let t2;
59
+ if ($[0] !== a) {
60
+ t2 = { a };
61
+ $[0] = a;
62
+ $[1] = t2;
63
+ } else {
64
+ t2 = $[1];
65
+ }
66
+ t1 = t2;
67
+ const o = t1;
68
+ let t3;
69
+ let x;
70
+ if ($[2] !== b || $[3] !== o) {
71
+ t3 = [o];
72
+ x = t3;
73
+ const y = typedCapture(x);
74
+ const z = typedCapture(y);
75
+ x.push(z);
76
+ x.push(b);
77
+ $[2] = b;
78
+ $[3] = o;
79
+ $[4] = x;
80
+ $[5] = t3;
81
+ } else {
82
+ x = $[4];
83
+ t3 = $[5];
84
+ }
85
+ let t4;
86
+ if ($[6] !== a) {
87
+ t4 = [a];
88
+ $[6] = a;
89
+ $[7] = t4;
90
+ } else {
91
+ t4 = $[7];
92
+ }
93
+ let t5;
94
+ if ($[8] !== o || $[9] !== t4) {
95
+ t5 = <ValidateMemoization inputs={t4} output={o} alwaysCheck={true} />;
96
+ $[8] = o;
97
+ $[9] = t4;
98
+ $[10] = t5;
99
+ } else {
100
+ t5 = $[10];
101
+ }
102
+ let t6;
103
+ if ($[11] !== a || $[12] !== b) {
104
+ t6 = [a, b];
105
+ $[11] = a;
106
+ $[12] = b;
107
+ $[13] = t6;
108
+ } else {
109
+ t6 = $[13];
110
+ }
111
+ let t7;
112
+ if ($[14] !== t6 || $[15] !== x) {
113
+ t7 = <ValidateMemoization inputs={t6} output={x} alwaysCheck={true} />;
114
+ $[14] = t6;
115
+ $[15] = x;
116
+ $[16] = t7;
117
+ } else {
118
+ t7 = $[16];
119
+ }
120
+ let t8;
121
+ if ($[17] !== t5 || $[18] !== t7) {
122
+ t8 = (
123
+ <>
124
+ {t5};{t7};
125
+ </>
126
+ );
127
+ $[17] = t5;
128
+ $[18] = t7;
129
+ $[19] = t8;
130
+ } else {
131
+ t8 = $[19];
132
+ }
133
+ return t8;
134
+}
135
+
136
+export const FIXTURE_ENTRYPOINT = {
137
+ fn: Component,
138
+ params: [{ a: 0, b: 0 }],
139
+ sequentialRenders: [
140
+ { a: 0, b: 0 },
141
+ { a: 0, b: 1 },
142
+ { a: 1, b: 1 },
143
+ { a: 0, b: 0 },
144
+ ],
145
+};
146
+
147
+```
148
+
149
+### Eval output
150
+(kind: ok) <div>{"inputs":[0],"output":{"a":0}}</div>;<div>{"inputs":[0,0],"output":[{"a":0},[["[[ cyclic ref *2 ]]"]],0]}</div>;
151
+<div>{"inputs":[0],"output":{"a":0}}</div>;<div>{"inputs":[0,1],"output":[{"a":0},[["[[ cyclic ref *2 ]]"]],1]}</div>;
152
+<div>{"inputs":[1],"output":{"a":1}}</div>;<div>{"inputs":[1,1],"output":[{"a":1},[["[[ cyclic ref *2 ]]"]],1]}</div>;
153
+<div>{"inputs":[0],"output":{"a":0}}</div>;<div>{"inputs":[0,0],"output":[{"a":0},[["[[ cyclic ref *2 ]]"]],0]}</div>;
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-add-captured-array-to-itself.tsx
new
+34
@@ -0,0 +1,34 @@
1
+import {useMemo} from 'react';
2
+import {
3
+ typedCapture,
4
+ typedCreateFrom,
5
+ typedMutate,
6
+ ValidateMemoization,
7
+} from 'shared-runtime';
8
+
9
+function Component({a, b}) {
10
+ const o: any = useMemo(() => ({a}), [a]);
11
+ const x: Array<any> = useMemo(() => [o], [o, b]);
12
+ const y = typedCapture(x);
13
+ const z = typedCapture(y);
14
+ x.push(z);
15
+ x.push(b);
16
+
17
+ return (
18
+ <>
19
+ <ValidateMemoization inputs={[a]} output={o} alwaysCheck={true} />;
20
+ <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
21
+ </>
22
+ );
23
+}
24
+
25
+export const FIXTURE_ENTRYPOINT = {
26
+ fn: Component,
27
+ params: [{a: 0, b: 0}],
28
+ sequentialRenders: [
29
+ {a: 0, b: 0},
30
+ {a: 0, b: 1},
31
+ {a: 1, b: 1},
32
+ {a: 0, b: 0},
33
+ ],
34
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.expect.md
new
+115
@@ -0,0 +1,115 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useMemo} from 'react';
6
+import {
7
+ typedCapture,
8
+ typedCreateFrom,
9
+ typedMutate,
10
+ ValidateMemoization,
11
+} from 'shared-runtime';
12
+
13
+function Component({a, b}: {a: number; b: number}) {
14
+ const x = useMemo(() => ({a}), [a, b]);
15
+ const f = () => {
16
+ const y = typedCapture(x);
17
+ const z = typedCreateFrom(y);
18
+ return z;
19
+ };
20
+ const z = f();
21
+ // mutates x
22
+ typedMutate(z, b);
23
+
24
+ return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
25
+}
26
+
27
+export const FIXTURE_ENTRYPOINT = {
28
+ fn: Component,
29
+ params: [{a: 0, b: 0}],
30
+ sequentialRenders: [
31
+ {a: 0, b: 0},
32
+ {a: 0, b: 1},
33
+ {a: 1, b: 1},
34
+ {a: 0, b: 0},
35
+ ],
36
+};
37
+
38
+```
39
+
40
+## Code
41
+
42
+```javascript
43
+import { c as _c } from "react/compiler-runtime";
44
+import { useMemo } from "react";
45
+import {
46
+ typedCapture,
47
+ typedCreateFrom,
48
+ typedMutate,
49
+ ValidateMemoization,
50
+} from "shared-runtime";
51
+
52
+function Component(t0) {
53
+ const $ = _c(10);
54
+ const { a, b } = t0;
55
+ let t1;
56
+ let x;
57
+ if ($[0] !== a || $[1] !== b) {
58
+ t1 = { a };
59
+ x = t1;
60
+ const f = () => {
61
+ const y = typedCapture(x);
62
+ const z = typedCreateFrom(y);
63
+ return z;
64
+ };
65
+
66
+ const z_0 = f();
67
+
68
+ typedMutate(z_0, b);
69
+ $[0] = a;
70
+ $[1] = b;
71
+ $[2] = x;
72
+ $[3] = t1;
73
+ } else {
74
+ x = $[2];
75
+ t1 = $[3];
76
+ }
77
+ let t2;
78
+ if ($[4] !== a || $[5] !== b) {
79
+ t2 = [a, b];
80
+ $[4] = a;
81
+ $[5] = b;
82
+ $[6] = t2;
83
+ } else {
84
+ t2 = $[6];
85
+ }
86
+ let t3;
87
+ if ($[7] !== t2 || $[8] !== x) {
88
+ t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
89
+ $[7] = t2;
90
+ $[8] = x;
91
+ $[9] = t3;
92
+ } else {
93
+ t3 = $[9];
94
+ }
95
+ return t3;
96
+}
97
+
98
+export const FIXTURE_ENTRYPOINT = {
99
+ fn: Component,
100
+ params: [{ a: 0, b: 0 }],
101
+ sequentialRenders: [
102
+ { a: 0, b: 0 },
103
+ { a: 0, b: 1 },
104
+ { a: 1, b: 1 },
105
+ { a: 0, b: 0 },
106
+ ],
107
+};
108
+
109
+```
110
+
111
+### Eval output
112
+(kind: ok) <div>{"inputs":[0,0],"output":{"a":0,"property":0}}</div>
113
+<div>{"inputs":[0,1],"output":{"a":0,"property":1}}</div>
114
+<div>{"inputs":[1,1],"output":{"a":1,"property":1}}</div>
115
+<div>{"inputs":[0,0],"output":{"a":0,"property":0}}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom-lambda.tsx
new
+32
@@ -0,0 +1,32 @@
1
+import {useMemo} from 'react';
2
+import {
3
+ typedCapture,
4
+ typedCreateFrom,
5
+ typedMutate,
6
+ ValidateMemoization,
7
+} from 'shared-runtime';
8
+
9
+function Component({a, b}: {a: number; b: number}) {
10
+ const x = useMemo(() => ({a}), [a, b]);
11
+ const f = () => {
12
+ const y = typedCapture(x);
13
+ const z = typedCreateFrom(y);
14
+ return z;
15
+ };
16
+ const z = f();
17
+ // mutates x
18
+ typedMutate(z, b);
19
+
20
+ return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
21
+}
22
+
23
+export const FIXTURE_ENTRYPOINT = {
24
+ fn: Component,
25
+ params: [{a: 0, b: 0}],
26
+ sequentialRenders: [
27
+ {a: 0, b: 0},
28
+ {a: 0, b: 1},
29
+ {a: 1, b: 1},
30
+ {a: 0, b: 0},
31
+ ],
32
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.expect.md
new
+106
@@ -0,0 +1,106 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useMemo} from 'react';
6
+import {
7
+ typedCapture,
8
+ typedCreateFrom,
9
+ typedMutate,
10
+ ValidateMemoization,
11
+} from 'shared-runtime';
12
+
13
+function Component({a, b}: {a: number; b: number}) {
14
+ const x = useMemo(() => ({a}), [a, b]);
15
+ const y = typedCapture(x);
16
+ const z = typedCreateFrom(y);
17
+ // mutates x
18
+ typedMutate(z, b);
19
+
20
+ return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
21
+}
22
+
23
+export const FIXTURE_ENTRYPOINT = {
24
+ fn: Component,
25
+ params: [{a: 0, b: 0}],
26
+ sequentialRenders: [
27
+ {a: 0, b: 0},
28
+ {a: 0, b: 1},
29
+ {a: 1, b: 1},
30
+ {a: 0, b: 0},
31
+ ],
32
+};
33
+
34
+```
35
+
36
+## Code
37
+
38
+```javascript
39
+import { c as _c } from "react/compiler-runtime";
40
+import { useMemo } from "react";
41
+import {
42
+ typedCapture,
43
+ typedCreateFrom,
44
+ typedMutate,
45
+ ValidateMemoization,
46
+} from "shared-runtime";
47
+
48
+function Component(t0) {
49
+ const $ = _c(10);
50
+ const { a, b } = t0;
51
+ let t1;
52
+ let x;
53
+ if ($[0] !== a || $[1] !== b) {
54
+ t1 = { a };
55
+ x = t1;
56
+ const y = typedCapture(x);
57
+ const z = typedCreateFrom(y);
58
+
59
+ typedMutate(z, b);
60
+ $[0] = a;
61
+ $[1] = b;
62
+ $[2] = x;
63
+ $[3] = t1;
64
+ } else {
65
+ x = $[2];
66
+ t1 = $[3];
67
+ }
68
+ let t2;
69
+ if ($[4] !== a || $[5] !== b) {
70
+ t2 = [a, b];
71
+ $[4] = a;
72
+ $[5] = b;
73
+ $[6] = t2;
74
+ } else {
75
+ t2 = $[6];
76
+ }
77
+ let t3;
78
+ if ($[7] !== t2 || $[8] !== x) {
79
+ t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
80
+ $[7] = t2;
81
+ $[8] = x;
82
+ $[9] = t3;
83
+ } else {
84
+ t3 = $[9];
85
+ }
86
+ return t3;
87
+}
88
+
89
+export const FIXTURE_ENTRYPOINT = {
90
+ fn: Component,
91
+ params: [{ a: 0, b: 0 }],
92
+ sequentialRenders: [
93
+ { a: 0, b: 0 },
94
+ { a: 0, b: 1 },
95
+ { a: 1, b: 1 },
96
+ { a: 0, b: 0 },
97
+ ],
98
+};
99
+
100
+```
101
+
102
+### Eval output
103
+(kind: ok) <div>{"inputs":[0,0],"output":{"a":0,"property":0}}</div>
104
+<div>{"inputs":[0,1],"output":{"a":0,"property":1}}</div>
105
+<div>{"inputs":[1,1],"output":{"a":1,"property":1}}</div>
106
+<div>{"inputs":[0,0],"output":{"a":0,"property":0}}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-capture-createfrom.tsx
new
+28
@@ -0,0 +1,28 @@
1
+import {useMemo} from 'react';
2
+import {
3
+ typedCapture,
4
+ typedCreateFrom,
5
+ typedMutate,
6
+ ValidateMemoization,
7
+} from 'shared-runtime';
8
+
9
+function Component({a, b}: {a: number; b: number}) {
10
+ const x = useMemo(() => ({a}), [a, b]);
11
+ const y = typedCapture(x);
12
+ const z = typedCreateFrom(y);
13
+ // mutates x
14
+ typedMutate(z, b);
15
+
16
+ return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
17
+}
18
+
19
+export const FIXTURE_ENTRYPOINT = {
20
+ fn: Component,
21
+ params: [{a: 0, b: 0}],
22
+ sequentialRenders: [
23
+ {a: 0, b: 0},
24
+ {a: 0, b: 1},
25
+ {a: 1, b: 1},
26
+ {a: 0, b: 0},
27
+ ],
28
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-createfrom-capture.expect.md
new
+103
@@ -0,0 +1,103 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useMemo} from 'react';
6
+import {
7
+ typedCapture,
8
+ typedCreateFrom,
9
+ typedMutate,
10
+ ValidateMemoization,
11
+} from 'shared-runtime';
12
+
13
+function Component({a, b}) {
14
+ const x = useMemo(() => [{a}], [a]);
15
+ const y = typedCreateFrom(x);
16
+ const z = typedCapture(y);
17
+ // does not mutate x, so x should not depend on b
18
+ typedMutate(z, b);
19
+
20
+ return <ValidateMemoization inputs={[a]} output={x} alwaysCheck={true} />;
21
+}
22
+
23
+export const FIXTURE_ENTRYPOINT = {
24
+ fn: Component,
25
+ params: [{a: 0, b: 0}],
26
+ sequentialRenders: [
27
+ {a: 0, b: 0},
28
+ {a: 0, b: 1},
29
+ {a: 1, b: 1},
30
+ {a: 0, b: 0},
31
+ ],
32
+};
33
+
34
+```
35
+
36
+## Code
37
+
38
+```javascript
39
+import { c as _c } from "react/compiler-runtime";
40
+import { useMemo } from "react";
41
+import {
42
+ typedCapture,
43
+ typedCreateFrom,
44
+ typedMutate,
45
+ ValidateMemoization,
46
+} from "shared-runtime";
47
+
48
+function Component(t0) {
49
+ const $ = _c(7);
50
+ const { a, b } = t0;
51
+ let t1;
52
+ let t2;
53
+ if ($[0] !== a) {
54
+ t2 = [{ a }];
55
+ $[0] = a;
56
+ $[1] = t2;
57
+ } else {
58
+ t2 = $[1];
59
+ }
60
+ t1 = t2;
61
+ const x = t1;
62
+ const y = typedCreateFrom(x);
63
+ const z = typedCapture(y);
64
+
65
+ typedMutate(z, b);
66
+ let t3;
67
+ if ($[2] !== a) {
68
+ t3 = [a];
69
+ $[2] = a;
70
+ $[3] = t3;
71
+ } else {
72
+ t3 = $[3];
73
+ }
74
+ let t4;
75
+ if ($[4] !== t3 || $[5] !== x) {
76
+ t4 = <ValidateMemoization inputs={t3} output={x} alwaysCheck={true} />;
77
+ $[4] = t3;
78
+ $[5] = x;
79
+ $[6] = t4;
80
+ } else {
81
+ t4 = $[6];
82
+ }
83
+ return t4;
84
+}
85
+
86
+export const FIXTURE_ENTRYPOINT = {
87
+ fn: Component,
88
+ params: [{ a: 0, b: 0 }],
89
+ sequentialRenders: [
90
+ { a: 0, b: 0 },
91
+ { a: 0, b: 1 },
92
+ { a: 1, b: 1 },
93
+ { a: 0, b: 0 },
94
+ ],
95
+};
96
+
97
+```
98
+
99
+### Eval output
100
+(kind: ok) <div>{"inputs":[0],"output":[{"a":0}]}</div>
101
+<div>{"inputs":[0],"output":[{"a":0}]}</div>
102
+<div>{"inputs":[1],"output":[{"a":1}]}</div>
103
+<div>{"inputs":[0],"output":[{"a":0}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-createfrom-capture.tsx
new
+28
@@ -0,0 +1,28 @@
1
+import {useMemo} from 'react';
2
+import {
3
+ typedCapture,
4
+ typedCreateFrom,
5
+ typedMutate,
6
+ ValidateMemoization,
7
+} from 'shared-runtime';
8
+
9
+function Component({a, b}) {
10
+ const x = useMemo(() => [{a}], [a]);
11
+ const y = typedCreateFrom(x);
12
+ const z = typedCapture(y);
13
+ // does not mutate x, so x should not depend on b
14
+ typedMutate(z, b);
15
+
16
+ return <ValidateMemoization inputs={[a]} output={x} alwaysCheck={true} />;
17
+}
18
+
19
+export const FIXTURE_ENTRYPOINT = {
20
+ fn: Component,
21
+ params: [{a: 0, b: 0}],
22
+ sequentialRenders: [
23
+ {a: 0, b: 0},
24
+ {a: 0, b: 1},
25
+ {a: 1, b: 1},
26
+ {a: 0, b: 0},
27
+ ],
28
+};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.expect.md
new
+122
@@ -0,0 +1,122 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import {useMemo} from 'react';
6
+import {
7
+ typedCapture,
8
+ typedCreateFrom,
9
+ typedMutate,
10
+ ValidateMemoization,
11
+} from 'shared-runtime';
12
+
13
+function Component({a, b}) {
14
+ const x = useMemo(() => [{a}], [a, b]);
15
+ let z: any;
16
+ if (b) {
17
+ z = x;
18
+ } else {
19
+ z = typedCapture(x);
20
+ }
21
+ // could mutate x
22
+ typedMutate(z, b);
23
+
24
+ return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
25
+}
26
+
27
+export const FIXTURE_ENTRYPOINT = {
28
+ fn: Component,
29
+ params: [{a: 0, b: 0}],
30
+ sequentialRenders: [
31
+ {a: 0, b: 0},
32
+ {a: 0, b: 1},
33
+ {a: 1, b: 1},
34
+ {a: 0, b: 0},
35
+ ],
36
+};
37
+
38
+```
39
+
40
+## Code
41
+
42
+```javascript
43
+import { c as _c } from "react/compiler-runtime";
44
+import { useMemo } from "react";
45
+import {
46
+ typedCapture,
47
+ typedCreateFrom,
48
+ typedMutate,
49
+ ValidateMemoization,
50
+} from "shared-runtime";
51
+
52
+function Component(t0) {
53
+ const $ = _c(12);
54
+ const { a, b } = t0;
55
+ let t1;
56
+ let t2;
57
+ if ($[0] !== a) {
58
+ t2 = { a };
59
+ $[0] = a;
60
+ $[1] = t2;
61
+ } else {
62
+ t2 = $[1];
63
+ }
64
+ let x;
65
+ if ($[2] !== b || $[3] !== t2) {
66
+ t1 = [t2];
67
+ x = t1;
68
+ let z;
69
+ if (b) {
70
+ z = x;
71
+ } else {
72
+ z = typedCapture(x);
73
+ }
74
+
75
+ typedMutate(z, b);
76
+ $[2] = b;
77
+ $[3] = t2;
78
+ $[4] = x;
79
+ $[5] = t1;
80
+ } else {
81
+ x = $[4];
82
+ t1 = $[5];
83
+ }
84
+ let t3;
85
+ if ($[6] !== a || $[7] !== b) {
86
+ t3 = [a, b];
87
+ $[6] = a;
88
+ $[7] = b;
89
+ $[8] = t3;
90
+ } else {
91
+ t3 = $[8];
92
+ }
93
+ let t4;
94
+ if ($[9] !== t3 || $[10] !== x) {
95
+ t4 = <ValidateMemoization inputs={t3} output={x} alwaysCheck={true} />;
96
+ $[9] = t3;
97
+ $[10] = x;
98
+ $[11] = t4;
99
+ } else {
100
+ t4 = $[11];
101
+ }
102
+ return t4;
103
+}
104
+
105
+export const FIXTURE_ENTRYPOINT = {
106
+ fn: Component,
107
+ params: [{ a: 0, b: 0 }],
108
+ sequentialRenders: [
109
+ { a: 0, b: 0 },
110
+ { a: 0, b: 1 },
111
+ { a: 1, b: 1 },
112
+ { a: 0, b: 0 },
113
+ ],
114
+};
115
+
116
+```
117
+
118
+### Eval output
119
+(kind: ok) <div>{"inputs":[0,0],"output":[{"a":0}]}</div>
120
+<div>{"inputs":[0,1],"output":[{"a":0}]}</div>
121
+<div>{"inputs":[1,1],"output":[{"a":1}]}</div>
122
+<div>{"inputs":[0,0],"output":[{"a":0}]}</div>
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/transitivity-phi-assign-or-capture.tsx
new
+32
@@ -0,0 +1,32 @@
1
+import {useMemo} from 'react';
2
+import {
3
+ typedCapture,
4
+ typedCreateFrom,
5
+ typedMutate,
6
+ ValidateMemoization,
7
+} from 'shared-runtime';
8
+
9
+function Component({a, b}) {
10
+ const x = useMemo(() => [{a}], [a, b]);
11
+ let z: any;
12
+ if (b) {
13
+ z = x;
14
+ } else {
15
+ z = typedCapture(x);
16
+ }
17
+ // could mutate x
18
+ typedMutate(z, b);
19
+
20
+ return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
21
+}
22
+
23
+export const FIXTURE_ENTRYPOINT = {
24
+ fn: Component,
25
+ params: [{a: 0, b: 0}],
26
+ sequentialRenders: [
27
+ {a: 0, b: 0},
28
+ {a: 0, b: 1},
29
+ {a: 1, b: 1},
30
+ {a: 0, b: 0},
31
+ ],
32
+};
compiler/packages/babel-plugin-react-compiler/src/index.ts
+1
@@ -30,6 +30,7 @@ export {
30
export {
31
Effect,
32
ValueKind,
33
+ ValueReason,
34
printHIR,
35
printFunctionWithOutlined,
36
validateEnvironmentConfig,
compiler/packages/snap/src/compiler.ts
+9
-1
@@ -18,7 +18,11 @@ import type {
18
CompilerReactTarget,
19
CompilerPipelineValue,
20
} from 'babel-plugin-react-compiler/src/Entrypoint';
21
-import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR';
21
+import type {
22
+ Effect,
23
+ ValueKind,
24
+ ValueReason,
25
+} from 'babel-plugin-react-compiler/src/HIR';
26
import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils';
27
import * as HermesParser from 'hermes-parser';
28
import invariant from 'invariant';
@@ -42,6 +46,7 @@ function makePluginOptions(
46
debugIRLogger: (value: CompilerPipelineValue) => void,
47
EffectEnum: typeof Effect,
48
ValueKindEnum: typeof ValueKind,
49
+ ValueReasonEnum: typeof ValueReason,
50
): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] {
51
// TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false
52
let validatePreserveExistingMemoizationGuarantees = false;
@@ -77,6 +82,7 @@ function makePluginOptions(
82
moduleTypeProvider: makeSharedRuntimeTypeProvider({
83
EffectEnum,
84
ValueKindEnum,
85
+ ValueReasonEnum,
86
}),
87
assertValidMutableRanges: true,
88
validatePreserveExistingMemoizationGuarantees,
@@ -209,6 +215,7 @@ export async function transformFixtureInput(
215
debugIRLogger: (value: CompilerPipelineValue) => void,
216
EffectEnum: typeof Effect,
217
ValueKindEnum: typeof ValueKind,
218
+ ValueReasonEnum: typeof ValueReason,
219
): Promise<{kind: 'ok'; value: TransformResult} | {kind: 'err'; msg: string}> {
220
// Extract the first line to quickly check for custom test directives
221
const firstLine = input.substring(0, input.indexOf('\n'));
@@ -237,6 +244,7 @@ export async function transformFixtureInput(
244
debugIRLogger,
245
EffectEnum,
246
ValueKindEnum,
247
+ ValueReasonEnum,
248
);
249
const forgetResult = transformFromAstSync(inputAst, input, {
250
filename: virtualFilepath,
compiler/packages/snap/src/runner-worker.ts
+5
@@ -24,6 +24,7 @@ import type {
24
CompilerPipelineValue,
25
Effect,
26
ValueKind,
27
+ ValueReason,
28
} from 'babel-plugin-react-compiler/src';
29
import chalk from 'chalk';
30
@@ -78,6 +79,9 @@ async function compile(
79
const ValueKindEnum = importedCompilerPlugin[
80
'ValueKind'
81
] as typeof ValueKind;
82
+ const ValueReasonEnum = importedCompilerPlugin[
83
+ 'ValueReason'
84
+ ] as typeof ValueReason;
85
const printFunctionWithOutlined = importedCompilerPlugin[
86
PRINT_HIR_IMPORT
87
] as typeof PrintFunctionWithOutlined;
@@ -128,6 +132,7 @@ async function compile(
132
debugIRLogger,
133
EffectEnum,
134
ValueKindEnum,
135
+ ValueReasonEnum,
136
);
137
138
if (result.kind === 'err') {
compiler/packages/snap/src/sprout/shared-runtime-type-provider.ts
+112
-1
@@ -5,15 +5,21 @@
5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src';
8
+import type {
9
+ Effect,
10
+ ValueKind,
11
+ ValueReason,
12
+} from 'babel-plugin-react-compiler/src';
13
import type {TypeConfig} from 'babel-plugin-react-compiler/src/HIR/TypeSchema';
14
15
export function makeSharedRuntimeTypeProvider({
16
EffectEnum,
17
ValueKindEnum,
18
+ ValueReasonEnum,
19
}: {
20
EffectEnum: typeof Effect;
21
ValueKindEnum: typeof ValueKind;
22
+ ValueReasonEnum: typeof ValueReason;
23
}) {
24
return function sharedRuntimeTypeProvider(
25
moduleName: string,
@@ -85,6 +91,111 @@ export function makeSharedRuntimeTypeProvider({
91
effects: [{kind: 'Assign', from: '@value', into: '@return'}],
92
},
93
},
94
+ typedAssign: {
95
+ kind: 'function',
96
+ positionalParams: [EffectEnum.Read],
97
+ restParam: null,
98
+ calleeEffect: EffectEnum.Read,
99
+ returnType: {kind: 'type', name: 'Any'},
100
+ returnValueKind: ValueKindEnum.Mutable,
101
+ aliasing: {
102
+ receiver: '@receiver',
103
+ params: ['@value'],
104
+ rest: null,
105
+ returns: '@return',
106
+ temporaries: [],
107
+ effects: [{kind: 'Assign', from: '@value', into: '@return'}],
108
+ },
109
+ },
110
+ typedAlias: {
111
+ kind: 'function',
112
+ positionalParams: [EffectEnum.Read],
113
+ restParam: null,
114
+ calleeEffect: EffectEnum.Read,
115
+ returnType: {kind: 'type', name: 'Any'},
116
+ returnValueKind: ValueKindEnum.Mutable,
117
+ aliasing: {
118
+ receiver: '@receiver',
119
+ params: ['@value'],
120
+ rest: null,
121
+ returns: '@return',
122
+ temporaries: [],
123
+ effects: [
124
+ {
125
+ kind: 'Create',
126
+ into: '@return',
127
+ value: ValueKindEnum.Mutable,
128
+ reason: ValueReasonEnum.KnownReturnSignature,
129
+ },
130
+ {kind: 'Alias', from: '@value', into: '@return'},
131
+ ],
132
+ },
133
+ },
134
+ typedCapture: {
135
+ kind: 'function',
136
+ positionalParams: [EffectEnum.Read],
137
+ restParam: null,
138
+ calleeEffect: EffectEnum.Read,
139
+ returnType: {kind: 'type', name: 'Array'},
140
+ returnValueKind: ValueKindEnum.Mutable,
141
+ aliasing: {
142
+ receiver: '@receiver',
143
+ params: ['@value'],
144
+ rest: null,
145
+ returns: '@return',
146
+ temporaries: [],
147
+ effects: [
148
+ {
149
+ kind: 'Create',
150
+ into: '@return',
151
+ value: ValueKindEnum.Mutable,
152
+ reason: ValueReasonEnum.KnownReturnSignature,
153
+ },
154
+ {kind: 'Capture', from: '@value', into: '@return'},
155
+ ],
156
+ },
157
+ },
158
+ typedCreateFrom: {
159
+ kind: 'function',
160
+ positionalParams: [EffectEnum.Read],
161
+ restParam: null,
162
+ calleeEffect: EffectEnum.Read,
163
+ returnType: {kind: 'type', name: 'Any'},
164
+ returnValueKind: ValueKindEnum.Mutable,
165
+ aliasing: {
166
+ receiver: '@receiver',
167
+ params: ['@value'],
168
+ rest: null,
169
+ returns: '@return',
170
+ temporaries: [],
171
+ effects: [{kind: 'CreateFrom', from: '@value', into: '@return'}],
172
+ },
173
+ },
174
+ typedMutate: {
175
+ kind: 'function',
176
+ positionalParams: [EffectEnum.Read, EffectEnum.Capture],
177
+ restParam: null,
178
+ calleeEffect: EffectEnum.Store,
179
+ returnType: {kind: 'type', name: 'Primitive'},
180
+ returnValueKind: ValueKindEnum.Primitive,
181
+ aliasing: {
182
+ receiver: '@receiver',
183
+ params: ['@object', '@value'],
184
+ rest: null,
185
+ returns: '@return',
186
+ temporaries: [],
187
+ effects: [
188
+ {
189
+ kind: 'Create',
190
+ into: '@return',
191
+ value: ValueKindEnum.Primitive,
192
+ reason: ValueReasonEnum.KnownReturnSignature,
193
+ },
194
+ {kind: 'Mutate', value: '@object'},
195
+ {kind: 'Capture', from: '@value', into: '@object'},
196
+ ],
197
+ },
198
+ },
199
},
200
};
201
} else if (moduleName === 'ReactCompilerTest') {
compiler/packages/snap/src/sprout/shared-runtime.ts
+26
-3
@@ -269,10 +269,12 @@ export function ValidateMemoization({
269
inputs,
270
output: rawOutput,
271
onlyCheckCompiled = false,
272
+ alwaysCheck = false,
273
}: {
274
inputs: Array<any>;
275
output: any;
275
- onlyCheckCompiled: boolean;
276
+ onlyCheckCompiled?: boolean;
277
+ alwaysCheck?: boolean;
278
}): React.ReactElement {
279
'use no forget';
280
// Wrap rawOutput as it might be a function, which useState would invoke.
@@ -280,8 +282,9 @@ export function ValidateMemoization({
282
const [previousInputs, setPreviousInputs] = React.useState(inputs);
283
const [previousOutput, setPreviousOutput] = React.useState(output);
284
if (
283
- onlyCheckCompiled &&
284
- (globalThis as any).__SNAP_EVALUATOR_MODE === 'forget'
285
+ alwaysCheck ||
286
+ (onlyCheckCompiled &&
287
+ (globalThis as any).__SNAP_EVALUATOR_MODE === 'forget')
288
) {
289
if (
290
inputs.length !== previousInputs.length ||
@@ -400,4 +403,24 @@ export function typedIdentity<T>(value: T): T {
403
return value;
404
}
405
406
+export function typedAssign<T>(x: T): T {
407
+ return x;
408
+}
409
+
410
+export function typedAlias<T>(x: T): T {
411
+ return x;
412
+}
413
+
414
+export function typedCapture<T>(x: T): Array<T> {
415
+ return [x];
416
+}
417
+
418
+export function typedCreateFrom<T>(array: Array<T>): T {
419
+ return array[0];
420
+}
421
+
422
+export function typedMutate(x: any, v: any = null): void {
423
+ x.property = v;
424
+}
425
+
426
export default typedLog;