@samitouri / QOS-React / commits / cc3806377a

[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;