Summary: Using the change detection code to debug codebases that violate the rules of react is a lot easier when we have a source location corresponding to the value that has changed inappropriately. I didn't see an easy way to track that information in the existing data structures at the point of codegen, so this PR adds locations to identifiers and reactive scopes (the location of a reactive scope is the range of the locations of its included identifiers).
I'm interested if there's a better way to do this that I missed!
ghstack-source-id: aed5f7edda
Pull Request resolved: https://github.com/facebook/react/pull/29658
Summary: This PR expands the analysis from the previous in the stack in order to also capture when a value can incorrectly change within a single render, rather than just changing between two renders. In the case where dependencies have changed and so a new value is being computed, we now compute the value twice and compare the results. This would, for example, catch when we call Math.random() in render.
The generated code is a little convoluted, because we don't want to have to traverse the generated code and substitute variable names with new ones. Instead, we save the initial value to the cache as normal, then run the computation block again and compare the resulting values to the cached ones. Then, to make sure that the cached values are identical to the computed ones, we reassign the cached values into the output variables.
ghstack-source-id: d0f11a4cb2
Pull Request resolved: https://github.com/facebook/react/pull/29657
Summary: jmbrown215 recently had an observation that the arguments to useState/useRef are only used when a component renders for the first time, and never afterwards. We can skip more computation that we previously could, with reactive blocks that previously recomputed values when inputs changed now only ever computing them on the first render.
ghstack-source-id: 5d044ef787
Pull Request resolved: https://github.com/facebook/react/pull/29653
Summary: The essential assumption of the compiler is that if the inputs to a computation have not changed, then the output should not change either--computation that the compiler optimizes is idempotent.
This is, of course, known to be false in practice, because this property rests on requirements (the Rules of React) that are loosely enforced at best. When rolling out the compiler to a codebase that might have rules of react violations, how should developers debug any issues that arise?
This diff attempts one approach to that: when the option is set, rather than simply skipping computation when dependencies haven't changed, we will *still perform the computation*, but will then use a runtime function to compare the original value and the resultant value. The runtime function can be customized, but the idea is that it will perform a structural equality check on the values, and if the values aren't structurally equal, we can report an error, including information about what file and what variable was to blame.
This assists in debugging by narrowing down what specific computation is responsible for a difference in behavior between the uncompiled code and the program after compilation.
ghstack-source-id: 50dad3dacf
Pull Request resolved: https://github.com/facebook/react/pull/29656
Summary: This adds a debugging mode to the compiler that simply adds a `|| true` to the guard on all memoization blocks, which results in the generated code never using memoized values and always recomputing them. This is designed as a validation tool for the compiler's correctness--every program *should* behave exactly the same with this option enabled as it would with it disabled, and so any difference in behavior should be investigated as either a compiler bug or a pipeline issue.
(We add `|| true` rather than dropping the conditional block entirely because we still want to exercise the guard tests, in case the guards themselves are the source of an error, like reading a property from undefined in a guard.)
ghstack-source-id: 955a47ec16
Pull Request resolved: https://github.com/facebook/react/pull/29655
Summary: This adds a compiler option to not drop existing manual memoization and leaving useMemo/useCallback in the generated source. Why do we need this, given that we also have options to validate or ensure that existing memoization is preserved? It's because later diffs on this stack are designed to alter the behavior of the memoization that the compiler emits, in order to detect rules of react violations and debug issues. We don't want to change the behavior of user-level memoization, however, since doing so would be altering the semantics of the user's program in an unacceptable way.
ghstack-source-id: 89dccdec9c
Pull Request resolved: https://github.com/facebook/react/pull/29654
## Summary
Resolves#29622
## How did you test this change?
I verified the implementation using the test.
Note:
This PR was done without waiting for approval in #29622, so feel free to
just close it.
We were missing a check that ObjectMethods are not getters or setters. In our experience this is pretty rare within React components and hooks themselves, so let's start with a todo.
Closes#29586
ghstack-source-id: 03c6cce9a9
Pull Request resolved: https://github.com/facebook/react/pull/29592
Fixes https://x.com/raibima/status/1794395807216738792
The issue is that if you pass a global-modifying function as prop to JSX, we currently report that it's invalid to modify a global during rendering. The problem is that we don't really know when/if the child component will actually call that function prop. It would be against the rules to call the function during render, but it's totally fine to call it during an event handler or from a useEffect.
Since we don't know at the call-site how the child will use the function, we should allow such calls. In the future we could improve this in a few ways:
* For all functions that modify globals, codegen an assertion or warning into the function that fires if it's called "during render". We'd have to precisely define what "during render" is, but this would at least help developers catch this dynamically.
* Use the type system to distinguish "event/effect" and "render" functions to help developers avoid accidentally mutating globals during render.
ghstack-source-id: 4aba4e6d21
Pull Request resolved: https://github.com/facebook/react/pull/29591
user's pipeline
When the user app has a babel.config file that is missing the compiler,
strange things happen as babel does some strange merging of options from
the user's config and in various callsites like in our eslint rule and
healthcheck script. To minimize odd behavior, we default to not reading
the user's babel.config
Fixes#29135
ghstack-source-id: d6fdc43c5c
Pull Request resolved: https://github.com/facebook/react/pull/29211
After this is merged, I'll add it to .git-blame-ignore-revs. I can't do
it now as the hash will change after ghstack lands this stack.
ghstack-source-id: 054ca869b7
Pull Request resolved: https://github.com/facebook/react/pull/29214
When I added new builtin types for jsx and functions, i forget to add a shape definition. This meant that attempting to accesss a property or method on these types would cause an internal error with an unresolved shape. That wasn't obvious because we rarely call methods on these types.
I confirmed that the new fixtures here fail without the fix.
ghstack-source-id: aa8f8d75a3
Pull Request resolved: https://github.com/facebook/react/pull/29624
We currently don't report an error if the code attempts to reassign a const. Our thinking has been that we're not trying to catch all possible mistakes you could make in JavaScript — that's what ESLint, TypeScript, and Flow are for — and that we want to focus on React errors. However, accidentally reassigning a const is easy to catch and doesn't get in the way of other analysis so let's implement it.
Note that React Compiler's ESLint plugin won't report these errors by default, but they will show up in playground.
Fixes#29598
ghstack-source-id: a0af8b9a48
Pull Request resolved: https://github.com/facebook/react/pull/29619
Updates Environment#getGlobalDeclaration() to only resolve "globals" if they are a true global or an import from react/react-dom. We still keep the logic to resolve hook-like names as custom hooks. Notably, this means that a local `Array` reference won't get confused with our Array global declaration, a local `useState` (or import from something other than React) won't get confused as `React.useState()`, etc.
I tried to write a proper fixture test to test that we react to changes to a custom setState setter function, but I think there may be an issue with snap and how it handles re-renders from effects. I think the tests are good here but open to feedback if we want to go down the rabbit hole of figuring out a proper snap test for this.
ghstack-source-id: 5e9a8f6e0d
Pull Request resolved: https://github.com/facebook/react/pull/29190
No-op refactor to make Environment#getGlobalDeclaration() take a NonLocalBinding instead of just a name. The idea is that in subsequent PRs we can use information about the binding to resolve a type more accurately. For example, we can resolve `Array` differently if its an import or local and not the global Array. Similar for resolving local `useState` differently than the one from React.
ghstack-source-id: c8063e6fb8
Pull Request resolved: https://github.com/facebook/react/pull/29189
We currently use `LoadGlobal` and `StoreGlobal` to represent any read (or write) of a variable defined outside the component or hook that is being compiled. This is mostly fine, but for a lot of things we want to do going forward (resolving types across modules, for example) it helps to understand the actual source of a variable.
This PR is an incremental step in that direction. We continue to use LoadGlobal/StoreGlobal, but LoadGlobal now has a `binding:NonLocalBinding` instead of just the name of the global. The NonLocalBinding type tells us whether it was an import (and which kind, the source module name etc), a module-local binding, or a true global. By keeping the LoadGlobal/StoreGlobal instructions, most code that deals with "anything not declared locally" doesn't have to care about the difference. However, code that _does_ want to know the source of the value can figure it out.
ghstack-source-id: e701d4ebc0
Pull Request resolved: https://github.com/facebook/react/pull/29188
Repro of a case where we should ideally merge consecutive scopes, but where intermediate temporaries prevent the scopes from merging.
We'd need to reorder instructions in order to merge these.
ghstack-source-id: 4f05672604
Pull Request resolved: https://github.com/facebook/react/pull/29197
In MergeReactiveScopesThatInvalidateTogether when deciding which scopes were eligible for mergin at all, we looked specifically at the instructions whose lvalue produces the declaration. So if a scope declaration was `t0`, we'd love for the instruction where `t0` was the lvalue and look at the instruction type to decide if it is eligible for merging.
Here, we use the inferred type instead (now that the inferred types support the same set of types of instructions we looked at before). This allows us to find more cases where scopes can be merged.
ghstack-source-id: 0e3e05f24e
Pull Request resolved: https://github.com/facebook/react/pull/29157
Improves merging of consecutive scopes so that we now merge two scopes if the dependencies of the second scope are a subset of the previous scope's output *and* that dependency has a type that will always produce a new value (array, object, jsx, function) if it is re-evaluated.
To make this easier, we extend the set of builtin types to include ones for function expressions and JSX and to infer these types in InferTypes. This allows using the already inferred types in MergeReactiveScopesThatInvalidateTogether.
ghstack-source-id: e9119fc4e0
Pull Request resolved: https://github.com/facebook/react/pull/29156
React Compiler attempts to merge consecutive reactive scopes in order to reduce overhead. The basic idea is that if two consecutive scopes would always invalidate together then we should merge them. It gets more complicated, though, because values produced by the earlier scope may not always invalidate when their inputs do. For example, a scope that produces `fn(x)` may not invalidate on all changes to `x` if the function is `Math.max(x, 10)` (changing x from 8 to 9 won't change the output).
Previously we were conservative and only merged if either:
* the two scopes had the same dependencies
* the second scope's deps exactly matched the previous scope's outputs.
You can see this in the new fixture, where the second `<button>` gets its own scope, which happens because the preceding scope has an extra output that isn't a dep of the `<button>`'s scope.
ghstack-source-id: d869c8d4df
Pull Request resolved: https://github.com/facebook/react/pull/29155
## Summary
We ran React compiler against part of our codebase and collected
compiler errors. One of the more common non-actionable errors is caused
by usage of the `!` TypeScript non-null assertion operation:
```
(BuildHIR::lowerExpression) Handle TSNonNullExpression expressions
```
It seems like React Compiler _should_ be able to support this by just
ignoring the syntax and using the underlying expression. I'm sure a lot
of our non-null assertion usage should not exist and I understand if
React Compiler does not want to support this syntax. It wasn't obvious
to me if this omission was intentional or if there are future plans to
use `TSNonNullExpression` as part of the compiler's analysis. If there
are no future plans it seems like just ignoring it should be fine.
## How did you test this change?
```sh
❯ yarn snap --filter
yarn run v1.17.3
$ yarn workspace babel-plugin-react-compiler run snap --filter
$ node ../snap/dist/main.js --filter
PASS non-null-assertion
1 Tests, 1 Passed, 0 Failed
```
By default, React Compiler will skip compilation if it cannot preserve existing memoization. Ie, if the code has an existing `useMemo()` or `useCallback()` and the compiler cannot determine that it is safe to keep that memoization — or do even better — then we'll leave the code alone. The actual compilation doesn't use any hints from existing memo calls, this is purely to check and avoid regressing any specific memoization that developers may have already applied.
However, we were accidentally reporting some false-positive _validation_ errors due to the StartMemoize and FinishMemoize instructions that we emit to track where the memoization was in the source code. This is now fixed.
Fixes#29131Fixes#29132
ghstack-source-id: 9f6b8dbc50
Pull Request resolved: https://github.com/facebook/react/pull/29154
Improves ValidateNoRefAccessInRender (still disabled by default) to properly ignore ref access within effects. This includes allowing ref access within functions that are only transitively called from an effect.
While I was here I also added some extra test fixtures for allowing global mutation in effects.
ghstack-source-id: fb6352a178
Pull Request resolved: https://github.com/facebook/react/pull/29151
Babel doesn't seem to properly preserve escaping of HTML entities when emitting JSX text children, so this commit works around the issue by emitting a JsxExpressionContainer for JSX children that contain ">", "<", or "&" characters.
Closes#29100
ghstack-source-id: 2d0622397c
Pull Request resolved: https://github.com/facebook/react/pull/29143
Workaround for a bug in older versions of Babel, where strings with unicode are incorrectly escaped when emitted as JSX attributes, causing double-escaping by later processing.
Closes#29120Closes#29124
ghstack-source-id: 065440d4fb
Pull Request resolved: https://github.com/facebook/react/pull/29141
@jbonta nerd-sniped me into making this optimization during conference
prep, posting this as a PR now that keynote is over.
Consider these two cases:
```javascript
export default function MyApp1({ count }) {
const cb = () => count;
return <div onclick={cb}>Hello World</div>;
}
export default function MyApp2({ count }) {
return <div onclick={() => count}>Hello World</div>;
}
```
Previously, the former would create two reactive scopes (one for `cb`,
one for the div) while the latter would only have a single scope for the
`div` and its inline callback. The reason we created separate scopes
before is that there's a `StoreLocal 'cb' = t0` instruction in-between,
and i had conservatively implemented the merging pass to not allow
intervening StoreLocal instructions.
The observation is that intervening StoreLocals are fine _if_ the
assigned variable's last usage is the next scope. We already have a
check that the intervening lvalues are last-used at/before the next
scope, so it's trivial to extend this to support StoreLocal.
Note that we already don't merge scopes if there are intervening
terminals, so we don't have to worry about things like conditional
StoreLocal, conditional access of the resulting value, etc.
The [`files` field](https://docs.npmjs.com/cli/v10/commands/npm-publish#files-included-in-package)
controls what files get included in the published package.
This PR specifies the `files` field on our publishable packages to only
include the `dist` directory, since we don't need to ship any types or
sourcemaps with 3 of them.
react-compiler-runtime is a runtime package which has sourcemaps, so we
also include the `src` directory in the published package.
Also fixes an invalid version range for the react peer dependency in
react-compiler-runtime, tested that it works via https://semver.npmjs.com/
ghstack-source-id: 12b36c203fc9fd8d72a1995fb3fba2312de4aa51
Pull Request resolved: https://github.com/facebook/react-forget/pull/2965
We found this issue through enabling the compiler on the React Conf app.
`babel-preset-expo` automatically adds the `react-native-animated`
plugin to apps that use the preset. This means that Expo apps sometimes
omit the react-native-animated plugin from their config, which was
failing our existing check. This PR copies the same detection that Expo
does for adding reanimated as a fallback
ghstack-source-id: 46f7aec0bc
Pull Request resolved: https://github.com/facebook/react-forget/pull/2953
Adds supports for hot module reloading (HMR) by resetting the cache if a hash of the source file changes. This is enabled via a compiler flag, but also enabled automatically via the babel plugin when NODE_ENV=development.
ghstack-source-id: 5cd1ad5c89
Pull Request resolved: https://github.com/facebook/react-forget/pull/2951