This bug was reported via our wg and appears to only affect values
created as a ref.
Currently, postfix operators used in a callback gets compiled to:
```js
modalId.current = modalId.current + 1; // 1
const id = modalId.current; // 1
return id;
```
which is semantically incorrect. The postfix increment operator should
return the value before incrementing. In other words something like this
should have been compiled instead:
```js
const id = modalId.current; // 0
modalId.current = modalId.current + 1; // 1
return id;
```
This bug does not trigger when the incremented value is a plain
primitive, instead there is a TODO bailout.
## Summary
This tool leverages DevTools to get the component tree from the
currently open React App. This gives realtime information to agents
about the state of the app.
## How did you test this change?
Tested integration with Claude Desktop
This is a babel bug + edge case.
Babel compact mode produces invalid JavaScript (i.e. parse error) when
given a `NumericLiteral` with a negative value.
See https://codesandbox.io/p/devbox/5d47fr for repro.
## Summary
While investigating the root cause of #33208, I noticed a clear typo for
one of the validation files.
## How did you test this change?
Inside `/react/compiler/packages/babel-plugin-react-compiler` I ran the
test script successfully:
<img width="415" alt="Screenshot at May 22 16-43-06"
src="https://github.com/user-attachments/assets/3fe8c5e1-37ce-4a31-b35e-7e323e57cd9d"
/>
Inferred effect dependencies now include optional chains.
This is a temporary solution while
https://github.com/facebook/react/pull/32099 and its followups are
worked on. Ideally, we should model reactive scope dependencies in the
IR similarly to `ComputeIR` -- dependencies should be hoisted and all
references rewritten to use the hoisted dependencies.
`
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33326).
* __->__ #33326
* #33325
* #32286
React Compiler's program traversal logic is pretty lengthy and complex
as we've added a lot of features piecemeal. `compileProgram` is 300+
lines long and has confusing control flow (defining helpers inline,
invoking visitors, mutating-asts-while-iterating, mutating global
`ALREADY_COMPILED` state).
- Moved more stuff to `ProgramContext`
- Separated `compileProgram` into a bunch of helpers
Tested by syncing this stack to a Meta codebase and observing no
compilation output changes (D74487851, P1806855669, P1806855379)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33147).
* #33149
* #33148
* __->__ #33147
New take on #29716
## Summary
Template literals consisting entirely of constant values will be inlined
to a string literal, effectively replacing the backticks with a double
quote.
This is done primarily to make the resulting instruction a string
literal, so it can be processed further in constant propatation. So this
is now correctly simplified to `true`:
```js
`` === "" // now true
`a${1}` === "a1" // now true
```
If a template string literal can only partially be comptime-evaluated,
it is not that useful for dead code elimination or further constant
folding steps and thus, is left as-is in that case. Same is true if the
literal contains an array, object, symbol or function.
## How did you test this change?
See added tests.
(Almost) all pragmas are now one of the following:
- `@...TestOnly`: custom pragma for test fixtures
- `@<configName>` | `@<configName>:true`: enables with either true or a
default enabled value
- `@<configName>:<json value>`
## Summary
`-constant` is represented as a `UnaryExpression` node that is currently
not part of constant folding. If the operand is a constant number, the
node is folded to `constant * -1`. This also coerces `-0` to `0`,
resulting in `0 === -0` being folded to `true`.
## How did you test this change?
See attached tests
Multiple things here:
- Improve the mean calculation for metrics so we don't report 0 when
web-vitals fail to be retrieved
- improve ui chaos monkey to use puppeteer APIs since only those trigger
INP/CLS metrics since we need emulated mouse clicks
- Add logic to navigate to a temp page after render since some
web-vitals metrics are only calculated when the page is backgrounded
- Some readability improvements
Note that bailing out adds false positives for hoisted functions whose
only references are within other functions. For example, this rewrite
would be safe.
```js
// source program
function foo() {
return bar();
}
function bar() {
return 42;
}
// compiler output
let bar;
if (/* deps changed */) {
function foo() {
return bar();
}
bar = function bar() {
return 42;
}
}
```
These false positives are difficult to detect because any maybe-call of
foo before the definition of bar would be invalid.
Instead of bailing out, we should rewrite hoisted function declarations
to the following form.
```js
let bar$0;
if (/* deps changed */) {
// All references within the declaring memo block
// or before the function declaration should use
// the original identifier `bar`
function foo() {
return bar();
}
function bar() {
return 42;
}
bar$0 = bar;
}
// All references after the declaring memo block
// or after the function declaration should use
// the rewritten declaration `bar$0`
```
This revisits a validation I built a while ago, trying to make it more strict this time to ensure that it's high-signal.
We detect function expressions which are *known* mutable — they definitely can modify a variable defined outside of the function expression itself (modulo control flow). This uses types to look for known Store and Mutate effects only, and disregards mutations of effects. Any such function passed to a location with a Freeze effect is reported as a validation error.
This is behind a flag and disabled by default. If folks agree this makes sense to revisit, i'll test out internally and we can consider enabling by default.
ghstack-source-id: 075a731444ce95e52dbd5ea3be85c16d428927f5
Pull Request resolved: https://github.com/facebook/react/pull/33079
If a function captures a mutable value but never gets called, we don't infer a mutable range for that function. This means that we also don't alias the function with its mutable captures.
This case is tricky, because we don't generally know for sure what is a mutation and what may just be a normal function call. For example:
```js
hook useFoo() {
const x = makeObject();
return () => {
return readObject(x); // could be a mutation!
}
}
```
If we pessimistically assume that all such cases are mutations, we'd have to group lots of memo scopes together unnecessarily. However, if there is definitely a mutation:
```js
hook useFoo(createEntryForKey) {
const cache = new WeakMap();
return (key) => {
let entry = cache.get(key);
if (entry == null) {
entry = createEntryForKey(key);
cache.set(key, entry); // known mutation!
}
return entry;
}
}
```
Then we have to ensure that the function and its mutable captures alias together and end up in the same scope. However, aliasing together isn't enough if the function and operands all have empty mutable ranges (end = start + 1).
This pass finds function expressions and object methods that have an empty mutable range and known-mutable operands which also don't have a mutable range, and ensures that the function and those operands are aliased together *and* that their ranges are updated to end after the function expression. This is sufficient to ensure that a reactive scope is created for the alias set.
NOTE: The alternative is to reject these cases. If we do that we'd also want to similarly disallow cases like passing a mutable function to a hook.
ghstack-source-id: 5d8158246a320e80d8da3f0e395ac1953d8920a2
Pull Request resolved: https://github.com/facebook/react/pull/33078
Building on mofeiz's recent work to type constructors. Also, types for reanimated values which are useful in the next PR.
ghstack-source-id: 1c81e213a11337ac7e9c85a429ecf3f1d1adef66
Pull Request resolved: https://github.com/facebook/react/pull/33077
This pass didn't previously report the precise difference btw inferred/manual dependencies unless a debug flag was set. But the error message is really good (nice job mofeiz): the only catch is that in theory the inferred dep could be a temporary that can't trivially be reported to the user.
But the messages are really useful for quickly verifying why the compiler couldn't preserve memoization. So here we switch to outputting a detailed message about the discrepancy btw inferred/manual deps so long as the inferred dep root is a named variable. I also slightly adjusted the message to handle the case where there is no diagnostic, which can occur if there were no manual deps but the compiler inferred a dependency.
ghstack-source-id: 534f6f1fec0855e05e85077eba050eb2ba254ef8
Pull Request resolved: https://github.com/facebook/react/pull/33095
If a JSX attribute value is a string that contains unicode or other characters that need special escaping, we wrap the attribute value in an expression container. However, our unicode to detect this only handled the basic unicode character plane, not the "astral" plane which includes emojis.
This PR updates the regex to detect such extended characters and also use an expression container.
ghstack-source-id: 6d9c8e4dd22285077108e2fa53d66154d1b781fb
Pull Request resolved: https://github.com/facebook/react/pull/33096
Our runtime results are still bogus, but so far this tweak let's the llm
use the tools a little more intelligently.
example:
---
```js
import React from 'react';
function App() {
const [count, setCount] = React.useState(0);
const [data, setData] = React.useState([]);
React.useEffect(() => {
const processData = () => {
console.log("Processing data...");
const newData = [];
for (let i = 0; i < 1000000000; i++) {
newData.push({ id: i, value: Math.random() * count });
}
return newData;
};
const processedData = processData();
setData(processedData);
}, [count]);
return (
<div>
<p>Count: {count}</p>
<p>Data Length: {data.length}</p>
<ul>
{data.slice(0, 5).map((item) => (
<li key={item.id}>Item {item.id}: {item.value.toFixed(2)}</li>
))}
</ul>
</div>
);
};
```
why is this component slow? use the react tools to figure this out.
> I'll analyze why this component is slow using the React tools. Let's
first evaluate the performance of the original code.
>
[review-react-runtime ...]
>
> The performance measurement tool timed out, which is a strong
indication of just how severe the performance issue is in this
component. Let's now try to compile the code to see if React Compiler
can identify any issues:
>
[compile...]
>
> Now I understand the issue. Let's analyze why this component > is
extremely slow:
> Main Performance Issues:
>
> Massive Data Processing: The component is trying to create an array
with a billion (1,000,000,000) objects in the processData function. This
is an enormous amount of data that will:
>
> Consume massive amounts of memory
> Take an extremely long time to process
> Likely crash the browser
>
>
> Unnecessary Recalculation: The effect depends on [count], meaning
every time the count changes, it will reprocess this enormous dataset.
> Inefficient Data Handling: Even though only 5 items are displayed, the
component processes a billion items.
>
> Let's fix these issues:
>
[compile...]
>
> Now let's check if our modified code performs better:
>
[review-react-runtime...]
>
> Excellent! The performance has dramatically improved. Now let's check
the React documentation to make sure we're following the best practices:
>
[query-react-dev-docs...]
(hit the maximum length conversation limit because i'm on a free plan)
Since we use esbuild we need to correctly move dependencies that are
required at runtime into `dependencies` and other packages that are only
used in development in to `devDependencies`. This ensures the correct
packages are included in the build.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33101).
* #33085
* #33084
* #33083
* #33082
* __->__ #33101
We weren't treating terminal operands as eligible for memoization in PruneNonEscapingScopes, which meant that they could end up un-memoized. Terminal operands can also be compound ReactiveValues like SequenceExpressions, so part of the fix is to make sure we don't just recurse into compound values but record the full aliasing information we would for top-level instructions.
Still WIP, this needs to handle terminals other than for..of.
ghstack-source-id: 09a29230514e3bc95d1833cd4392de238fabbeda
Pull Request resolved: https://github.com/facebook/react/pull/33062
## Summary
Fix babel presets, and add a bit more context to the tool so that it is
more reliable
## How did you test this change?
Manually tested the mcp integrated with claude desktop
```js
function Component() {
useEffect(() => {
let hasCleanedUp = false;
document.addEventListener(..., () => hasCleanedUp ? foo() : bar());
// effect return values shouldn't be typed as frozen
return () => {
hasCleanedUp = true;
}
};
}
```
### Problem
`PruneHoistedContexts` currently strips hoisted declarations and
rewrites the first `StoreContext` reassignment to a declaration. For
example, in the following example, instruction 0 is removed while a
synthetic `DeclareContext let` is inserted before instruction 1.
```js
// source
const cb = () => x; // reference that causes x to be hoisted
let x = 4;
x = 5;
// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] StoreContext reassign 'x' = 4
[2] StoreContext reassign 'x' = 5
```
Currently, we don't account for `DeclareContext let`. As a result, we're
rewriting to insert duplicate declarations.
```js
// source
const cb = () => x; // reference that causes x to be hoisted
let x;
x = 5;
// React Compiler IR
[0] DeclareContext HoistedLet 'x'
...
[1] DeclareContext Let 'x'
[2] StoreContext reassign 'x' = 5
```
### Solution
Instead of always lowering context variables to a DeclareContext
followed by a StoreContext reassign, we can keep `kind: 'Const' | 'Let'
| 'Reassign' | etc` on StoreContext.
Pros:
- retain more information in HIR, so we can codegen easily `const` and
`let` context variable declarations back
- pruning hoisted `DeclareContext` instructions is simple.
Cons:
- passes are more verbose as we need to check for both `DeclareContext`
and `StoreContext` declarations
~(note: also see alternative implementation in
https://github.com/facebook/react/pull/32745)~
### Testing
Context variables are tricky. I synced and diffed changes in a large
meta codebase and feel pretty confident about landing this. About 0.01%
of compiled files changed. Among these changes, ~25% were [direct
bugfixes](https://www.internalfb.com/phabricator/paste/view/P1800029094).
The [other
changes](https://www.internalfb.com/phabricator/paste/view/P1800028575)
were primarily due to changed (corrected) mutable ranges from
https://github.com/facebook/react/pull/33047. I tried to represent most
interesting changes in new test fixtures
`
Fixes an edge case in React Compiler's effects inference model.
Returned values should only be typed as 'frozen' if they are (1) local
and (2) not a function expression which may capture and mutate this
function's outer context. See test fixtures for details
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33047).
* #32765
* #32747
* __->__ #33047
## Summary
Add a way for the agent to get some data on the performance of react
code
## How did you test this change?
Tested function independently and directly with claude desktop app
---------
Co-authored-by: Sebastian "Sebbie" Silbermann <sebastian.silbermann@vercel.com>
When effect dependencies cannot be inferred due to memoization-related
bailouts or unexpected mutable ranges (which currently often have to do
with writes to refs), fall back to traversing the effect lambda itself.
This fallback uses the same logic as PropagateScopeDependencies:
1. Collect a sidemap of loads and property loads
2. Find hoistable accesses from the control flow graph. Note that here,
we currently take into account the mutable ranges of instructions (see
`mutate-after-useeffect-granular-access` fixture)
3. Collect the set of property paths accessed by the effect
4. Merge to get the set of minimal dependencies
Inferred effect dependencies and inlined jsx (both experimental
features) rely on `InferReactivePlaces` to determine their dependencies.
Since adding type inference for phi nodes
(https://github.com/facebook/react/pull/30796), we have been incorrectly
inferring stable-typed value blocks (e.g. `props.cond ? setState1 :
setState2`) as non-reactive. This fix patches InferReactivePlaces
instead of adding a new pass since we want non-reactivity propagated
correctly
Previously the CompileSuccess event would emit first before CompileSkip,
so the lsp's codelens would incorrectly flag skipped components/hooks
(via 'use no memo') as being optimized.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33012).
* __->__ #33012
* #33011
* #33010
Compiler shouldn't crash Forgive if it can't compile (eg parse error due
to being mid-typing).
Co-authored-by: Jordan Brown <jmbrown@meta.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33001).
* #33002
* __->__ #33001
* #33000
---------
Co-authored-by: Jordan Brown <jmbrown@meta.com>
Adds a new codeaction event in the compiler and handler in forgive. This
allows you to remove a dependency array when you're editing a range that
is within an autodep eligible function.
Co-authored-by: Jordan Brown <jmbrown@meta.com>
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33000).
* #33002
* #33001
* __->__ #33000
Co-authored-by: Jordan Brown <jmbrown@meta.com>