[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).
This commit is contained in:
Joe Savona
2025-06-18 13:32:54 -07:00
parent 730cf6a890
commit 4e4c353e2a
20 changed files with 1289 additions and 7 deletions
@@ -271,9 +271,9 @@ a.property = value // a _is_ b, this mutates b
```
CreateFrom a <- b
Mutate A
Mutate a
=>
MutateTransitive b
Mutate b
```
Example:
@@ -301,6 +301,26 @@ a.b = b;
a.property = value; // mutates a, not b
```
### Mutation of Source Affects Alias, Assignment, CreateFrom, and Capture
```
Alias a <- b OR Assign a <- b OR CreateFrom a <- b OR Capture a <- b
Mutate b
=>
Mutate a
```
A derived value changes when it's source value is mutated.
Example:
```js
const x = {};
const y = [x];
x.y = true; // this changes the value within `y` ie mutates y
```
### TransitiveMutation of Alias, Assignment, CreateFrom, or Capture Mutates the Source
```
@@ -0,0 +1,166 @@
## Input
```javascript
import {useMemo} from 'react';
import {
mutate,
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b, c}: {a: number; b: number; c: number}) {
const x = useMemo(() => [{value: a}], [a, b, c]);
if (b === 0) {
// This object should only depend on c, it cannot be affected by the later mutation
x.push({value: c});
} else {
// This mutation shouldn't affect the object in the consequent
mutate(x);
}
return (
<>
<ValidateMemoization inputs={[a, b, c]} output={x} alwaysCheck={true} />;
{/* TODO: should only depend on c */}
<ValidateMemoization
inputs={[a, b, c]}
output={x[0]}
alwaysCheck={true}
/>
;
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0, c: 0}],
sequentialRenders: [
{a: 0, b: 0, c: 0},
{a: 0, b: 1, c: 0},
{a: 1, b: 1, c: 0},
{a: 1, b: 1, c: 1},
{a: 1, b: 1, c: 0},
{a: 1, b: 0, c: 0},
{a: 0, b: 0, c: 0},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import {
mutate,
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from "shared-runtime";
function Component(t0) {
const $ = _c(22);
const { a, b, c } = t0;
let t1;
let x;
if ($[0] !== a || $[1] !== b || $[2] !== c) {
t1 = [{ value: a }];
x = t1;
if (b === 0) {
x.push({ value: c });
} else {
mutate(x);
}
$[0] = a;
$[1] = b;
$[2] = c;
$[3] = x;
$[4] = t1;
} else {
x = $[3];
t1 = $[4];
}
let t2;
if ($[5] !== a || $[6] !== b || $[7] !== c) {
t2 = [a, b, c];
$[5] = a;
$[6] = b;
$[7] = c;
$[8] = t2;
} else {
t2 = $[8];
}
let t3;
if ($[9] !== t2 || $[10] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
$[9] = t2;
$[10] = x;
$[11] = t3;
} else {
t3 = $[11];
}
let t4;
if ($[12] !== a || $[13] !== b || $[14] !== c) {
t4 = [a, b, c];
$[12] = a;
$[13] = b;
$[14] = c;
$[15] = t4;
} else {
t4 = $[15];
}
let t5;
if ($[16] !== t4 || $[17] !== x[0]) {
t5 = <ValidateMemoization inputs={t4} output={x[0]} alwaysCheck={true} />;
$[16] = t4;
$[17] = x[0];
$[18] = t5;
} else {
t5 = $[18];
}
let t6;
if ($[19] !== t3 || $[20] !== t5) {
t6 = (
<>
{t3};{t5};
</>
);
$[19] = t3;
$[20] = t5;
$[21] = t6;
} else {
t6 = $[21];
}
return t6;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 0, b: 0, c: 0 }],
sequentialRenders: [
{ a: 0, b: 0, c: 0 },
{ a: 0, b: 1, c: 0 },
{ a: 1, b: 1, c: 0 },
{ a: 1, b: 1, c: 1 },
{ a: 1, b: 1, c: 0 },
{ a: 1, b: 0, c: 0 },
{ a: 0, b: 0, c: 0 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[0,0,0],"output":[{"value":0},{"value":0}]}</div>;<div>{"inputs":[0,0,0],"output":{"value":0}}</div>;
<div>{"inputs":[0,1,0],"output":[{"value":0},"joe"]}</div>;<div>{"inputs":[0,1,0],"output":{"value":0}}</div>;
<div>{"inputs":[1,1,0],"output":[{"value":1},"joe"]}</div>;<div>{"inputs":[1,1,0],"output":{"value":1}}</div>;
<div>{"inputs":[1,1,1],"output":[{"value":1},"joe"]}</div>;<div>{"inputs":[1,1,1],"output":{"value":1}}</div>;
<div>{"inputs":[1,1,0],"output":[{"value":1},"joe"]}</div>;<div>{"inputs":[1,1,0],"output":{"value":1}}</div>;
<div>{"inputs":[1,0,0],"output":[{"value":1},{"value":0}]}</div>;<div>{"inputs":[1,0,0],"output":{"value":1}}</div>;
<div>{"inputs":[0,0,0],"output":[{"value":0},{"value":0}]}</div>;<div>{"inputs":[0,0,0],"output":{"value":0}}</div>;
@@ -0,0 +1,46 @@
import {useMemo} from 'react';
import {
mutate,
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b, c}: {a: number; b: number; c: number}) {
const x = useMemo(() => [{value: a}], [a, b, c]);
if (b === 0) {
// This object should only depend on c, it cannot be affected by the later mutation
x.push({value: c});
} else {
// This mutation shouldn't affect the object in the consequent
mutate(x);
}
return (
<>
<ValidateMemoization inputs={[a, b, c]} output={x} alwaysCheck={true} />;
{/* TODO: should only depend on c */}
<ValidateMemoization
inputs={[a, b, c]}
output={x[0]}
alwaysCheck={true}
/>
;
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0, c: 0}],
sequentialRenders: [
{a: 0, b: 0, c: 0},
{a: 0, b: 1, c: 0},
{a: 1, b: 1, c: 0},
{a: 1, b: 1, c: 1},
{a: 1, b: 1, c: 0},
{a: 1, b: 0, c: 0},
{a: 0, b: 0, c: 0},
],
};
@@ -0,0 +1,116 @@
## Input
```javascript
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
const x = useMemo(() => [{a}], [a]);
const f = () => {
const y = typedCreateFrom(x);
const z = typedCapture(y);
return z;
};
const z = f();
// does not mutate x, so x should not depend on b
typedMutate(z, b);
// TODO: this *should* only depend on `a`
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from "shared-runtime";
function Component(t0) {
const $ = _c(10);
const { a, b } = t0;
let t1;
let x;
if ($[0] !== a || $[1] !== b) {
t1 = [{ a }];
x = t1;
const f = () => {
const y = typedCreateFrom(x);
const z = typedCapture(y);
return z;
};
const z_0 = f();
typedMutate(z_0, b);
$[0] = a;
$[1] = b;
$[2] = x;
$[3] = t1;
} else {
x = $[2];
t1 = $[3];
}
let t2;
if ($[4] !== a || $[5] !== b) {
t2 = [a, b];
$[4] = a;
$[5] = b;
$[6] = t2;
} else {
t2 = $[6];
}
let t3;
if ($[7] !== t2 || $[8] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
$[7] = t2;
$[8] = x;
$[9] = t3;
} else {
t3 = $[9];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 0, b: 0 }],
sequentialRenders: [
{ a: 0, b: 0 },
{ a: 0, b: 1 },
{ a: 1, b: 1 },
{ a: 0, b: 0 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[0,0],"output":[{"a":0}]}</div>
<div>{"inputs":[0,1],"output":[{"a":0}]}</div>
<div>{"inputs":[1,1],"output":[{"a":1}]}</div>
<div>{"inputs":[0,0],"output":[{"a":0}]}</div>
@@ -0,0 +1,33 @@
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
const x = useMemo(() => [{a}], [a]);
const f = () => {
const y = typedCreateFrom(x);
const z = typedCapture(y);
return z;
};
const z = f();
// does not mutate x, so x should not depend on b
typedMutate(z, b);
// TODO: this *should* only depend on `a`
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
@@ -0,0 +1,153 @@
## Input
```javascript
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
const o: any = useMemo(() => ({a}), [a]);
const x: Array<any> = useMemo(() => [o], [o, b]);
const y = typedCapture(x);
const z = typedCapture(y);
x.push(z);
x.push(b);
return (
<>
<ValidateMemoization inputs={[a]} output={o} alwaysCheck={true} />;
<ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from "shared-runtime";
function Component(t0) {
const $ = _c(20);
const { a, b } = t0;
let t1;
let t2;
if ($[0] !== a) {
t2 = { a };
$[0] = a;
$[1] = t2;
} else {
t2 = $[1];
}
t1 = t2;
const o = t1;
let t3;
let x;
if ($[2] !== b || $[3] !== o) {
t3 = [o];
x = t3;
const y = typedCapture(x);
const z = typedCapture(y);
x.push(z);
x.push(b);
$[2] = b;
$[3] = o;
$[4] = x;
$[5] = t3;
} else {
x = $[4];
t3 = $[5];
}
let t4;
if ($[6] !== a) {
t4 = [a];
$[6] = a;
$[7] = t4;
} else {
t4 = $[7];
}
let t5;
if ($[8] !== o || $[9] !== t4) {
t5 = <ValidateMemoization inputs={t4} output={o} alwaysCheck={true} />;
$[8] = o;
$[9] = t4;
$[10] = t5;
} else {
t5 = $[10];
}
let t6;
if ($[11] !== a || $[12] !== b) {
t6 = [a, b];
$[11] = a;
$[12] = b;
$[13] = t6;
} else {
t6 = $[13];
}
let t7;
if ($[14] !== t6 || $[15] !== x) {
t7 = <ValidateMemoization inputs={t6} output={x} alwaysCheck={true} />;
$[14] = t6;
$[15] = x;
$[16] = t7;
} else {
t7 = $[16];
}
let t8;
if ($[17] !== t5 || $[18] !== t7) {
t8 = (
<>
{t5};{t7};
</>
);
$[17] = t5;
$[18] = t7;
$[19] = t8;
} else {
t8 = $[19];
}
return t8;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 0, b: 0 }],
sequentialRenders: [
{ a: 0, b: 0 },
{ a: 0, b: 1 },
{ a: 1, b: 1 },
{ a: 0, b: 0 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[0],"output":{"a":0}}</div>;<div>{"inputs":[0,0],"output":[{"a":0},[["[[ cyclic ref *2 ]]"]],0]}</div>;
<div>{"inputs":[0],"output":{"a":0}}</div>;<div>{"inputs":[0,1],"output":[{"a":0},[["[[ cyclic ref *2 ]]"]],1]}</div>;
<div>{"inputs":[1],"output":{"a":1}}</div>;<div>{"inputs":[1,1],"output":[{"a":1},[["[[ cyclic ref *2 ]]"]],1]}</div>;
<div>{"inputs":[0],"output":{"a":0}}</div>;<div>{"inputs":[0,0],"output":[{"a":0},[["[[ cyclic ref *2 ]]"]],0]}</div>;
@@ -0,0 +1,34 @@
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
const o: any = useMemo(() => ({a}), [a]);
const x: Array<any> = useMemo(() => [o], [o, b]);
const y = typedCapture(x);
const z = typedCapture(y);
x.push(z);
x.push(b);
return (
<>
<ValidateMemoization inputs={[a]} output={o} alwaysCheck={true} />;
<ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
@@ -0,0 +1,115 @@
## Input
```javascript
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}: {a: number; b: number}) {
const x = useMemo(() => ({a}), [a, b]);
const f = () => {
const y = typedCapture(x);
const z = typedCreateFrom(y);
return z;
};
const z = f();
// mutates x
typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from "shared-runtime";
function Component(t0) {
const $ = _c(10);
const { a, b } = t0;
let t1;
let x;
if ($[0] !== a || $[1] !== b) {
t1 = { a };
x = t1;
const f = () => {
const y = typedCapture(x);
const z = typedCreateFrom(y);
return z;
};
const z_0 = f();
typedMutate(z_0, b);
$[0] = a;
$[1] = b;
$[2] = x;
$[3] = t1;
} else {
x = $[2];
t1 = $[3];
}
let t2;
if ($[4] !== a || $[5] !== b) {
t2 = [a, b];
$[4] = a;
$[5] = b;
$[6] = t2;
} else {
t2 = $[6];
}
let t3;
if ($[7] !== t2 || $[8] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
$[7] = t2;
$[8] = x;
$[9] = t3;
} else {
t3 = $[9];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 0, b: 0 }],
sequentialRenders: [
{ a: 0, b: 0 },
{ a: 0, b: 1 },
{ a: 1, b: 1 },
{ a: 0, b: 0 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[0,0],"output":{"a":0,"property":0}}</div>
<div>{"inputs":[0,1],"output":{"a":0,"property":1}}</div>
<div>{"inputs":[1,1],"output":{"a":1,"property":1}}</div>
<div>{"inputs":[0,0],"output":{"a":0,"property":0}}</div>
@@ -0,0 +1,32 @@
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}: {a: number; b: number}) {
const x = useMemo(() => ({a}), [a, b]);
const f = () => {
const y = typedCapture(x);
const z = typedCreateFrom(y);
return z;
};
const z = f();
// mutates x
typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
@@ -0,0 +1,106 @@
## Input
```javascript
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}: {a: number; b: number}) {
const x = useMemo(() => ({a}), [a, b]);
const y = typedCapture(x);
const z = typedCreateFrom(y);
// mutates x
typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from "shared-runtime";
function Component(t0) {
const $ = _c(10);
const { a, b } = t0;
let t1;
let x;
if ($[0] !== a || $[1] !== b) {
t1 = { a };
x = t1;
const y = typedCapture(x);
const z = typedCreateFrom(y);
typedMutate(z, b);
$[0] = a;
$[1] = b;
$[2] = x;
$[3] = t1;
} else {
x = $[2];
t1 = $[3];
}
let t2;
if ($[4] !== a || $[5] !== b) {
t2 = [a, b];
$[4] = a;
$[5] = b;
$[6] = t2;
} else {
t2 = $[6];
}
let t3;
if ($[7] !== t2 || $[8] !== x) {
t3 = <ValidateMemoization inputs={t2} output={x} alwaysCheck={true} />;
$[7] = t2;
$[8] = x;
$[9] = t3;
} else {
t3 = $[9];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 0, b: 0 }],
sequentialRenders: [
{ a: 0, b: 0 },
{ a: 0, b: 1 },
{ a: 1, b: 1 },
{ a: 0, b: 0 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[0,0],"output":{"a":0,"property":0}}</div>
<div>{"inputs":[0,1],"output":{"a":0,"property":1}}</div>
<div>{"inputs":[1,1],"output":{"a":1,"property":1}}</div>
<div>{"inputs":[0,0],"output":{"a":0,"property":0}}</div>
@@ -0,0 +1,28 @@
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}: {a: number; b: number}) {
const x = useMemo(() => ({a}), [a, b]);
const y = typedCapture(x);
const z = typedCreateFrom(y);
// mutates x
typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
@@ -0,0 +1,103 @@
## Input
```javascript
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
const x = useMemo(() => [{a}], [a]);
const y = typedCreateFrom(x);
const z = typedCapture(y);
// does not mutate x, so x should not depend on b
typedMutate(z, b);
return <ValidateMemoization inputs={[a]} output={x} alwaysCheck={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from "shared-runtime";
function Component(t0) {
const $ = _c(7);
const { a, b } = t0;
let t1;
let t2;
if ($[0] !== a) {
t2 = [{ a }];
$[0] = a;
$[1] = t2;
} else {
t2 = $[1];
}
t1 = t2;
const x = t1;
const y = typedCreateFrom(x);
const z = typedCapture(y);
typedMutate(z, b);
let t3;
if ($[2] !== a) {
t3 = [a];
$[2] = a;
$[3] = t3;
} else {
t3 = $[3];
}
let t4;
if ($[4] !== t3 || $[5] !== x) {
t4 = <ValidateMemoization inputs={t3} output={x} alwaysCheck={true} />;
$[4] = t3;
$[5] = x;
$[6] = t4;
} else {
t4 = $[6];
}
return t4;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 0, b: 0 }],
sequentialRenders: [
{ a: 0, b: 0 },
{ a: 0, b: 1 },
{ a: 1, b: 1 },
{ a: 0, b: 0 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[0],"output":[{"a":0}]}</div>
<div>{"inputs":[0],"output":[{"a":0}]}</div>
<div>{"inputs":[1],"output":[{"a":1}]}</div>
<div>{"inputs":[0],"output":[{"a":0}]}</div>
@@ -0,0 +1,28 @@
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
const x = useMemo(() => [{a}], [a]);
const y = typedCreateFrom(x);
const z = typedCapture(y);
// does not mutate x, so x should not depend on b
typedMutate(z, b);
return <ValidateMemoization inputs={[a]} output={x} alwaysCheck={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
@@ -0,0 +1,122 @@
## Input
```javascript
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
const x = useMemo(() => [{a}], [a, b]);
let z: any;
if (b) {
z = x;
} else {
z = typedCapture(x);
}
// could mutate x
typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from "shared-runtime";
function Component(t0) {
const $ = _c(12);
const { a, b } = t0;
let t1;
let t2;
if ($[0] !== a) {
t2 = { a };
$[0] = a;
$[1] = t2;
} else {
t2 = $[1];
}
let x;
if ($[2] !== b || $[3] !== t2) {
t1 = [t2];
x = t1;
let z;
if (b) {
z = x;
} else {
z = typedCapture(x);
}
typedMutate(z, b);
$[2] = b;
$[3] = t2;
$[4] = x;
$[5] = t1;
} else {
x = $[4];
t1 = $[5];
}
let t3;
if ($[6] !== a || $[7] !== b) {
t3 = [a, b];
$[6] = a;
$[7] = b;
$[8] = t3;
} else {
t3 = $[8];
}
let t4;
if ($[9] !== t3 || $[10] !== x) {
t4 = <ValidateMemoization inputs={t3} output={x} alwaysCheck={true} />;
$[9] = t3;
$[10] = x;
$[11] = t4;
} else {
t4 = $[11];
}
return t4;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ a: 0, b: 0 }],
sequentialRenders: [
{ a: 0, b: 0 },
{ a: 0, b: 1 },
{ a: 1, b: 1 },
{ a: 0, b: 0 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[0,0],"output":[{"a":0}]}</div>
<div>{"inputs":[0,1],"output":[{"a":0}]}</div>
<div>{"inputs":[1,1],"output":[{"a":1}]}</div>
<div>{"inputs":[0,0],"output":[{"a":0}]}</div>
@@ -0,0 +1,32 @@
import {useMemo} from 'react';
import {
typedCapture,
typedCreateFrom,
typedMutate,
ValidateMemoization,
} from 'shared-runtime';
function Component({a, b}) {
const x = useMemo(() => [{a}], [a, b]);
let z: any;
if (b) {
z = x;
} else {
z = typedCapture(x);
}
// could mutate x
typedMutate(z, b);
return <ValidateMemoization inputs={[a, b]} output={x} alwaysCheck={true} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{a: 0, b: 0}],
sequentialRenders: [
{a: 0, b: 0},
{a: 0, b: 1},
{a: 1, b: 1},
{a: 0, b: 0},
],
};
@@ -30,6 +30,7 @@ export {
export {
Effect,
ValueKind,
ValueReason,
printHIR,
printFunctionWithOutlined,
validateEnvironmentConfig,
+9 -1
View File
@@ -18,7 +18,11 @@ import type {
CompilerReactTarget,
CompilerPipelineValue,
} from 'babel-plugin-react-compiler/src/Entrypoint';
import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR';
import type {
Effect,
ValueKind,
ValueReason,
} from 'babel-plugin-react-compiler/src/HIR';
import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils';
import * as HermesParser from 'hermes-parser';
import invariant from 'invariant';
@@ -42,6 +46,7 @@ function makePluginOptions(
debugIRLogger: (value: CompilerPipelineValue) => void,
EffectEnum: typeof Effect,
ValueKindEnum: typeof ValueKind,
ValueReasonEnum: typeof ValueReason,
): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] {
// TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false
let validatePreserveExistingMemoizationGuarantees = false;
@@ -77,6 +82,7 @@ function makePluginOptions(
moduleTypeProvider: makeSharedRuntimeTypeProvider({
EffectEnum,
ValueKindEnum,
ValueReasonEnum,
}),
assertValidMutableRanges: true,
validatePreserveExistingMemoizationGuarantees,
@@ -209,6 +215,7 @@ export async function transformFixtureInput(
debugIRLogger: (value: CompilerPipelineValue) => void,
EffectEnum: typeof Effect,
ValueKindEnum: typeof ValueKind,
ValueReasonEnum: typeof ValueReason,
): Promise<{kind: 'ok'; value: TransformResult} | {kind: 'err'; msg: string}> {
// Extract the first line to quickly check for custom test directives
const firstLine = input.substring(0, input.indexOf('\n'));
@@ -237,6 +244,7 @@ export async function transformFixtureInput(
debugIRLogger,
EffectEnum,
ValueKindEnum,
ValueReasonEnum,
);
const forgetResult = transformFromAstSync(inputAst, input, {
filename: virtualFilepath,
@@ -24,6 +24,7 @@ import type {
CompilerPipelineValue,
Effect,
ValueKind,
ValueReason,
} from 'babel-plugin-react-compiler/src';
import chalk from 'chalk';
@@ -78,6 +79,9 @@ async function compile(
const ValueKindEnum = importedCompilerPlugin[
'ValueKind'
] as typeof ValueKind;
const ValueReasonEnum = importedCompilerPlugin[
'ValueReason'
] as typeof ValueReason;
const printFunctionWithOutlined = importedCompilerPlugin[
PRINT_HIR_IMPORT
] as typeof PrintFunctionWithOutlined;
@@ -128,6 +132,7 @@ async function compile(
debugIRLogger,
EffectEnum,
ValueKindEnum,
ValueReasonEnum,
);
if (result.kind === 'err') {
@@ -5,15 +5,21 @@
* LICENSE file in the root directory of this source tree.
*/
import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src';
import type {
Effect,
ValueKind,
ValueReason,
} from 'babel-plugin-react-compiler/src';
import type {TypeConfig} from 'babel-plugin-react-compiler/src/HIR/TypeSchema';
export function makeSharedRuntimeTypeProvider({
EffectEnum,
ValueKindEnum,
ValueReasonEnum,
}: {
EffectEnum: typeof Effect;
ValueKindEnum: typeof ValueKind;
ValueReasonEnum: typeof ValueReason;
}) {
return function sharedRuntimeTypeProvider(
moduleName: string,
@@ -85,6 +91,111 @@ export function makeSharedRuntimeTypeProvider({
effects: [{kind: 'Assign', from: '@value', into: '@return'}],
},
},
typedAssign: {
kind: 'function',
positionalParams: [EffectEnum.Read],
restParam: null,
calleeEffect: EffectEnum.Read,
returnType: {kind: 'type', name: 'Any'},
returnValueKind: ValueKindEnum.Mutable,
aliasing: {
receiver: '@receiver',
params: ['@value'],
rest: null,
returns: '@return',
temporaries: [],
effects: [{kind: 'Assign', from: '@value', into: '@return'}],
},
},
typedAlias: {
kind: 'function',
positionalParams: [EffectEnum.Read],
restParam: null,
calleeEffect: EffectEnum.Read,
returnType: {kind: 'type', name: 'Any'},
returnValueKind: ValueKindEnum.Mutable,
aliasing: {
receiver: '@receiver',
params: ['@value'],
rest: null,
returns: '@return',
temporaries: [],
effects: [
{
kind: 'Create',
into: '@return',
value: ValueKindEnum.Mutable,
reason: ValueReasonEnum.KnownReturnSignature,
},
{kind: 'Alias', from: '@value', into: '@return'},
],
},
},
typedCapture: {
kind: 'function',
positionalParams: [EffectEnum.Read],
restParam: null,
calleeEffect: EffectEnum.Read,
returnType: {kind: 'type', name: 'Array'},
returnValueKind: ValueKindEnum.Mutable,
aliasing: {
receiver: '@receiver',
params: ['@value'],
rest: null,
returns: '@return',
temporaries: [],
effects: [
{
kind: 'Create',
into: '@return',
value: ValueKindEnum.Mutable,
reason: ValueReasonEnum.KnownReturnSignature,
},
{kind: 'Capture', from: '@value', into: '@return'},
],
},
},
typedCreateFrom: {
kind: 'function',
positionalParams: [EffectEnum.Read],
restParam: null,
calleeEffect: EffectEnum.Read,
returnType: {kind: 'type', name: 'Any'},
returnValueKind: ValueKindEnum.Mutable,
aliasing: {
receiver: '@receiver',
params: ['@value'],
rest: null,
returns: '@return',
temporaries: [],
effects: [{kind: 'CreateFrom', from: '@value', into: '@return'}],
},
},
typedMutate: {
kind: 'function',
positionalParams: [EffectEnum.Read, EffectEnum.Capture],
restParam: null,
calleeEffect: EffectEnum.Store,
returnType: {kind: 'type', name: 'Primitive'},
returnValueKind: ValueKindEnum.Primitive,
aliasing: {
receiver: '@receiver',
params: ['@object', '@value'],
rest: null,
returns: '@return',
temporaries: [],
effects: [
{
kind: 'Create',
into: '@return',
value: ValueKindEnum.Primitive,
reason: ValueReasonEnum.KnownReturnSignature,
},
{kind: 'Mutate', value: '@object'},
{kind: 'Capture', from: '@value', into: '@object'},
],
},
},
},
};
} else if (moduleName === 'ReactCompilerTest') {
@@ -269,10 +269,12 @@ export function ValidateMemoization({
inputs,
output: rawOutput,
onlyCheckCompiled = false,
alwaysCheck = false,
}: {
inputs: Array<any>;
output: any;
onlyCheckCompiled: boolean;
onlyCheckCompiled?: boolean;
alwaysCheck?: boolean;
}): React.ReactElement {
'use no forget';
// Wrap rawOutput as it might be a function, which useState would invoke.
@@ -280,8 +282,9 @@ export function ValidateMemoization({
const [previousInputs, setPreviousInputs] = React.useState(inputs);
const [previousOutput, setPreviousOutput] = React.useState(output);
if (
onlyCheckCompiled &&
(globalThis as any).__SNAP_EVALUATOR_MODE === 'forget'
alwaysCheck ||
(onlyCheckCompiled &&
(globalThis as any).__SNAP_EVALUATOR_MODE === 'forget')
) {
if (
inputs.length !== previousInputs.length ||
@@ -400,4 +403,24 @@ export function typedIdentity<T>(value: T): T {
return value;
}
export function typedAssign<T>(x: T): T {
return x;
}
export function typedAlias<T>(x: T): T {
return x;
}
export function typedCapture<T>(x: T): Array<T> {
return [x];
}
export function typedCreateFrom<T>(array: Array<T>): T {
return array[0];
}
export function typedMutate(x: any, v: any = null): void {
x.property = v;
}
export default typedLog;