Commit Graph
129 Commits
Author SHA1 Message Date
josephsavona 7838606b0c [compiler] Detect known incompatible libraries (#34027)
A few libraries are known to be incompatible with memoization, whether
manually via `useMemo()` or via React Compiler. This puts us in a tricky
situation. On the one hand, we understand that these libraries were
developed prior to our documenting the [Rules of
React](https://react.dev/reference/rules), and their designs were the
result of trying to deliver a great experience for their users and
balance multiple priorities around DX, performance, etc. At the same
time, using these libraries with memoization — and in particular with
automatic memoization via React Compiler — can break apps by causing the
components using these APIs not to update. Concretely, the APIs have in
common that they return a function which returns different values over
time, but where the function itself does not change. Memoizing the
result on the identity of the function will mean that the value never
changes. Developers reasonable interpret this as "React Compiler broke
my code".

Of course, the best solution is to work with developers of these
libraries to address the root cause, and we're doing that. We've
previously discussed this situation with both of the respective
libraries:
* React Hook Form:
https://github.com/react-hook-form/react-hook-form/issues/11910#issuecomment-2135608761
* TanStack Table:
https://github.com/facebook/react/issues/33057#issuecomment-2840600158
and https://github.com/TanStack/table/issues/5567

In the meantime we need to make sure that React Compiler can work out of
the box as much as possible. This means teaching it about popular
libraries that cannot be memoized. We also can't silently skip
compilation, as this confuses users, so we need these error messages to
be visible to users. To that end, this PR adds:

* A flag to mark functions/hooks as incompatible
* Validation against use of such functions
* A default type provider to provide declarations for two
known-incompatible libraries

Note that Mobx is also incompatible, but the `observable()` function is
called outside of the component itself, so the compiler cannot currently
detect it. We may add validation for such APIs in the future.

Again, we really empathize with the developers of these libraries. We've
tried to word the error message non-judgementally, because we get that
it's hard! We're open to feedback about the error message, please let us
know.

DiffTrain build for [4082b0e7d3](https://github.com/facebook/react/commit/4082b0e7d3c042d49ef8987547b923051936956f)
2025-08-28 16:26:41 -07:00
josephsavona 4a8d929631 [compiler] Show a ref name hint when assigning to non-ref in a callback (#34298)
In #34125 I added a hint where if you assign to the .current property of
a frozen object, we suggest naming the variable as `ref` or `-Ref`.
However, the tracking for mutations that assign to .current specifically
wasn't propagated past function expression boundaries, which meant that
the hint only showed up if you mutated the ref in the main body of the
component/hook. That's less likely to happen since most folks know not
to access refs in render. What's more likely is that you'll (correctly)
assign a ref in an effect or callback, but the compiler will throw an
error. By showing a hint in this case we can help people understand the
naming pattern.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34298).
* #34276
* __->__ #34298

DiffTrain build for [8d7b5e4903](https://github.com/facebook/react/commit/8d7b5e490320732f40d9c3aa4590b5b0ae5116f5)
2025-08-27 17:11:33 -07:00
poteto 579b9bf81a [compiler] Emit better error for unsupported syntax this (#34322)
DiffTrain build for [bd5b1b7639](https://github.com/facebook/react/commit/bd5b1b7639b818a3fbd33ce83bf022a6f9f27b55)
2025-08-27 15:04:18 -07:00
poteto 5944537970 [compiler] Validate against component/hook factories (#34305)
Previously, the compiler would incorrectly attempt to compile nested
components/hooks defined inside non-React functions. This would lead to
scope reference errors at runtime because the compiler would optimize
the nested React function without understanding its closure over the
parent function's variables.

This PR adds detection when non-React functions declare components or
hooks, and reports a clear error before compilation. I put this under a
new compiler flag defaulting to false. I'll run a test on this
internally first, but I expect we should be able to just turn it on in
both compiler (so we stop miscompiling) and linter.

Closes #33978

Playground example:
https://react-compiler-playground-git-pr34305-fbopensource.vercel.app/#N4Igzg9grgTgxgUxALhAejQAgAIDcCGANgJYAm+ALggHIQAiAngHb4C2xcRhDAwjApQSkeEVgAcITBEwpgA8jAASECAGswAHSkAPCTAqYAZlCZwKxSZgDmCCgEkmYqBQAU+AJSZgWzJjiSwAwB1GHwxMQQYTABeTBdPaIA+Lx9fPwCDAAt8JlJCBB5sphsYuITk7yY0tPwAOklCnJt4gG5U3wBfNqZ2zH4KWCqAHmJHZ0wGopto4CK8gqmEDsw0RO7O7tT+wcwQsIiYbo6QDqA

DiffTrain build for [b870042915](https://github.com/facebook/react/commit/b870042915c8a7cc1524feb7e0b5cbe7453a7648)
2025-08-27 11:05:15 -07:00
josephsavona 042d0d3b6a [compiler] Infer render helpers for additional validation (#33647)
We currently assume that any functions passes as props may be event
handlers or effect functions, and thus don't check for side effects such
as mutating globals. However, if a prop is a function that returns JSX
that is a sure sign that it's actually a render helper and not an event
handler or effect function. So we now emit a `Render` effect for any
prop that is a JSX-returning function, triggering all of our render
validation.

This required a small fix to InferTypes: we weren't correctly populating
the `return` type of function types during unification. I also improved
the printing of types so we can see the inferred return types.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33647).
* #33643
* #33650
* #33642
* __->__ #33647

DiffTrain build for [33a1095d72](https://github.com/facebook/react/commit/33a1095d724c5ad0a6238e03e04461b6df5e73e2)
2025-08-27 08:51:42 -07:00
josephsavona 57d2c2c347 [compiler] Script to produce markdown of lint rule docs (#34260)
The docs site is in a separate repo, but this gives us a semi-automated
way to update the docs about our lint rules. The script generates
markdown files from the rule definitions which we can then manually
copy/paste into the docs site somewhere. In the future we can automate
this fully.

DiffTrain build for [425ba0ad6d](https://github.com/facebook/react/commit/425ba0ad6d3ebd1779f6a658dcbf5c666d054948)
2025-08-22 10:05:33 -07:00
josephsavonaandMofei Zhang 78e67b01e1 [compiler] Aggregate error reporting, separate eslint rules (#34176)
NOTE: this is a merged version of @mofeiZ's original PR along with my
edits per offline discussion. The description is updated to reflect the
latest approach.

The key problem we're trying to solve with this PR is to allow
developers more control over the compiler's various validations. The
idea is to have a number of rules targeting a specific category of
issues, such as enforcing immutability of props/state/etc or disallowing
access to refs during render. We don't want to have to run the compiler
again for every single rule, though, so @mofeiZ added an LRU cache that
caches the full compilation output of N most recent files. The first
rule to run on a given file will cause it to get cached, and then
subsequent rules can pull from the cache, with each rule filtering down
to its specific category of errors.

For the categories, I went through and assigned a category roughly 1:1
to existing validations, and then used my judgement on some places that
felt distinct enough to warrant a separate error. Every error in the
compiler now has to supply both a severity (for legacy reasons) and a
category (for ESLint). Each category corresponds 1:1 to a ESLint rule
definition, so that the set of rules is automatically populated based on
the defined categories.

Categories include a flag for whether they should be in the recommended
set or not.

Note that as with the original version of this PR, only
eslint-plugin-react-compiler is changed. We still have to update the
main lint rule.

## Test Plan

* Created a sample project using ESLint v9 and verified that the plugin
can be configured correctly and detects errors
* Edited `fixtures/eslint-v9` and introduced errors, verified that the w
latest config changes in that fixture it correctly detects the errors
* In the sample project, confirmed that the LRU caching is correctly
caching compiler output, ie compiling files just once.

Co-authored-by: Mofei Zhang <feifei0@meta.com>

DiffTrain build for [7d29ecbeb2](https://github.com/facebook/react/commit/7d29ecbeb24327fdcd889fe184311bbeb0f04c30)
2025-08-21 15:02:42 -07:00
jackpope 2e51326058 [eslint-plugin-react-hooks][RulesOfHooks] handle React.useEffect in addition to useEffect (#34076)
## Summary

This is a fix for https://github.com/facebook/react/issues/34074

## How did you test this change?

I added tests in the eslint package, and ran `yarn jest`. After adding
the new tests, I have this:

On main | On this branch
-|-
<img width="356" height="88" alt="image"
src="https://github.com/user-attachments/assets/4ae099a1-0156-4032-b2ca-635ebadcaa3f"
/> | <img width="435" height="120" alt="image"
src="https://github.com/user-attachments/assets/b06c04b8-6cec-43de-befa-a8b4dd20500e"
/>

## Changes

- Add tests to check that we are checking both `CallExpression`
(`useEffect(`), and `MemberExpression` (`React.useEffect(`). To do that,
I copied the `getNodeWithoutReactNamespace(` fn from `ExhaustiveDeps.ts`
to `RulesOfHooks.ts`

DiffTrain build for [87a45ae37f](https://github.com/facebook/react/commit/87a45ae37f4014b6df548a5d9b06bad5dc557992)
2025-08-18 06:20:48 -07:00
josephsavona dc2e9147f2 [compiler] Remove now-unused FunctionEffect type (#34029)
The new mutation/aliasing model significantly expands on the idea of
FunctionEffect. The type (and its usage in HIRFunction.effects) was only
necessary for the now-deleted old inference model so we can clean up
this code now.

DiffTrain build for [5063b3283f](https://github.com/facebook/react/commit/5063b3283fcae4bb43756d0d18d32008e3910bea)
2025-08-15 15:33:10 -07:00
josephsavona e69dea88c2 [compiler][wip] Remove old mutation/aliasing implementation (#34028)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34028).
* #34029
* __->__ #34028

DiffTrain build for [eaf6adb127](https://github.com/facebook/react/commit/eaf6adb1277e4cb4f91d1b7f687f773657a5751b)
2025-08-15 15:26:50 -07:00
josephsavona f3edebc918 [compiler] Add support for diagnostic hints (#34126)
Hints are meant as additional information to present to the developer
about an error. The first use-case here is for the suggestion to name
refs with "-Ref" if we encounter a mutation that looks like it might be
a ref. The original error printing used a second error detail which
printed the source code twice, a hint with just extra text is less
noisy.

DiffTrain build for [6ffcac8558](https://github.com/facebook/react/commit/6ffcac8558efbd204c7df5d52787a90e507dd8d7)
2025-08-15 15:15:33 -07:00
josephsavona 9718c76458 [compiler] Add hint to name variables with "Ref" suffix (#34125)
If you have a ref that the compiler doesn't know is a ref (say, a value
returned from a custom hook) and try to assign its `.current = ...`, we
currently fail with a generic error that hook return values are not
mutable. However, an assignment to `.current` specifically is a very
strong hint that the value is likely to be a ref. So in this PR, we
track the reason for the mutation and if it ends up being an error, we
use it to show an additional hint to the user. See the fixture for an
example of the message.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34125).
* #34126
* __->__ #34125
* #34124

DiffTrain build for [724b324b96](https://github.com/facebook/react/commit/724b324b966343f08ff0cb16ec9d8013e3891dfd)
2025-08-15 15:11:43 -07:00
josephsavona 5752f3502e [compiler] remove use of inspect module (#34124)
DiffTrain build for [f468d37739](https://github.com/facebook/react/commit/f468d37739cbce9caff88c3b83aca47b0ea7bdbc)
2025-08-07 00:06:36 -07:00
josephsavona a23648fa0d [compiler] Delete PropagatePhiTypes (#34107)
We moved this logic into InferTypes a long time ago and the PRs to clean
it up keep getting lost in the shuffle.

DiffTrain build for [7deda941f7](https://github.com/facebook/react/commit/7deda941f7f77e82de0311fc3e0cf94d8a863069)
2025-08-04 15:24:38 -07:00
josephsavona df8c8c31d8 [compiler] rebase #32285 (#34102)
Redo of #32285 which was created with ghstack and is tedious to rebase
with sapling.

DiffTrain build for [d3b26b2953](https://github.com/facebook/react/commit/d3b26b2953c7e780abaa49f422c53fd4cda08e47)
2025-08-04 12:10:41 -07:00
josephsavona 952bee4aa2 [compiler] Improve merging of scopes that invalidate together (#34049)
We try to merge consecutive reactive scopes that will always invalidate
together, but there's one common case that isn't handled.

```js
const y = [[x]];
```

Here we'll create two consecutive scopes for the inner and outer array
expressions. Because the input to the second scope is a temporary,
they'll merge into one scope.

But if we name the inner array, the merging stops:

```js
const array = [x];
const y = [array];
```

This is because the merging logic checks if all the dependencies of the
second scope are outputs of the first scope, but doesn't account for
renaming due to LoadLocal/StoreLocal. The fix is to track these
temporaries.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34049).
* __->__ #34049
* #34047
* #34044

DiffTrain build for [ddf8bc3fba](https://github.com/facebook/react/commit/ddf8bc3fbac7aefbf557e2e4a3e14d8de1b80872)
2025-08-01 13:07:00 -07:00
josephsavona c4e079782f [compiler] Add definitions for Object entries/keys/values (#34047)
Fixes remaining issue in #32261, where passing a previously useMemo()-d
value to `Object.entries()` makes the compiler think the value is
mutated and fail validatePreserveExistingMemo. While I was there I added
Object.keys() and Object.values() too.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34047).
* #34049
* __->__ #34047
* #34044

DiffTrain build for [0860b9cc1f](https://github.com/facebook/react/commit/0860b9cc1f4a7188b41204bddc57a127a8bbf6e9)
2025-08-01 13:06:06 -07:00
poteto 12966d2c5f [compiler] Emit more specific error when making identifiers with reserved words (#34080)
This currently throws an invariant which may be misleading. I checked
the ecma262 spec and used the same list of reserved words in our check.
To err on the side of being conservative, we also error when strict mode
reserved words are used.

DiffTrain build for [52612a7cbd](https://github.com/facebook/react/commit/52612a7cbdd8e1fee9599478247f78725869ebad)
2025-08-01 12:18:08 -07:00
josephsavona e918a9db2a Enable ref validation in linter (#34044)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34044).
* #34027
* __->__ #34044

DiffTrain build for [88b40f6e41](https://github.com/facebook/react/commit/88b40f6e41004773f19d37827980c66a8cf1865b)
2025-07-29 12:36:49 -07:00
josephsavona 880a03f99e [compiler] Allow assigning ref-accessing functions to objects if not mutated (#34026)
Allows assigning a ref-accessing function to an object so long as that
object is not subsequently transitively mutated. We should likely
rewrite the ref validation to use the new mutation/aliasing effects,
which would provide a more consistent behavior across instruction types
and require fewer special cases like this.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34026).
* #34027
* __->__ #34026

DiffTrain build for [04a7a61918](https://github.com/facebook/react/commit/04a7a61918522734a083bff37843865d7815d466)
2025-07-29 11:04:45 -07:00
josephsavona e0a3dc5c61 [compiler] disallow ref access in state initializer, reducer/initializer (#34025)
Per title, disallow ref access in `useState()` initializer function,
`useReducer()` reducer, and `useReducer()` init function.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34025).
* #34027
* #34026
* __->__ #34025

DiffTrain build for [c2326b1336](https://github.com/facebook/react/commit/c2326b1336e38a52899fad6c2ddbb71ea7ddd3ee)
2025-07-29 11:03:02 -07:00
josephsavona 7f88cd0cf5 [compiler] treat ref-like identifiers as refs by default (#34005)
`@enableTreatRefLikeIdentifiersAsRefs` is now on by default. I made one
small fix to the render helper logic as part of this, uncovered by
including more tests.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34005).
* #34027
* #34026
* #34025
* #34024
* __->__ #34005

DiffTrain build for [6891dcb87d](https://github.com/facebook/react/commit/6891dcb87db831db17ab109730dea63706875dd7)
2025-07-29 10:58:35 -07:00
josephsavona 5cf31b63ee [compiler] Allow mergeRefs pattern (and detect refs passed as ref prop) (#34004)
Two related changes:
* ValidateNoRefAccessInRender now allows the mergeRefs pattern, ie a
function that aggregates multiple refs into a new ref. This is the main
case where we have seen false positive no-ref-in-render errors.
* Behind `@enableTreatRefLikeIdentifiersAsRefs`, we infer values passed
as the `ref` prop to some JSX as refs.

The second change is potentially helpful for situations such as

```js
function Component({ref: parentRef}) {
  const childRef = useRef(null);
  const mergedRef = mergeRefs(parentRef, childRef);
  useEffect(() => {
    // generally accesses childRef, not mergedRef
  }, []);
  return <Foo ref={mergedRef} />;
}
```

Ie where you create a merged ref but don't access its `.current`
property. Without inferring `ref` props as refs, we'd fail to allow this
merge refs case.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34004).
* #34027
* #34026
* #34025
* #34024
* #34005
* #34006
* __->__ #34004

DiffTrain build for [1d7e942da7](https://github.com/facebook/react/commit/1d7e942da76c794c3cbf52a8c214d90a69814a8f)
2025-07-29 10:12:05 -07:00
josephsavona 266f429f27 [compiler] Improve ref validation error message (#34003)
Improves the error message for ValidateNoRefAccessInRender, using the
new diagnostic type as well as providing a longer but succinct summary
of what refs are for and why they're unsafe to access in render.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34003).
* #34027
* #34026
* #34025
* #34024
* #34005
* #34006
* #34004
* __->__ #34003

DiffTrain build for [79dc706498](https://github.com/facebook/react/commit/79dc706498c4f8ef077167898492693197e1b975)
2025-07-29 10:10:52 -07:00
josephsavona c681973384 [compiler] Fixes to enableTreatRefLikeIdentifiersAsRefs (#34000)
We added the `@enableTreatRefLikeIdentifiersAsRefs` feature a while back
but never enabled it. Since then we've continued to see examples that
motivate this mode, so here we're fixing it up to prepare to enable by
default. It now works as follows:

* If we find a property load or property store where both a) the
object's name is ref-like (`ref` or `-Ref`) and b) the property is
`current`, we infer the object itself as a ref and the value of the
property as a ref value. Originally the feature only detected property
loads, not stores.
* Inferred refs are not considered stable (this is a change from the
original implementation). The only way to get a stable ref is by calling
`useRef()`. We've seen issues with assuming refs are stable.

With this change, cases like the following now correctly error:

```js
function Foo(props) {
  const fooRef = props.fooRef;
  fooRef.current = true;
  ^^^^^^^^^^^^^^ cannot modify ref in render
}
```

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34000).
* #34027
* #34026
* #34025
* #34024
* #34005
* #34006
* #34004
* #34003
* __->__ #34000

DiffTrain build for [85bbe39ef8](https://github.com/facebook/react/commit/85bbe39ef8e24a192b5e9f2987b1babf8ce772e1)
2025-07-29 10:05:12 -07:00
poteto 16f8eb9153 [eslint] Disallow use within try/catch blocks (#34040)
Follow up to #34032. The linter now ensures that `use` cannot be used
within try/catch.

DiffTrain build for [820af20971](https://github.com/facebook/react/commit/820af2097103309fdc5675d2bde744103a439eff)
2025-07-29 09:39:49 -07:00
poteto 88ca7c7ed6 [compiler] Enable validateNoVoidUseMemo in eslint & playground (#34022)
Enables `validateNoVoidUseMemo` by default only in eslint (it defaults
to false otherwise) as well as the playground.

DiffTrain build for [7ee7571212](https://github.com/facebook/react/commit/7ee7571212bc02354b852752a98b23bc90546fdf)
2025-07-28 10:48:35 -07:00
poteto 1abacc2693 [compiler] Aggregate all errors reported from DropManualMemoization (#34002)
Noticed this from my previous PR that this pass was throwing on the
first error. This PR is a small refactor to aggregate every violation
and report them all at once.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34002).
* #34022
* __->__ #34002

DiffTrain build for [6b22f31f1a](https://github.com/facebook/react/commit/6b22f31f1ac88cce1b38c67a5c97c7ab0e832823)
2025-07-28 10:41:53 -07:00
poteto cf7bb43316 [compiler] Add new ValidateNoVoidUseMemo pass (#33990)
Adds a new validation pass to validate against `useMemo`s that don't
return anything. This usually indicates some kind of "useEffect"-like
code that has side effects that need to be memoized to prevent
overfiring, and is an anti-pattern.

A follow up validation could also look at the return value of `useMemo`s
to see if they are being used.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33990).
* #34022
* #34002
* #34001
* __->__ #33990
* #33989

DiffTrain build for [c60eebffea](https://github.com/facebook/react/commit/c60eebffea94a67f35c6ebbf7019e5b2145d4284)
2025-07-28 10:00:02 -07:00
poteto b723ffdb55 [compiler] Disambiguate between void, implicit, and explicit returns (#33989)
Adds a new property to ReturnTerminals to disambiguate whether it was
explicit, implicit (arrow function expressions), or void (where it was
omitted). I will use this property in the next PR adding a new
validation pass.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33989).
* #34022
* #34002
* #34001
* #33990
* __->__ #33989

DiffTrain build for [5dd622eabe](https://github.com/facebook/react/commit/5dd622eabe38e01781b3699d0d81c4a16e302f09)
2025-07-28 09:53:06 -07:00
eps1lon bacebfd0a5 [Runtime] Fix CI (#33999)
DiffTrain build for [19baee813c](https://github.com/facebook/react/commit/19baee813cb08fc5971a3b301814faa6cd530ec8)
2025-07-25 12:11:08 -07:00
josephsavona e671c74a23 [compiler] Enable additional lints by default (#33752)
Enable more validations to help catch bad patterns, but only in the
linter. These rules are already enabled by default in the compiler _if_
violations could produce unsafe output.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33752).
* #33981
* #33777
* #33767
* #33765
* #33760
* #33759
* #33758
* #33751
* __->__ #33752
* #33753

DiffTrain build for [0d39496eab](https://github.com/facebook/react/commit/0d39496eab710b5a3efc07d34c0db3f2475c04f8)
2025-07-24 16:02:56 -07:00
josephsavona 97b9414b9a [compiler] Fix for false positive mutation of destructured spread object (#33786)
When destructuring, spread creates a new mutable object that _captures_
part of the original rvalue. This new value is safe to modify.

When making this change I realized that we weren't inferring array
pattern spread as creating an array (in type inference) so I also added
that here.

DiffTrain build for [448f781a52](https://github.com/facebook/react/commit/448f781a52d62042341d2411d1352d705ce2cbfe)
2025-07-24 15:40:55 -07:00
jbrown215 a25e9a67c2 Change autodeps configuration (#33800)
DiffTrain build for [074e92777c](https://github.com/facebook/react/commit/074e92777c22a56269647d614fdae80bf6406485)
2025-07-21 13:09:36 -07:00
jbrown215 fef10fd68d InferEffectDeps takes a React.AUTODEPS sigil (#33799)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33799).
* #33800
* __->__ #33799

DiffTrain build for [dffacc7b80](https://github.com/facebook/react/commit/dffacc7b8094576c19790fe8341996f743ba4a89)
2025-07-17 05:38:15 -07:00
josephsavona 93f1925740 [compiler] Check TSAsExpression and TSNonNullExpression reorderability (#33788)
## Summary

The `TSAsExpression` and `TSNonNullExpression` nodes are supported by
`lowerExpression()` but `isReorderableExpression()` does not check if
they can be reordered. This PR updates `isReorderableExpression()` to
handle these two node types by adding cases that fall through to the
existing `TypeCastExpression` case.

We ran `react-compiler-healthcheck` at scale on several of our repos and
found dozens of `` (BuildHIR::node.lowerReorderableExpression)
Expression type `TSAsExpression` cannot be safely reordered`` errors and
a handful for `TSNonNullExpression`.

## How did you test this change?

In this case I added two fixture tests

DiffTrain build for [fe813143e2](https://github.com/facebook/react/commit/fe813143e23675963f92d25fa1fb35b39db40272)
2025-07-15 11:57:18 -07:00
jbrown215 af29a26f3d [eslint] Do not allow useEffectEvent fns to be called in arbitrary closures (#33544)
Summary:

useEffectEvent is meant to be used specifically in combination with
useEffect, and using
the feature in arbitrary closures can lead to surprising reactivity
semantics. In order to
minimize risk in the experimental rollout, we are going to restrict its
usage to being
called directly inside an effect or another useEffectEvent, effectively
enforcing the function
coloring statically. Without an effect system this is the best we can
do.

DiffTrain build for [97cdd5d3c3](https://github.com/facebook/react/commit/97cdd5d3c33eda77be4f96a43f72d6916d3badbb)
2025-07-10 13:56:44 -07:00
josephsavona 4ab994be7f [compiler] Add CompilerError.UnsupportedJS variant (#33750)
We use this variant for syntax we intentionally don't support: with
statements, eval, and inline class declarations.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33750).
* #33753
* #33752
* #33751
* __->__ #33750
* #33748

DiffTrain build for [96c61b7f1f](https://github.com/facebook/react/commit/96c61b7f1f145b9fe5103051b636959cdeb20cc8)
2025-07-09 22:30:34 -07:00
josephsavona 0116228a39 [compiler] Errors for eval(), with statments, class declarations (#33746)
* Error for `eval()`
* More specific error message for `with (expr) { ... }` syntax
* More specific error message for class declarations

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33746).
* #33752
* #33751
* #33750
* #33748
* #33747
* __->__ #33746

DiffTrain build for [4a3ff8eed6](https://github.com/facebook/react/commit/4a3ff8eed65f96cda7617150f92de3544d5ddf6a)
2025-07-09 22:24:39 -07:00
josephsavona 80e4d87f06 [compiler] Improve IIFE inlining (#33726)
We currently inline IIFEs by creating a temporary and a labeled block w
the original code. The original return statements turn into an
assignment to the temporary and break out of the label. However, many
cases of IIFEs are due to inlining of manual `useMemo()`, and these
cases often have only a single return statement. Here, the output is
cleaner if we avoid the temporary and label - so that's what we do in
this PR.

Note that the most complex part of the change is actually around
ValidatePreserveExistingMemo - we have some logic to track the IIFE
temporary reassignmetns which needs to be updated to handle the simpler
version of inlining.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33726).
* __->__ #33726
* #33725

DiffTrain build for [956d770adf](https://github.com/facebook/react/commit/956d770adf59e1f8a00a7b7c52b5727ef9e353e7)
2025-07-08 16:43:32 -07:00
sebmarkbageandHendrik Liebau 562aaeb636 [Flight] Log aborted await and component renders (#33641)
<img width="926" alt="Screenshot 2025-06-25 at 1 02 14 PM"
src="https://github.com/user-attachments/assets/1877d13d-5259-4cc4-8f48-12981e3073fe"
/>

The I/O entry doesn't show as aborted in the Server Request track
because technically it wasn't. The end time is just made up. It's still
going. It's not aborted until the abort signal propagates and if we do
get that signal wired up before it emits, it instead would show up as
rejected.

---------

Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de>

DiffTrain build for [bb6c9d521e](https://github.com/facebook/react/commit/bb6c9d521e7f44eb9a9754a14cde62c8e6439e5e)
2025-06-25 13:39:33 -07:00
josephsavona 1796cb9a66 [compiler] Fix bug with reassigning function param in destructuring (#33624)
Closes #33577, a bug with ExtractScopeDeclarationsFromDestructuring and
codegen when a function param is reassigned.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33624).
* #33643
* #33642
* #33640
* #33625
* __->__ #33624

DiffTrain build for [9894c488e0](https://github.com/facebook/react/commit/9894c488e0d9a4d9759d80ba8666d4d094b894e9)
2025-06-25 11:24:29 -07:00
josephsavona 59b07a2562 [compiler] Consolidate HIRFunction return information (#33640)
We now have `HIRFunction.returns: Place` as well as `returnType: Type`.
I want to add additional return information, so as a first step i'm
consolidating everything under an object at `HIRFunction.returns:
{place: Place}`. We use the type of this place as the return type. Next
step is to add more properties to this object to represent things like
the return kind.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33640).
* #33643
* #33642
* __->__ #33640
* #33625
* #33624

DiffTrain build for [123ff13b19](https://github.com/facebook/react/commit/123ff13b193cd361a61b99056dd08f2decd7f55d)
2025-06-25 11:18:30 -07:00
josephsavona ab9cbb66a5 [compiler] Avoid empty switch cases (#33625)
Small cosmetic win, found this when i was looking at some code
internally with lots of cases that all share the same logic. Previously,
all the but last one would have an empty block.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33625).
* #33643
* #33642
* #33640
* __->__ #33625
* #33624

DiffTrain build for [e130c08b06](https://github.com/facebook/react/commit/e130c08b06470b5fc4ec8095310d19e782924427)
2025-06-25 11:16:30 -07:00
josephsavona 57e4b48f33 [compiler] New inference repros/fixes (#33584)
Substantially improves the last major known issue with the new inference
model's implementation: inferring effects of function expressions. I
knowingly used a really simple (dumb) approach in
InferFunctionExpressionAliasingEffects but it worked surprisingly well
on a ton of code. However, investigating during the sync I saw that we
the algorithm was literally running out of memory, or crashing from
arrays that exceeded the maximum capacity. We were accumluating data
flow in a way that could lead to lists of data flow captures compounding
on themselves and growing very large very quickly. Plus, we were
incorrectly recording some data flow, leading to cases where we reported
false positive "can't mutate frozen value" for example.

So I went back to the drawing board. InferMutationAliasingRanges already
builds up a data flow graph which it uses to figure out what values
would be affected by mutations of other values, and update mutable
ranges. Well, the key question that we really want to answer for
inferring a function expression's aliasing effects is which values
alias/capture where. Per the docs I wrote up, we only have to record
such aliasing _if they are observable via mutations_. So, lightbulb:
simulate mutations of the params, free variables, and return of the
function expression and see which params/free-vars would be affected!
That's what we do now, giving us precise information about which such
values alias/capture where. When the "into" is a param/context-var we
use Capture, iwhen the destination is the return we use Alias to be
conservative.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33584).
* #33626
* #33625
* #33624
* __->__ #33584

DiffTrain build for [94cf60bede](https://github.com/facebook/react/commit/94cf60bede7cd6685e07a4374d1e3aa90445130b)
2025-06-24 10:08:41 -07:00
eps1lon 2b1f8fbbf1 Fix CI (#33578)
DiffTrain build for [a947eba4f2](https://github.com/facebook/react/commit/a947eba4f2c8741d2c61a3b33fd79cf13bf9f39d)
2025-06-19 14:50:52 -07:00
sebmarkbage a9140c79a9 Expose cacheSignal() alongside cache() (#33557)
This was really meant to be there from the beginning. A `cache()`:ed
entry has a life time. On the server this ends when the render finishes.
On the client this ends when the cache of that scope gets refreshed.

When a cache is no longer needed, it should be possible to abort any
outstanding network requests or other resources. That's what
`cacheSignal()` gives you. It returns an `AbortSignal` which aborts when
the cache lifetime is done based on the same execution scope as a
`cache()`ed function - i.e. `AsyncLocalStorage` on the server or the
render scope on the client.

```js
import {cacheSignal} from 'react';
async function Component() {
  await fetch(url, { signal: cacheSignal() });
}
```

For `fetch` in particular, a patch should really just do this
automatically for you. But it's useful for other resources like database
connections.

Another reason it's useful to have a `cacheSignal()` is to ignore any
errors that might have triggered from the act of being aborted. This is
just a general useful JavaScript pattern if you have access to a signal:

```js
async function getData(id, signal) {
  try {
     await queryDatabase(id, { signal });
  } catch (x) {
     if (!signal.aborted) {
       logError(x); // only log if it's a real error and not due to cancellation
     }
     return null;
  }
}
```

This just gets you a convenient way to get to it without drilling
through so a more idiomatic code in React might look something like.

```js
import {cacheSignal} from "react";

async function getData(id) {
  try {
     await queryDatabase(id);
  } catch (x) {
     if (!cacheSignal()?.aborted) {
       logError(x);
     }
     return null;
  }
}
```

If it's called outside of a React render, we normally treat any cached
functions as uncached. They're not an error call. They can still load
data. It's just not cached. This is not like an aborted signal because
then you couldn't issue any requests. It's also not like an infinite
abort signal because it's not actually cached forever. Therefore,
`cacheSignal()` returns `null` when called outside of a React render
scope.

Notably the `signal` option passed to `renderToReadableStream` in both
SSR (Fizz) and RSC (Flight Server) is not the same instance that comes
out of `cacheSignal()`. If you abort the `signal` passed in, then the
`cacheSignal()` is also aborted with the same reason. However, the
`cacheSignal()` can also get aborted if the render completes
successfully or fatally errors during render - allowing any outstanding
work that wasn't used to clean up. In the future we might also expand on
this to give different
[`TaskSignal`](https://developer.mozilla.org/en-US/docs/Web/API/TaskSignal)
to different scopes to pass different render or network priorities.

On the client version of `"react"` this exposes a noop (both for
Fiber/Fizz) due to `disableClientCache` flag but it's exposed so that
you can write shared code.

DiffTrain build for [e1dc03492e](https://github.com/facebook/react/commit/e1dc03492eedaec517e14a6e32b8fda571d00767)
2025-06-17 14:10:14 -07:00
jbrown215 7709cf1986 [compiler] Add repro for IIFE in ternary causing a bailout (#33546)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33546).
* #33548
* __->__ #33546

DiffTrain build for [75e78d243f](https://github.com/facebook/react/commit/75e78d243f749d009fa1c5c09c3464301b992718)
2025-06-16 19:05:31 -07:00
jbrown215 99e2e1c37d [compiler] Do not inline IIFEs in value blocks (#33548)
As discussed in chat, this is a simple fix to stop introducing labels
inside expressions.

The useMemo-with-optional test was added in
https://github.com/facebook/react/commit/d70b2c2c4e85c2a7061214c15a8ff13167d10422
and crashes for the same reason- an unexpected label as a value block
terminal.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33548).
* __->__ #33548
* #33546

DiffTrain build for [90bee81902](https://github.com/facebook/react/commit/90bee819028bfecb724df298da798607b6a76abf)
2025-06-16 19:01:04 -07:00
jbrown215 fc1c5a2f2c [compiler] Don't include useEffectEvent values in autodeps (#33450)
Summary: useEffectEvent values are not meant to be added to the dep
array

DiffTrain build for [4df098c4c2](https://github.com/facebook/react/commit/4df098c4c2c51a033592ebc84abc47cc49a6bfb2)
2025-06-09 06:37:31 -07:00