From 6be80df3a611380caf1893f152f2dd8bb2560964 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 5 Dec 2025 16:09:25 -0500 Subject: [PATCH 1/2] fix: correctly compile post-increment assignment --- .../src/HIR/BuildHIR.ts | 10 ++++++++-- .../bug-post-increment-assignment.expect.md | 0 .../compiler/bug-post-increment-assignment.js | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) 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 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..20f1a18b1ee 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, }; } 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: [{}], +}; From a9c6ffea4d9cac762f8f826f368a5288be035100 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 5 Dec 2025 16:16:50 -0500 Subject: [PATCH 2/2] fix(compiler): support UpdateExpression on captured variables --- .../src/HIR/BuildHIR.ts | 54 ++++++++++++++++--- ...ate-expression-captured-variable.expect.md | 50 +++++++++++++++++ .../update-expression-captured-variable.js | 13 +++++ 3 files changed, 111 insertions(+), 6 deletions(-) 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/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 20f1a18b1ee..62e1d2877d7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -2652,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/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: [], +};