Skip to content

Commit afe22b9

Browse files
committed
[compiler] Tests for different orders of createfrom/capture w/wo function expressions
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).
1 parent b6b1f82 commit afe22b9

20 files changed

+1289
-7
lines changed

compiler/packages/babel-plugin-react-compiler/src/Inference/MUTABILITY_ALIASING_MODEL.md

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,9 @@ a.property = value // a _is_ b, this mutates b
271271

272272
```
273273
CreateFrom a <- b
274-
Mutate A
274+
Mutate a
275275
=>
276-
MutateTransitive b
276+
Mutate b
277277
```
278278

279279
Example:
@@ -301,6 +301,26 @@ a.b = b;
301301
a.property = value; // mutates a, not b
302302
```
303303

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+
304324
### TransitiveMutation of Alias, Assignment, CreateFrom, or Capture Mutates the Source
305325

306326
```
Original file line numberDiff line numberDiff line change
@@ -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>;
Original file line numberDiff line numberDiff line change
@@ -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+
};
Original file line numberDiff line numberDiff line change
@@ -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>

0 commit comments

Comments
 (0)