[compiler][newinference] Fixes, update remaining snapshots

A bunch of small fixes to make the remaining fixtures work. This is really really close now.

ghstack-source-id: a3d6803eb4
Pull Request resolved: https://github.com/facebook/react/pull/33477
This commit is contained in:
Joe Savona
2025-06-06 17:51:24 -07:00
parent bf7cbad4e3
commit 42e10399eb
32 changed files with 433 additions and 691 deletions
@@ -247,7 +247,6 @@ function runWithEnvironment(
}
if (!env.config.enableNewMutationAliasingModel) {
// NOTE: in the new model this is part of validateNoFreezingKnownMutableFunctions
validateLocalsNotReassignedAfterRender(hir);
}
@@ -275,6 +274,7 @@ function runWithEnvironment(
if (mutabilityAliasingErrors.isErr()) {
throw mutabilityAliasingErrors.unwrapErr();
}
validateLocalsNotReassignedAfterRender(hir);
}
}
@@ -43,6 +43,7 @@ import {
} from '../HIR/visitors';
import {Ok, Result} from '../Utils/Result';
import {
getArgumentEffect,
getFunctionCallSignature,
isKnownMutableEffect,
mergeValueKinds,
@@ -547,7 +548,11 @@ function applyEffect(
effect =>
effect.kind === 'MutateFrozen' || effect.kind === 'MutateGlobal',
);
const isMutable = hasCaptures || hasTrackedSideEffects;
// For legacy compatibility
const capturesRef = effect.function.loweredFunc.func.context.some(
operand => isRefOrRefValue(operand.identifier),
);
const isMutable = hasCaptures || hasTrackedSideEffects || capturesRef;
for (const operand of effect.function.loweredFunc.func.context) {
if (operand.effect !== Effect.Capture) {
continue;
@@ -805,6 +810,11 @@ function applyEffect(
effects,
);
}
const mutateIterator =
arg.kind === 'Spread' ? conditionallyMutateIterator(operand) : null;
if (mutateIterator) {
applyEffect(context, state, mutateIterator, aliased, effects);
}
applyEffect(
context,
state,
@@ -1299,6 +1309,22 @@ type InstructionSignature = {
effects: ReadonlyArray<AliasingEffect>;
};
function conditionallyMutateIterator(place: Place): AliasingEffect | null {
if (
!(
isArrayType(place.identifier) ||
isSetType(place.identifier) ||
isMapType(place.identifier)
)
) {
return {
kind: 'MutateTransitiveConditionally',
value: place,
};
}
return null;
}
/**
* Computes an effect signature for the instruction _without_ looking at the inference state,
* and only using the semantics of the instructions and the inferred types. The idea is to make
@@ -1334,6 +1360,10 @@ function computeSignatureForInstruction(
into: lvalue,
});
} else if (element.kind === 'Spread') {
const mutateIterator = conditionallyMutateIterator(element.place);
if (mutateIterator != null) {
effects.push(mutateIterator);
}
effects.push({
kind: 'Capture',
from: element.place,
@@ -1567,7 +1597,7 @@ function computeSignatureForInstruction(
// Extracts part of the original collection into the result
effects.push({
kind: 'CreateFrom',
from: value.iterator,
from: value.collection,
into: lvalue,
});
break;
@@ -1623,11 +1653,20 @@ function computeSignatureForInstruction(
}
case 'Destructure': {
for (const patternLValue of eachInstructionValueLValue(value)) {
effects.push({
kind: 'CreateFrom',
from: value.value,
into: patternLValue,
});
if (isPrimitiveType(patternLValue.identifier)) {
effects.push({
kind: 'Create',
into: patternLValue,
value: ValueKind.Primitive,
reason: ValueReason.Other,
});
} else {
effects.push({
kind: 'CreateFrom',
from: value.value,
into: patternLValue,
});
}
}
effects.push({kind: 'Assign', from: value.value, into: lvalue});
break;
@@ -1947,17 +1986,11 @@ function computeEffectsForLegacySignature(
continue;
}
const place = arg.kind === 'Identifier' ? arg : arg.place;
const effect =
const signatureEffect =
arg.kind === 'Identifier' && i < signature.positionalParams.length
? signature.positionalParams[i]!
: (signature.restParam ?? Effect.ConditionallyMutate);
if (arg.kind === 'Spread' && effect === Effect.Freeze) {
CompilerError.throwTodo({
reason: 'Support spread syntax for hook arguments',
loc: arg.place.loc,
});
}
const effect = getArgumentEffect(signatureEffect, arg);
visit(place, effect);
}
@@ -121,7 +121,12 @@ export function inferMutationAliasingRanges(
});
} else if (effect.kind === 'CreateFrom') {
state.createFrom(index++, effect.from, effect.into);
} else if (effect.kind === 'Assign' || effect.kind === 'Alias') {
} else if (effect.kind === 'Assign') {
if (!state.nodes.has(effect.into.identifier)) {
state.create(effect.into, {kind: 'Object'});
}
state.assign(index++, effect.from, effect.into);
} else if (effect.kind === 'Alias') {
state.assign(index++, effect.from, effect.into);
} else if (effect.kind === 'Capture') {
state.capture(index++, effect.from, effect.into);
@@ -204,8 +209,8 @@ export function inferMutationAliasingRanges(
errors,
);
}
if (VERBOSE) {
console.log(state.debug());
if (DEBUG) {
console.log(pretty([...state.nodes.keys()]));
}
fn.aliasingEffects ??= [];
for (const param of [...fn.context, ...fn.params]) {
@@ -534,6 +539,11 @@ class AliasingState {
loc: SourceLocation,
errors: CompilerError,
): void {
if (DEBUG) {
console.log(
`mutate ix=${index} start=$${start.id} end=[${end}]${transitive ? ' transitive' : ''} kind=${kind}`,
);
}
const seen = new Set<Identifier>();
const queue: Array<{
place: Identifier;
@@ -557,7 +567,7 @@ class AliasingState {
}
if (DEBUG) {
console.log(
`[${end}] mutate index=${index} ${printIdentifier(start)}: ${printIdentifier(node.id)}`,
` mutate $${node.id.id} transitive=${transitive} direction=${direction}`,
);
}
node.id.mutableRange.end = makeInstructionId(
@@ -1984,7 +1984,7 @@ function areArgumentsImmutableAndNonMutating(
return true;
}
function getArgumentEffect(
export function getArgumentEffect(
signatureEffect: Effect | null,
arg: Place | SpreadPattern,
): Effect {
@@ -131,7 +131,10 @@ export function validateNoFreezingKnownMutableFunctions(
lvalue.identifier.id,
knownMutation,
);
} else if (context.has(effect.value.identifier.id)) {
} else if (
context.has(effect.value.identifier.id) &&
!isRefOrRefLikeMutableType(effect.value.identifier.type)
) {
contextMutationEffects.set(lvalue.identifier.id, {
kind: 'ContextMutation',
effect: Effect.Mutate,
@@ -175,21 +175,14 @@ import {
* and mutability.
*/
function Component(t0) {
const $ = _c(4);
const $ = _c(2);
const { prop } = t0;
let t1;
if ($[0] !== prop) {
const obj = shallowCopy(prop);
const aliasedObj = identity(obj);
let t2;
if ($[2] !== obj) {
t2 = [obj.id];
$[2] = obj;
$[3] = t2;
} else {
t2 = $[3];
}
const id = t2;
const id = [obj.id];
mutate(aliasedObj);
setPropertyByKey(aliasedObj, "id", prop.id + 1);
@@ -23,18 +23,34 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function Component(props) {
const $ = _c(2);
const $ = _c(6);
let t0;
if ($[0] !== props.a) {
const item = { a: props.a };
const items = [item];
t0 = items.map(_temp);
t0 = { a: props.a };
$[0] = props.a;
$[1] = t0;
} else {
t0 = $[1];
}
const mapped = t0;
const item = t0;
let t1;
if ($[2] !== item) {
t1 = [item];
$[2] = item;
$[3] = t1;
} else {
t1 = $[3];
}
const items = t1;
let t2;
if ($[4] !== items) {
t2 = items.map(_temp);
$[4] = items;
$[5] = t2;
} else {
t2 = $[5];
}
const mapped = t2;
return mapped;
}
function _temp(item_0) {
@@ -84,19 +84,11 @@ import { makeArray, mutate } from "shared-runtime";
* used when we analyze CallExpressions.
*/
function Component(t0) {
const $ = _c(5);
const $ = _c(3);
const { foo, bar } = t0;
let t1;
if ($[0] !== foo) {
t1 = { foo };
$[0] = foo;
$[1] = t1;
} else {
t1 = $[1];
}
const x = t1;
let y;
if ($[2] !== bar || $[3] !== x) {
if ($[0] !== bar || $[1] !== foo) {
const x = { foo };
y = { bar };
const f0 = function () {
const a = makeArray(y);
@@ -107,11 +99,11 @@ function Component(t0) {
f0();
mutate(y.x);
$[2] = bar;
$[3] = x;
$[4] = y;
$[0] = bar;
$[1] = foo;
$[2] = y;
} else {
y = $[4];
y = $[2];
}
return y;
}
@@ -38,7 +38,6 @@ export const FIXTURE_ENTRYPOINT = {
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { CONST_TRUE, Stringify, mutate, useIdentity } from "shared-runtime";
/**
@@ -54,31 +53,14 @@ import { CONST_TRUE, Stringify, mutate, useIdentity } from "shared-runtime";
*
*/
function Component() {
const $ = _c(4);
const obj = CONST_TRUE ? { inner: { value: "hello" } } : null;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [obj?.inner];
$[0] = t0;
} else {
t0 = $[0];
}
const boxedInner = t0;
const boxedInner = [obj?.inner];
useIdentity(null);
mutate(obj);
if (boxedInner[0] !== obj?.inner) {
throw new Error("invariant broken");
}
let t1;
if ($[1] !== boxedInner || $[2] !== obj) {
t1 = <Stringify obj={obj} inner={boxedInner} />;
$[1] = boxedInner;
$[2] = obj;
$[3] = t1;
} else {
t1 = $[3];
}
return t1;
return <Stringify obj={obj} inner={boxedInner} />;
}
export const FIXTURE_ENTRYPOINT = {
@@ -56,47 +56,21 @@ import { identity, mutate } from "shared-runtime";
* [{"[object Object]":[42]},{"wat0":"joe","wat1":"joe","wat2":"joe"}]
*/
function Component(props) {
const $ = _c(8);
let key;
const $ = _c(2);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
key = {};
t0 = (mutate(key), key);
$[0] = key;
if ($[0] !== props.value) {
const key = {};
const tmp = (mutate(key), key);
const context = { [tmp]: identity([props.value]) };
mutate(key);
t0 = [context, key];
$[0] = props.value;
$[1] = t0;
} else {
key = $[0];
t0 = $[1];
}
const tmp = t0;
let t1;
if ($[2] !== props.value) {
t1 = identity([props.value]);
$[2] = props.value;
$[3] = t1;
} else {
t1 = $[3];
}
let t2;
if ($[4] !== t1) {
t2 = { [tmp]: t1 };
$[4] = t1;
$[5] = t2;
} else {
t2 = $[5];
}
const context = t2;
mutate(key);
let t3;
if ($[6] !== context) {
t3 = [context, key];
$[6] = context;
$[7] = t3;
} else {
t3 = $[7];
}
return t3;
return t0;
}
export const FIXTURE_ENTRYPOINT = {
@@ -66,88 +66,54 @@ const Codes = {
};
function Component(a) {
const $ = _c(13);
const $ = _c(4);
let keys;
let t0;
let t1;
if ($[0] !== a) {
t1 = Symbol.for("react.early_return_sentinel");
bb0: {
if (a) {
keys = Object.keys(Codes);
} else {
t1 = null;
break bb0;
}
t0 = keys.map(_temp);
if (a) {
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = Object.keys(Codes);
$[0] = t0;
} else {
t0 = $[0];
}
$[0] = a;
keys = t0;
} else {
return null;
}
let t0;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t0 = keys.map(_temp);
$[1] = t0;
$[2] = t1;
$[3] = keys;
} else {
t0 = $[1];
t1 = $[2];
keys = $[3];
}
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
const options = t0;
let t1;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (
<ValidateMemoization inputs={[]} output={keys} onlyCheckCompiled={true} />
);
$[2] = t1;
} else {
t1 = $[2];
}
let t2;
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
t2 = [];
$[4] = t2;
} else {
t2 = $[4];
}
let t3;
if ($[5] !== keys) {
t3 = (
<ValidateMemoization inputs={t2} output={keys} onlyCheckCompiled={true} />
);
$[5] = keys;
$[6] = t3;
} else {
t3 = $[6];
}
let t4;
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
t4 = [];
$[7] = t4;
} else {
t4 = $[7];
}
let t5;
if ($[8] !== options) {
t5 = (
<ValidateMemoization
inputs={t4}
output={options}
onlyCheckCompiled={true}
/>
);
$[8] = options;
$[9] = t5;
} else {
t5 = $[9];
}
let t6;
if ($[10] !== t3 || $[11] !== t5) {
t6 = (
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t2 = (
<>
{t3}
{t5}
{t1}
<ValidateMemoization
inputs={[]}
output={options}
onlyCheckCompiled={true}
/>
</>
);
$[10] = t3;
$[11] = t5;
$[12] = t6;
$[3] = t2;
} else {
t6 = $[12];
t2 = $[3];
}
return t6;
return t2;
}
function _temp(code) {
const country = Codes[code];
@@ -25,17 +25,25 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function bar(a) {
const $ = _c(2);
let y;
const $ = _c(4);
let t0;
if ($[0] !== a) {
const x = [a];
t0 = [a];
$[0] = a;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let y;
if ($[2] !== x[0][1]) {
y = {};
y = x[0][1];
$[0] = a;
$[1] = y;
$[2] = x[0][1];
$[3] = y;
} else {
y = $[1];
y = $[3];
}
return y;
}
@@ -29,20 +29,29 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function bar(a, b) {
const $ = _c(3);
let y;
const $ = _c(6);
let t0;
if ($[0] !== a || $[1] !== b) {
const x = [a, b];
t0 = [a, b];
$[0] = a;
$[1] = b;
$[2] = t0;
} else {
t0 = $[2];
}
const x = t0;
let y;
if ($[3] !== x[0][1] || $[4] !== x[1][0]) {
y = {};
let t = {};
y = x[0][1];
t = x[1][0];
$[0] = a;
$[1] = b;
$[2] = y;
$[3] = x[0][1];
$[4] = x[1][0];
$[5] = y;
} else {
y = $[2];
y = $[5];
}
return y;
}
@@ -25,17 +25,25 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function bar(a) {
const $ = _c(2);
let y;
const $ = _c(4);
let t0;
if ($[0] !== a) {
const x = [a];
t0 = [a];
$[0] = a;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let y;
if ($[2] !== x[0].a[1]) {
y = {};
y = x[0].a[1];
$[0] = a;
$[1] = y;
$[2] = x[0].a[1];
$[3] = y;
} else {
y = $[1];
y = $[3];
}
return y;
}
@@ -24,17 +24,25 @@ export const FIXTURE_ENTRYPOINT = {
```javascript
import { c as _c } from "react/compiler-runtime";
function bar(a) {
const $ = _c(2);
let y;
const $ = _c(4);
let t0;
if ($[0] !== a) {
const x = [a];
t0 = [a];
$[0] = a;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let y;
if ($[2] !== x[0]) {
y = {};
y = x[0];
$[0] = a;
$[1] = y;
$[2] = x[0];
$[3] = y;
} else {
y = $[1];
y = $[3];
}
return y;
}
@@ -27,7 +27,7 @@ function SomeComponent() {
9 | return (
10 | <Button
> 11 | onPress={() => (sharedVal.value = Math.random())}
| ^^^^^^^^^ InvalidReact: Mutating a value returned from a function whose return value should not be mutated. Found mutation of `sharedVal` (11:11)
| ^^^^^^^^^ InvalidReact: This mutates a variable that React considers immutable. Found mutation of `sharedVal` (11:11)
12 | title="Randomize"
13 | />
14 | );
@@ -15,16 +15,11 @@ function useFoo() {
## Error
```
1 | function useFoo() {
2 | let x = 0;
> 3 | return value => {
| ^^^^^^^^^^
3 | return value => {
> 4 | x = value;
| ^^^^^^^^^^^^^^
> 5 | };
| ^^^^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (3:5)
InvalidReact: The function modifies a local variable here (4:4)
| ^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `x` cannot be reassigned after render (4:4)
5 | };
6 | }
7 |
```
@@ -47,19 +47,13 @@ function Component() {
## Error
```
31 | };
32 |
> 33 | useEffect(() => {
| ^^^^^^^
> 34 | onMount();
| ^^^^^^^^^^^^^^
> 35 | }, [onMount]);
| ^^^^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (33:35)
InvalidReact: The function modifies a local variable here (7:7)
36 |
37 | return 'ok';
38 | }
5 |
6 | const reassignLocal = newValue => {
> 7 | local = newValue;
| ^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `local` cannot be reassigned after render (7:7)
8 | };
9 |
10 | const onMount = newValue => {
```
@@ -48,19 +48,13 @@ function Component() {
## Error
```
32 | };
33 |
> 34 | useIdentity(() => {
| ^^^^^^^
> 35 | callback();
| ^^^^^^^^^^^^^^^
> 36 | });
| ^^^^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (34:36)
InvalidReact: The function modifies a local variable here (8:8)
37 |
38 | return 'ok';
39 | }
6 |
7 | const reassignLocal = newValue => {
> 8 | local = newValue;
| ^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `local` cannot be reassigned after render (8:8)
9 | };
10 |
11 | const callback = newValue => {
```
@@ -41,14 +41,13 @@ function Component() {
## Error
```
29 | };
30 |
> 31 | return <button onClick={onClick}>Submit</button>;
| ^^^^^^^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (31:31)
InvalidReact: The function modifies a local variable here (5:5)
32 | }
33 |
3 |
4 | const reassignLocal = newValue => {
> 5 | local = newValue;
| ^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `local` cannot be reassigned after render (5:5)
6 | };
7 |
8 | const onClick = newValue => {
```
@@ -32,15 +32,13 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
15 | b.push(false);
16 | };
> 17 | return <div onClick={f} />;
| ^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (17:17)
InvalidReact: The function modifies a local variable here (8:8)
18 | }
19 |
20 | export const FIXTURE_ENTRYPOINT = {
6 | const f = () => {
7 | if (cond) {
> 8 | a = {};
| ^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `a` cannot be reassigned after render (8:8)
9 | b = [];
10 | } else {
11 | a = {};
```
@@ -17,14 +17,13 @@ function Component() {
## Error
```
1 | function Component() {
2 | let callback = () => {
> 3 | onClick = () => {};
| ^^^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `onClick` cannot be reassigned after render (3:3)
4 | };
5 | let onClick;
6 |
> 7 | return <div onClick={callback} />;
| ^^^^^^^^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (7:7)
InvalidReact: The function modifies a local variable here (3:3)
8 | }
9 |
```
@@ -1,77 +0,0 @@
## Input
```javascript
import {useEffect, useState} from 'react';
import {Stringify} from 'shared-runtime';
function Foo() {
/**
* Previously, this lowered to
* $1 = LoadContext capture setState
* $2 = FunctionExpression deps=$1 context=setState
* [[ at this point, we freeze the `LoadContext setState` instruction, but it will never be referenced again ]]
*
* Now, this function expression directly references `setState`, which freezes
* the source `DeclareContext HoistedConst setState`. Freezing source identifiers
* (instead of the one level removed `LoadContext`) is more semantically correct
* for everything *other* than hoisted context declarations.
*
* $2 = Function context=setState
*/
useEffect(() => setState(2), []);
const [state, setState] = useState(0);
return <Stringify state={state} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}],
sequentialRenders: [{}, {}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useEffect, useState } from "react";
import { Stringify } from "shared-runtime";
function Foo() {
const $ = _c(3);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = [];
$[0] = t0;
} else {
t0 = $[0];
}
useEffect(() => setState(2), t0);
const [state, t1] = useState(0);
const setState = t1;
let t2;
if ($[1] !== state) {
t2 = <Stringify state={state} />;
$[1] = state;
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Foo,
params: [{}],
sequentialRenders: [{}, {}],
};
```
### Eval output
(kind: ok) <div>{"state":2}</div>
<div>{"state":2}</div>
@@ -34,22 +34,28 @@ import { print } from "shared-runtime";
* setState types are not enough to determine to omit from deps. Must also take reactivity into account.
*/
function ReactiveRefInEffect(props) {
const $ = _c(2);
const $ = _c(4);
const [, setState1] = useRef("initial value");
const [, setState2] = useRef("initial value");
let setState;
if (props.foo) {
setState = setState1;
if ($[0] !== props.foo) {
if (props.foo) {
setState = setState1;
} else {
setState = setState2;
}
$[0] = props.foo;
$[1] = setState;
} else {
setState = setState2;
setState = $[1];
}
let t0;
if ($[0] !== setState) {
if ($[2] !== setState) {
t0 = () => print(setState);
$[0] = setState;
$[1] = t0;
$[2] = setState;
$[3] = t0;
} else {
t0 = $[1];
t0 = $[3];
}
useEffect(t0, [setState]);
}
@@ -57,62 +57,67 @@ import { Stringify } from "shared-runtime";
* - cb1 is not assumed to be called since it's only used as a call operand
*/
function useFoo(t0) {
const $ = _c(13);
const { arr1, arr2 } = t0;
const $ = _c(14);
let arr1;
let arr2;
let t1;
if ($[0] !== arr1[0]) {
t1 = (e) => arr1[0].value + e.value;
$[0] = arr1[0];
$[1] = t1;
if ($[0] !== t0) {
({ arr1, arr2 } = t0);
let t2;
if ($[4] !== arr1[0]) {
t2 = (e) => arr1[0].value + e.value;
$[4] = arr1[0];
$[5] = t2;
} else {
t2 = $[5];
}
const cb1 = t2;
t1 = () => arr1.map(cb1);
$[0] = t0;
$[1] = arr1;
$[2] = arr2;
$[3] = t1;
} else {
t1 = $[1];
arr1 = $[1];
arr2 = $[2];
t1 = $[3];
}
const cb1 = t1;
const getArrMap1 = t1;
let t2;
if ($[2] !== arr1 || $[3] !== cb1) {
t2 = () => arr1.map(cb1);
$[2] = arr1;
$[3] = cb1;
$[4] = t2;
if ($[6] !== arr2) {
t2 = (e_0) => arr2[0].value + e_0.value;
$[6] = arr2;
$[7] = t2;
} else {
t2 = $[4];
t2 = $[7];
}
const getArrMap1 = t2;
const cb2 = t2;
let t3;
if ($[5] !== arr2) {
t3 = (e_0) => arr2[0].value + e_0.value;
$[5] = arr2;
$[6] = t3;
if ($[8] !== arr1 || $[9] !== cb2) {
t3 = () => arr1.map(cb2);
$[8] = arr1;
$[9] = cb2;
$[10] = t3;
} else {
t3 = $[6];
t3 = $[10];
}
const cb2 = t3;
const getArrMap2 = t3;
let t4;
if ($[7] !== arr1 || $[8] !== cb2) {
t4 = () => arr1.map(cb2);
$[7] = arr1;
$[8] = cb2;
$[9] = t4;
} else {
t4 = $[9];
}
const getArrMap2 = t4;
let t5;
if ($[10] !== getArrMap1 || $[11] !== getArrMap2) {
t5 = (
if ($[11] !== getArrMap1 || $[12] !== getArrMap2) {
t4 = (
<Stringify
getArrMap1={getArrMap1}
getArrMap2={getArrMap2}
shouldInvokeFns={true}
/>
);
$[10] = getArrMap1;
$[11] = getArrMap2;
$[12] = t5;
$[11] = getArrMap1;
$[12] = getArrMap2;
$[13] = t4;
} else {
t5 = $[12];
t4 = $[13];
}
return t5;
return t4;
}
export const FIXTURE_ENTRYPOINT = {
@@ -1,129 +0,0 @@
## Input
```javascript
import {Stringify, useIdentity} from 'shared-runtime';
function Component({prop1, prop2}) {
'use memo';
const data = useIdentity(
new Map([
[0, 'value0'],
[1, 'value1'],
])
);
let i = 0;
const items = [];
items.push(
<Stringify
key={i}
onClick={() => data.get(i) + prop1}
shouldInvokeFns={true}
/>
);
i = i + 1;
items.push(
<Stringify
key={i}
onClick={() => data.get(i) + prop2}
shouldInvokeFns={true}
/>
);
return <>{items}</>;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{prop1: 'prop1', prop2: 'prop2'}],
sequentialRenders: [
{prop1: 'prop1', prop2: 'prop2'},
{prop1: 'prop1', prop2: 'prop2'},
{prop1: 'changed', prop2: 'prop2'},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { Stringify, useIdentity } from "shared-runtime";
function Component(t0) {
"use memo";
const $ = _c(12);
const { prop1, prop2 } = t0;
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = new Map([
[0, "value0"],
[1, "value1"],
]);
$[0] = t1;
} else {
t1 = $[0];
}
const data = useIdentity(t1);
let t2;
if ($[1] !== data || $[2] !== prop1 || $[3] !== prop2) {
let i = 0;
const items = [];
items.push(
<Stringify
key={i}
onClick={() => data.get(i) + prop1}
shouldInvokeFns={true}
/>,
);
i = i + 1;
const t3 = i;
let t4;
if ($[5] !== data || $[6] !== i || $[7] !== prop2) {
t4 = () => data.get(i) + prop2;
$[5] = data;
$[6] = i;
$[7] = prop2;
$[8] = t4;
} else {
t4 = $[8];
}
let t5;
if ($[9] !== t3 || $[10] !== t4) {
t5 = <Stringify key={t3} onClick={t4} shouldInvokeFns={true} />;
$[9] = t3;
$[10] = t4;
$[11] = t5;
} else {
t5 = $[11];
}
items.push(t5);
t2 = <>{items}</>;
$[1] = data;
$[2] = prop1;
$[3] = prop2;
$[4] = t2;
} else {
t2 = $[4];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ prop1: "prop1", prop2: "prop2" }],
sequentialRenders: [
{ prop1: "prop1", prop2: "prop2" },
{ prop1: "prop1", prop2: "prop2" },
{ prop1: "changed", prop2: "prop2" },
],
};
```
### Eval output
(kind: ok) <div>{"onClick":{"kind":"Function","result":"value1prop1"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1prop2"},"shouldInvokeFns":true}</div>
<div>{"onClick":{"kind":"Function","result":"value1prop1"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1prop2"},"shouldInvokeFns":true}</div>
<div>{"onClick":{"kind":"Function","result":"value1changed"},"shouldInvokeFns":true}</div><div>{"onClick":{"kind":"Function","result":"value1prop2"},"shouldInvokeFns":true}</div>
@@ -41,14 +41,13 @@ function Component() {
## Error
```
29 | };
30 |
> 31 | return <button onClick={onClick}>Submit</button>;
| ^^^^^^^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (31:31)
InvalidReact: The function modifies a local variable here (5:5)
32 | }
33 |
3 |
4 | const reassignLocal = newValue => {
> 5 | local = newValue;
| ^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `local` cannot be reassigned after render (5:5)
6 | };
7 |
8 | const onClick = newValue => {
```
@@ -40,39 +40,46 @@ import { useCallback } from "react";
import { Stringify } from "shared-runtime";
function Foo(t0) {
const $ = _c(8);
const $ = _c(10);
const { arr1, arr2, foo } = t0;
let getVal1;
let t1;
if ($[0] !== arr1 || $[1] !== arr2 || $[2] !== foo) {
const x = [arr1];
if ($[0] !== arr1) {
t1 = [arr1];
$[0] = arr1;
$[1] = t1;
} else {
t1 = $[1];
}
const x = t1;
let getVal1;
let t2;
if ($[2] !== arr2 || $[3] !== foo || $[4] !== x) {
let y = [];
getVal1 = _temp;
t1 = () => [y];
t2 = () => [y];
foo ? (y = x.concat(arr2)) : y;
$[0] = arr1;
$[1] = arr2;
$[2] = foo;
$[3] = getVal1;
$[4] = t1;
} else {
getVal1 = $[3];
t1 = $[4];
}
const getVal2 = t1;
let t2;
if ($[5] !== getVal1 || $[6] !== getVal2) {
t2 = <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
$[2] = arr2;
$[3] = foo;
$[4] = x;
$[5] = getVal1;
$[6] = getVal2;
$[7] = t2;
$[6] = t2;
} else {
t2 = $[7];
getVal1 = $[5];
t2 = $[6];
}
return t2;
const getVal2 = t2;
let t3;
if ($[7] !== getVal1 || $[8] !== getVal2) {
t3 = <Stringify val1={getVal1} val2={getVal2} shouldInvokeFns={true} />;
$[7] = getVal1;
$[8] = getVal2;
$[9] = t3;
} else {
t3 = $[9];
}
return t3;
}
function _temp() {
return { x: 2 };
@@ -36,31 +36,38 @@ import { Stringify } from "shared-runtime";
// We currently produce invalid output (incorrect scoping for `y` declaration)
function useFoo(arr1, arr2) {
const $ = _c(5);
const $ = _c(7);
let t0;
if ($[0] !== arr1 || $[1] !== arr2) {
const x = [arr1];
if ($[0] !== arr1) {
t0 = [arr1];
$[0] = arr1;
$[1] = t0;
} else {
t0 = $[1];
}
const x = t0;
let t1;
if ($[2] !== arr2 || $[3] !== x) {
let y;
t0 = () => ({ y });
t1 = () => ({ y });
(y = x.concat(arr2)), y;
$[0] = arr1;
$[1] = arr2;
$[2] = t0;
} else {
t0 = $[2];
}
const getVal = t0;
let t1;
if ($[3] !== getVal) {
t1 = <Stringify getVal={getVal} shouldInvokeFns={true} />;
$[3] = getVal;
$[2] = arr2;
$[3] = x;
$[4] = t1;
} else {
t1 = $[4];
}
return t1;
const getVal = t1;
let t2;
if ($[5] !== getVal) {
t2 = <Stringify getVal={getVal} shouldInvokeFns={true} />;
$[5] = getVal;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
export const FIXTURE_ENTRYPOINT = {
@@ -30,29 +30,36 @@ import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
function useFoo(arr1, arr2) {
const $ = _c(5);
let y;
if ($[0] !== arr1 || $[1] !== arr2) {
const x = [arr1];
(y = x.concat(arr2)), y;
$[0] = arr1;
$[1] = arr2;
$[2] = y;
} else {
y = $[2];
}
const $ = _c(7);
let t0;
let t1;
if ($[3] !== y) {
t1 = { y };
$[3] = y;
$[4] = t1;
if ($[0] !== arr1) {
t0 = [arr1];
$[0] = arr1;
$[1] = t0;
} else {
t1 = $[4];
t0 = $[1];
}
t0 = t1;
return t0;
const x = t0;
let y;
if ($[2] !== arr2 || $[3] !== x) {
(y = x.concat(arr2)), y;
$[2] = arr2;
$[3] = x;
$[4] = y;
} else {
y = $[4];
}
let t1;
let t2;
if ($[5] !== y) {
t2 = { y };
$[5] = y;
$[6] = t2;
} else {
t2 = $[6];
}
t1 = t2;
return t1;
}
export const FIXTURE_ENTRYPOINT = {
@@ -1,77 +0,0 @@
## Input
```javascript
// @flow
/**
* This hook returns a function that when called with an input object,
* will return the result of mapping that input with the supplied map
* function. Results are cached, so if the same input is passed again,
* the same output object will be returned.
*
* Note that this technically violates the rules of React and is unsafe:
* hooks must return immutable objects and be pure, and a function which
* captures and mutates a value when called is inherently not pure.
*
* However, in this case it is technically safe _if_ the mapping function
* is pure *and* the resulting objects are never modified. This is because
* the function only caches: the result of `returnedFunction(someInput)`
* strictly depends on `returnedFunction` and `someInput`, and cannot
* otherwise change over time.
*/
hook useMemoMap<TInput: interface {}, TOutput>(
map: TInput => TOutput
): TInput => TOutput {
return useMemo(() => {
// The original issue is that `cache` was not memoized together with the returned
// function. This was because neither appears to ever be mutated — the function
// is known to mutate `cache` but the function isn't called.
//
// The fix is to detect cases like this — functions that are mutable but not called -
// and ensure that their mutable captures are aliased together into the same scope.
const cache = new WeakMap<TInput, TOutput>();
return input => {
let output = cache.get(input);
if (output == null) {
output = map(input);
cache.set(input, output);
}
return output;
};
}, [map]);
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function useMemoMap(map) {
const $ = _c(2);
let t0;
let t1;
if ($[0] !== map) {
const cache = new WeakMap();
t1 = (input) => {
let output = cache.get(input);
if (output == null) {
output = map(input);
cache.set(input, output);
}
return output;
};
$[0] = map;
$[1] = t1;
} else {
t1 = $[1];
}
t0 = t1;
return t0;
}
```
### Eval output
(kind: exception) Fixture not implemented
@@ -30,47 +30,60 @@ import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire
import { fire } from "react";
function Component(t0) {
const $ = _c(9);
const { bar, baz } = t0;
let t1;
if ($[0] !== bar) {
t1 = () => {
console.log(bar);
};
$[0] = bar;
$[1] = t1;
const $ = _c(13);
let bar;
let baz;
let foo;
if ($[0] !== t0) {
({ bar, baz } = t0);
let t1;
if ($[4] !== bar) {
t1 = () => {
console.log(bar);
};
$[4] = bar;
$[5] = t1;
} else {
t1 = $[5];
}
foo = t1;
$[0] = t0;
$[1] = bar;
$[2] = baz;
$[3] = foo;
} else {
t1 = $[1];
bar = $[1];
baz = $[2];
foo = $[3];
}
const foo = t1;
const t2 = useFire(foo);
const t3 = useFire(baz);
let t4;
if ($[2] !== bar || $[3] !== t2 || $[4] !== t3) {
t4 = () => {
t2(bar);
t3(bar);
};
$[2] = bar;
$[3] = t2;
$[4] = t3;
$[5] = t4;
} else {
t4 = $[5];
}
useEffect(t4);
let t5;
if ($[6] !== bar || $[7] !== t2) {
t5 = () => {
const t1 = useFire(foo);
const t2 = useFire(baz);
let t3;
if ($[6] !== bar || $[7] !== t1 || $[8] !== t2) {
t3 = () => {
t1(bar);
t2(bar);
};
$[6] = bar;
$[7] = t2;
$[8] = t5;
$[7] = t1;
$[8] = t2;
$[9] = t3;
} else {
t5 = $[8];
t3 = $[9];
}
useEffect(t5);
useEffect(t3);
let t4;
if ($[10] !== bar || $[11] !== t1) {
t4 = () => {
t1(bar);
};
$[10] = bar;
$[11] = t1;
$[12] = t4;
} else {
t4 = $[12];
}
useEffect(t4);
return null;
}