[compiler] Examples of invalid code reassigning locals outside of render

ghstack-source-id: c48d0d0b86
Pull Request resolved: https://github.com/facebook/react/pull/30106
This commit is contained in:
Joe Savona
2024-06-26 16:51:15 -07:00
parent 8416ebee38
commit b1d10c8a45
7 changed files with 385 additions and 0 deletions
@@ -0,0 +1,98 @@
## Input
```javascript
import { useEffect } from "react";
function Component() {
let local;
const reassignLocal = (newValue) => {
local = newValue;
};
const onMount = (newValue) => {
reassignLocal("hello");
if (local === newValue) {
// Without React Compiler, `reassignLocal` is freshly created
// on each render, capturing a binding to the latest `local`,
// such that invoking reassignLocal will reassign the same
// binding that we are observing in the if condition, and
// we reach this branch
console.log("`local` was updated!");
} else {
// With React Compiler enabled, `reassignLocal` is only created
// once, capturing a binding to `local` in that render pass.
// Therefore, calling `reassignLocal` will reassign the wrong
// version of `local`, and not update the binding we are checking
// in the if condition.
//
// To protect against this, we disallow reassigning locals from
// functions that escape
throw new Error("`local` not updated!");
}
};
useEffect(() => {
onMount();
}, [onMount]);
return "ok";
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useEffect } from "react";
function Component() {
const $ = _c(4);
let local;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (newValue) => {
local = newValue;
};
$[0] = t0;
} else {
t0 = $[0];
}
const reassignLocal = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (newValue_0) => {
reassignLocal("hello");
if (local === newValue_0) {
console.log("`local` was updated!");
} else {
throw new Error("`local` not updated!");
}
};
$[1] = t1;
} else {
t1 = $[1];
}
const onMount = t1;
let t2;
let t3;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
onMount();
};
t3 = [onMount];
$[2] = t2;
$[3] = t3;
} else {
t2 = $[2];
t3 = $[3];
}
useEffect(t2, t3);
return "ok";
}
```
@@ -0,0 +1,38 @@
import { useEffect } from "react";
function Component() {
let local;
const reassignLocal = (newValue) => {
local = newValue;
};
const onMount = (newValue) => {
reassignLocal("hello");
if (local === newValue) {
// Without React Compiler, `reassignLocal` is freshly created
// on each render, capturing a binding to the latest `local`,
// such that invoking reassignLocal will reassign the same
// binding that we are observing in the if condition, and
// we reach this branch
console.log("`local` was updated!");
} else {
// With React Compiler enabled, `reassignLocal` is only created
// once, capturing a binding to `local` in that render pass.
// Therefore, calling `reassignLocal` will reassign the wrong
// version of `local`, and not update the binding we are checking
// in the if condition.
//
// To protect against this, we disallow reassigning locals from
// functions that escape
throw new Error("`local` not updated!");
}
};
useEffect(() => {
onMount();
}, [onMount]);
return "ok";
}
@@ -0,0 +1,96 @@
## Input
```javascript
import { useEffect } from "react";
import { useIdentity } from "shared-runtime";
function Component() {
let local;
const reassignLocal = (newValue) => {
local = newValue;
};
const callback = (newValue) => {
reassignLocal("hello");
if (local === newValue) {
// Without React Compiler, `reassignLocal` is freshly created
// on each render, capturing a binding to the latest `local`,
// such that invoking reassignLocal will reassign the same
// binding that we are observing in the if condition, and
// we reach this branch
console.log("`local` was updated!");
} else {
// With React Compiler enabled, `reassignLocal` is only created
// once, capturing a binding to `local` in that render pass.
// Therefore, calling `reassignLocal` will reassign the wrong
// version of `local`, and not update the binding we are checking
// in the if condition.
//
// To protect against this, we disallow reassigning locals from
// functions that escape
throw new Error("`local` not updated!");
}
};
useIdentity(() => {
callback();
});
return "ok";
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useEffect } from "react";
import { useIdentity } from "shared-runtime";
function Component() {
const $ = _c(3);
let local;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (newValue) => {
local = newValue;
};
$[0] = t0;
} else {
t0 = $[0];
}
const reassignLocal = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = (newValue_0) => {
reassignLocal("hello");
if (local === newValue_0) {
console.log("`local` was updated!");
} else {
throw new Error("`local` not updated!");
}
};
$[1] = t1;
} else {
t1 = $[1];
}
const callback = t1;
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = () => {
callback();
};
$[2] = t2;
} else {
t2 = $[2];
}
useIdentity(t2);
return "ok";
}
```
@@ -0,0 +1,39 @@
import { useEffect } from "react";
import { useIdentity } from "shared-runtime";
function Component() {
let local;
const reassignLocal = (newValue) => {
local = newValue;
};
const callback = (newValue) => {
reassignLocal("hello");
if (local === newValue) {
// Without React Compiler, `reassignLocal` is freshly created
// on each render, capturing a binding to the latest `local`,
// such that invoking reassignLocal will reassign the same
// binding that we are observing in the if condition, and
// we reach this branch
console.log("`local` was updated!");
} else {
// With React Compiler enabled, `reassignLocal` is only created
// once, capturing a binding to `local` in that render pass.
// Therefore, calling `reassignLocal` will reassign the wrong
// version of `local`, and not update the binding we are checking
// in the if condition.
//
// To protect against this, we disallow reassigning locals from
// functions that escape
throw new Error("`local` not updated!");
}
};
useIdentity(() => {
callback();
});
return "ok";
}
@@ -0,0 +1,77 @@
## Input
```javascript
function Component() {
let local;
const reassignLocal = (newValue) => {
local = newValue;
};
const onClick = (newValue) => {
reassignLocal("hello");
if (local === newValue) {
// Without React Compiler, `reassignLocal` is freshly created
// on each render, capturing a binding to the latest `local`,
// such that invoking reassignLocal will reassign the same
// binding that we are observing in the if condition, and
// we reach this branch
console.log("`local` was updated!");
} else {
// With React Compiler enabled, `reassignLocal` is only created
// once, capturing a binding to `local` in that render pass.
// Therefore, calling `reassignLocal` will reassign the wrong
// version of `local`, and not update the binding we are checking
// in the if condition.
//
// To protect against this, we disallow reassigning locals from
// functions that escape
throw new Error("`local` not updated!");
}
};
return <button onClick={onClick}>Submit</button>;
}
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
function Component() {
const $ = _c(2);
let local;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = (newValue) => {
local = newValue;
};
$[0] = t0;
} else {
t0 = $[0];
}
const reassignLocal = t0;
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
const onClick = (newValue_0) => {
reassignLocal("hello");
if (local === newValue_0) {
console.log("`local` was updated!");
} else {
throw new Error("`local` not updated!");
}
};
t1 = <button onClick={onClick}>Submit</button>;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
```
@@ -0,0 +1,32 @@
function Component() {
let local;
const reassignLocal = (newValue) => {
local = newValue;
};
const onClick = (newValue) => {
reassignLocal("hello");
if (local === newValue) {
// Without React Compiler, `reassignLocal` is freshly created
// on each render, capturing a binding to the latest `local`,
// such that invoking reassignLocal will reassign the same
// binding that we are observing in the if condition, and
// we reach this branch
console.log("`local` was updated!");
} else {
// With React Compiler enabled, `reassignLocal` is only created
// once, capturing a binding to `local` in that render pass.
// Therefore, calling `reassignLocal` will reassign the wrong
// version of `local`, and not update the binding we are checking
// in the if condition.
//
// To protect against this, we disallow reassigning locals from
// functions that escape
throw new Error("`local` not updated!");
}
};
return <button onClick={onClick}>Submit</button>;
}
@@ -483,6 +483,11 @@ const skipFilter = new Set([
"rules-of-hooks/rules-of-hooks-93dc5d5e538a",
"rules-of-hooks/rules-of-hooks-69521d94fa03",
// should error
"todo.invalid-reassign-local-variable-in-jsx-callback",
"todo.invalid-reassign-local-variable-in-hook-argument",
"todo.invalid-reassign-local-variable-in-effect",
// bugs
"bug-invalid-hoisting-functionexpr",
"original-reactive-scopes-fork/bug-nonmutating-capture-in-unsplittable-memo-block",