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/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: [], +};