mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[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:
+98
@@ -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";
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+38
@@ -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";
|
||||
}
|
||||
+96
@@ -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";
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+39
@@ -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";
|
||||
}
|
||||
+77
@@ -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;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
+32
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user