From 83cc9e5e96131c9c4cfd22e8428c2ba5f16a1b46 Mon Sep 17 00:00:00 2001 From: Andrew Leng Date: Sun, 7 Dec 2025 19:40:09 -0500 Subject: [PATCH 1/2] fix(compiler): Support UpdateExpression on captured variables logic --- .../src/HIR/BuildHIR.ts | 64 ++++++++++++++++--- split_react_35305.sh | 45 +++++++++++++ 2 files changed, 101 insertions(+), 8 deletions(-) create mode 100755 split_react_35305.sh diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index f6872da1117..62e1d2877d7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -2596,11 +2596,17 @@ function lowerExpression( // Store the previous value to a temporary const previousValuePlace = lowerValueToTemporary(builder, value); + const capturedPreviousValue = lowerValueToTemporary(builder, { + kind: 'LoadLocal', + place: {...previousValuePlace}, + loc: exprLoc, + }); + // Store the new value to a temporary const updatedValue = lowerValueToTemporary(builder, { kind: 'BinaryExpression', operator: binaryOperator, - left: {...previousValuePlace}, + left: {...capturedPreviousValue}, right: lowerValueToTemporary(builder, { kind: 'Primitive', value: 1, @@ -2633,7 +2639,7 @@ function lowerExpression( kind: 'LoadLocal', place: expr.node.prefix ? {...newValuePlace} - : {...previousValuePlace}, + : {...capturedPreviousValue}, loc: exprLoc, }; } @@ -2646,13 +2652,55 @@ function lowerExpression( }); return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; } else if (builder.isContextIdentifier(argument)) { - builder.errors.push({ - reason: `(BuildHIR::lowerExpression) Handle UpdateExpression to variables captured within lambdas.`, - category: ErrorCategory.Todo, - loc: exprPath.node.loc ?? null, - suggestions: null, + const binaryOperator = expr.node.operator === '++' ? '+' : '-'; + const lvalue = lowerIdentifierForAssignment( + builder, + argument.node.loc ?? GeneratedSource, + InstructionKind.Reassign, + argument, + ); + + if (lvalue === null) { + return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; + } else if (lvalue.kind === 'Global') { + builder.errors.push({ + reason: `(BuildHIR::lowerExpression) Expected Identifier to be a context variable`, + category: ErrorCategory.Invariant, + loc: exprLoc, + suggestions: null, + }); + return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; + } + + // Load the current value + const previousValue = lowerExpressionToTemporary(builder, argument); + + // Calculate the new value + const updatedValue = lowerValueToTemporary(builder, { + kind: 'BinaryExpression', + operator: binaryOperator, + left: {...previousValue}, + right: lowerValueToTemporary(builder, { + kind: 'Primitive', + value: 1, + loc: GeneratedSource, + }), + loc: exprLoc, }); - return {kind: 'UnsupportedNode', node: exprNode, loc: exprLoc}; + + // Store the new value + const newValuePlace = lowerValueToTemporary(builder, { + kind: 'StoreContext', + lvalue: {place: {...lvalue}, kind: InstructionKind.Reassign}, + value: {...updatedValue}, + loc: exprLoc, + }); + + return { + kind: 'LoadLocal', + place: expr.node.prefix ? {...newValuePlace} : {...previousValue}, + loc: exprLoc, + }; } const lvalue = lowerIdentifierForAssignment( builder, diff --git a/split_react_35305.sh b/split_react_35305.sh new file mode 100755 index 00000000000..961784993b7 --- /dev/null +++ b/split_react_35305.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -e + +# React +cd /Users/work/react_temp +git checkout main +git reset --hard origin/main +gh pr checkout 35305 -R facebook/react -b feature/original-35305 --force +git remote set-url origin https://github.com/tennisleng/react.git + +ORIGIN_BRANCH="feature/original-35305" + +# 1. Logic Branch +echo "Creating branch: feat/react-update-expr-logic" +git checkout main +git checkout -b feat/react-update-expr-logic + +# Checkout logic file +git checkout $ORIGIN_BRANCH -- compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts + +# Commit +git add . +git commit -m "fix(compiler): Support UpdateExpression on captured variables logic" + +# Push +git push origin feat/react-update-expr-logic +gh pr create --repo facebook/react --base main --head tennisleng:feat/react-update-expr-logic --title "fix(compiler): Support UpdateExpression on captured variables logic" --body "Splitting #35305: Core logical fix." + +# 2. Test Branch +echo "Creating branch: feat/react-update-expr-tests" +git checkout feat/react-update-expr-logic +git checkout -b feat/react-update-expr-tests + +# Checkout tests +git checkout $ORIGIN_BRANCH -- compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures + +# Commit +git add . +git commit -m "test(compiler): Add regression tests for UpdateExpression" + +# Push +git push origin feat/react-update-expr-tests +gh pr create --repo facebook/react --base main --head tennisleng:feat/react-update-expr-tests --title "test(compiler): Add regression tests for UpdateExpression" --body "Splitting #35305: Test fixtures. Depends on #logic-pr" + +echo "Done splitting #35305" From 72d6f286eae7f4a247cf8c8344c7bc02749079f5 Mon Sep 17 00:00:00 2001 From: Andrew Leng Date: Sun, 7 Dec 2025 19:40:12 -0500 Subject: [PATCH 2/2] test(compiler): Add regression tests for UpdateExpression --- .../bug-post-increment-assignment.expect.md | 0 .../compiler/bug-post-increment-assignment.js | 14 ++++++ ...ate-expression-captured-variable.expect.md | 50 +++++++++++++++++++ .../update-expression-captured-variable.js | 13 +++++ 4 files changed, 77 insertions(+) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-post-increment-assignment.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-post-increment-assignment.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/update-expression-captured-variable.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/update-expression-captured-variable.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-post-increment-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-post-increment-assignment.expect.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-post-increment-assignment.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-post-increment-assignment.js new file mode 100644 index 00000000000..464d9ceb172 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-post-increment-assignment.js @@ -0,0 +1,14 @@ + +function Component(props) { + const items = [0, 1, 2]; + return items.reduce((agg, item) => { + const current = agg.count++; + agg.res.push(current); + return agg; + }, {count: 0, res: []}); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/update-expression-captured-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/update-expression-captured-variable.expect.md new file mode 100644 index 00000000000..35124d5694c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/update-expression-captured-variable.expect.md @@ -0,0 +1,50 @@ + +## Input + +```javascript +function Component() { + let x = 0; + const inc = () => { + x++; + }; + inc(); + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component() { + const $ = _c(1); + let x; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + x = 0; + const inc = () => { + x = x + 1; + }; + + inc(); + $[0] = x; + } else { + x = $[0]; + } + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +### Eval output +(kind: ok) 1 \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/update-expression-captured-variable.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/update-expression-captured-variable.js new file mode 100644 index 00000000000..628a0657a2d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/update-expression-captured-variable.js @@ -0,0 +1,13 @@ +function Component() { + let x = 0; + const inc = () => { + x++; + }; + inc(); + return x; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +};