diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts index 167db6dedeccc..b2f1b9e6d4edc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -1354,20 +1354,6 @@ function codegenForInit( init: ReactiveValue, ): t.Expression | t.VariableDeclaration | null { if (init.kind === 'SequenceExpression') { - for (const instr of init.instructions) { - if (instr.value.kind === 'DeclareContext') { - CompilerError.throwTodo({ - reason: `Support for loops where the index variable is a context variable`, - loc: instr.loc, - description: - instr.value.lvalue.place.identifier.name != null - ? `\`${instr.value.lvalue.place.identifier.name.value}\` is a context variable` - : null, - suggestions: null, - }); - } - } - const body = codegenBlock( cx, init.instructions.map(instruction => ({ @@ -1378,20 +1364,33 @@ function codegenForInit( const declarators: Array = []; let kind: 'let' | 'const' = 'const'; body.forEach(instr => { - CompilerError.invariant( - instr.type === 'VariableDeclaration' && - (instr.kind === 'let' || instr.kind === 'const'), - { - reason: 'Expected a variable declaration', - loc: init.loc, - description: `Got ${instr.type}`, - suggestions: null, - }, - ); - if (instr.kind === 'let') { - kind = 'let'; + let top: undefined | t.VariableDeclarator = undefined; + if ( + instr.type === 'ExpressionStatement' && + instr.expression.type === 'AssignmentExpression' && + instr.expression.operator === '=' && + instr.expression.left.type === 'Identifier' && + (top = declarators.at(-1))?.id.type === 'Identifier' && + top?.id.name === instr.expression.left.name && + top?.init == null + ) { + top.init = instr.expression.right; + } else { + CompilerError.invariant( + instr.type === 'VariableDeclaration' && + (instr.kind === 'let' || instr.kind === 'const'), + { + reason: 'Expected a variable declaration', + loc: init.loc, + description: `Got ${instr.type}`, + suggestions: null, + }, + ); + if (instr.kind === 'let') { + kind = 'let'; + } + declarators.push(...instr.declarations); } - declarators.push(...instr.declarations); }); CompilerError.invariant(declarators.length > 0, { reason: 'Expected a variable declaration', diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md deleted file mode 100644 index fd03115be1021..0000000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md +++ /dev/null @@ -1,31 +0,0 @@ - -## Input - -```javascript -function Component() { - const data = useData(); - const items = []; - // NOTE: `i` is a context variable because it's reassigned and also referenced - // within a closure, the `onClick` handler of each item - for (let i = MIN; i <= MAX; i += INCREMENT) { - items.push( data.set(i)} />); - } - return items; -} - -``` - - -## Error - -``` - 4 | // NOTE: `i` is a context variable because it's reassigned and also referenced - 5 | // within a closure, the `onClick` handler of each item -> 6 | for (let i = MIN; i <= MAX; i += INCREMENT) { - | ^^^^^^^^^^^ Todo: Support for loops where the index variable is a context variable. `i` is a context variable (6:6) - 7 | items.push( data.set(i)} />); - 8 | } - 9 | return items; -``` - - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-context-variable-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-context-variable-iterator.expect.md new file mode 100644 index 0000000000000..d92e1919625d6 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-context-variable-iterator.expect.md @@ -0,0 +1,78 @@ + +## Input + +```javascript +function Component() { + const data = useData(); + const items = []; + // NOTE: `i` is a context variable because it's reassigned and also referenced + // within a closure, the `onClick` handler of each item + for (let i = MIN; i <= MAX; i += INCREMENT) { + items.push(
data.set(i)} />); + } + return <>{items}; +} + +const MIN = 0; +const MAX = 3; +const INCREMENT = 1; + +function useData() { + return new Map(); +} + +export const FIXTURE_ENTRYPOINT = { + params: [], + fn: Component, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +function Component() { + const $ = _c(2); + const data = useData(); + let t0; + if ($[0] !== data) { + const items = []; + for (let i = MIN; i <= MAX; i = i + INCREMENT, i) { + items.push(
data.set(i)} />); + } + + t0 = <>{items}; + $[0] = data; + $[1] = t0; + } else { + t0 = $[1]; + } + return t0; +} + +const MIN = 0; +const MAX = 3; +const INCREMENT = 1; + +function useData() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = new Map(); + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + params: [], + fn: Component, +}; + +``` + +### Eval output +(kind: ok)
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-context-variable-iterator.js similarity index 51% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-context-variable-iterator.js index 5330874b89bca..86e222e9e05cc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/for-loop-with-context-variable-iterator.js @@ -4,7 +4,20 @@ function Component() { // NOTE: `i` is a context variable because it's reassigned and also referenced // within a closure, the `onClick` handler of each item for (let i = MIN; i <= MAX; i += INCREMENT) { - items.push( data.set(i)} />); + items.push(
data.set(i)} />); } - return items; + return <>{items}; } + +const MIN = 0; +const MAX = 3; +const INCREMENT = 1; + +function useData() { + return new Map(); +} + +export const FIXTURE_ENTRYPOINT = { + params: [], + fn: Component, +};