Repository: facebook/react
Pull request: [compiler] Validate against locals being reassigned after render

Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at bottom):
* #30109
* __->__ #30107
* #30106

Adds a pass which validates that local variables are not reassigned by functions which may be called after render. This is a straightforward forward data-flow analysis, where we:
1. Build up a mapping of context variables in the outer component/hook
2. Find ObjectMethod/FunctionExpressions which may reassign those context variables
3. Propagate aliases of those functions via StoreLocal/LoadLocal
4. Disallow passing those functions with a Freeze effect. This includes JSX arguments, hook arguments, hook return types, etc.

Conceptually, a function that reassigns a local is inherently mutable. Frozen functions must be side-effect free, so these two categories are incompatible and we can use the freeze effect to find all instances of where such functions are disallowed rather than special-casing eg hook calls and JSX.

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts
patch: @@ -21,7 +21,7 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void {
   const contextVariables = new Set<IdentifierId>();
   const reassignment = getContextReassignment(fn, contextVariables, false);
   if (reassignment !== null) {
-    CompilerError.throwInvalidJS({
+    CompilerError.throwInvalidReact({
       reason:
         "Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead",
       description:

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md
patch: @@ -18,7 +18,7 @@ function useFoo() {
   2 |   let x = 0;
   3 |   return (value) => {
 > 4 |     x = value;
-    |     ^ InvalidJS: 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)
+    |     ^ 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 |

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md
patch: @@ -50,7 +50,7 @@ function Component() {
    5 |
    6 |   const reassignLocal = (newValue) => {
 >  7 |     local = newValue;
-     |     ^^^^^ InvalidJS: 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)
+     |     ^^^^^ 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) => {

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md
patch: @@ -51,7 +51,7 @@ function Component() {
    6 |
    7 |   const reassignLocal = (newValue) => {
 >  8 |     local = newValue;
-     |     ^^^^^ InvalidJS: 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)
+     |     ^^^^^ 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) => {

filename: compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md
patch: @@ -44,7 +44,7 @@ function Component() {
   3 |
   4 |   const reassignLocal = (newValue) => {
 > 5 |     local = newValue;
-    |     ^^^^^ InvalidJS: 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)
+    |     ^^^^^ 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) => {