Skip to content

Commit 226b859

Browse files
committed
[compiler] Support for context variable loop iterators
Summary: Fixing a compiler todo ghstack-source-id: c4d9226b1745d003dc9945df1ac5c5a01712f909 Pull Request resolved: facebook#31709
1 parent 7283a21 commit 226b859

File tree

4 files changed

+119
-60
lines changed

4 files changed

+119
-60
lines changed

compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/CodegenReactiveFunction.ts

+26-27
Original file line numberDiff line numberDiff line change
@@ -1354,20 +1354,6 @@ function codegenForInit(
13541354
init: ReactiveValue,
13551355
): t.Expression | t.VariableDeclaration | null {
13561356
if (init.kind === 'SequenceExpression') {
1357-
for (const instr of init.instructions) {
1358-
if (instr.value.kind === 'DeclareContext') {
1359-
CompilerError.throwTodo({
1360-
reason: `Support for loops where the index variable is a context variable`,
1361-
loc: instr.loc,
1362-
description:
1363-
instr.value.lvalue.place.identifier.name != null
1364-
? `\`${instr.value.lvalue.place.identifier.name.value}\` is a context variable`
1365-
: null,
1366-
suggestions: null,
1367-
});
1368-
}
1369-
}
1370-
13711357
const body = codegenBlock(
13721358
cx,
13731359
init.instructions.map(instruction => ({
@@ -1378,20 +1364,33 @@ function codegenForInit(
13781364
const declarators: Array<t.VariableDeclarator> = [];
13791365
let kind: 'let' | 'const' = 'const';
13801366
body.forEach(instr => {
1381-
CompilerError.invariant(
1382-
instr.type === 'VariableDeclaration' &&
1383-
(instr.kind === 'let' || instr.kind === 'const'),
1384-
{
1385-
reason: 'Expected a variable declaration',
1386-
loc: init.loc,
1387-
description: `Got ${instr.type}`,
1388-
suggestions: null,
1389-
},
1390-
);
1391-
if (instr.kind === 'let') {
1392-
kind = 'let';
1367+
let top: undefined | t.VariableDeclarator = undefined;
1368+
if (
1369+
instr.type === 'ExpressionStatement' &&
1370+
instr.expression.type === 'AssignmentExpression' &&
1371+
instr.expression.operator === '=' &&
1372+
instr.expression.left.type === 'Identifier' &&
1373+
(top = declarators.at(-1))?.id.type === 'Identifier' &&
1374+
top?.id.name === instr.expression.left.name &&
1375+
top?.init == null
1376+
) {
1377+
top.init = instr.expression.right;
1378+
} else {
1379+
CompilerError.invariant(
1380+
instr.type === 'VariableDeclaration' &&
1381+
(instr.kind === 'let' || instr.kind === 'const'),
1382+
{
1383+
reason: 'Expected a variable declaration',
1384+
loc: init.loc,
1385+
description: `Got ${instr.type}`,
1386+
suggestions: null,
1387+
},
1388+
);
1389+
if (instr.kind === 'let') {
1390+
kind = 'let';
1391+
}
1392+
declarators.push(...instr.declarations);
13931393
}
1394-
declarators.push(...instr.declarations);
13951394
});
13961395
CompilerError.invariant(declarators.length > 0, {
13971396
reason: 'Expected a variable declaration',

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-for-loop-with-context-variable-iterator.expect.md

-31
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
2+
## Input
3+
4+
```javascript
5+
function Component() {
6+
const data = useData();
7+
const items = [];
8+
// NOTE: `i` is a context variable because it's reassigned and also referenced
9+
// within a closure, the `onClick` handler of each item
10+
for (let i = MIN; i <= MAX; i += INCREMENT) {
11+
items.push(<div key={i} onClick={() => data.set(i)} />);
12+
}
13+
return <>{items}</>;
14+
}
15+
16+
const MIN = 0;
17+
const MAX = 3;
18+
const INCREMENT = 1;
19+
20+
function useData() {
21+
return new Map();
22+
}
23+
24+
export const FIXTURE_ENTRYPOINT = {
25+
params: [],
26+
fn: Component,
27+
};
28+
29+
```
30+
31+
## Code
32+
33+
```javascript
34+
import { c as _c } from "react/compiler-runtime";
35+
function Component() {
36+
const $ = _c(2);
37+
const data = useData();
38+
let t0;
39+
if ($[0] !== data) {
40+
const items = [];
41+
for (let i = MIN; i <= MAX; i = i + INCREMENT, i) {
42+
items.push(<div key={i} onClick={() => data.set(i)} />);
43+
}
44+
45+
t0 = <>{items}</>;
46+
$[0] = data;
47+
$[1] = t0;
48+
} else {
49+
t0 = $[1];
50+
}
51+
return t0;
52+
}
53+
54+
const MIN = 0;
55+
const MAX = 3;
56+
const INCREMENT = 1;
57+
58+
function useData() {
59+
const $ = _c(1);
60+
let t0;
61+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
62+
t0 = new Map();
63+
$[0] = t0;
64+
} else {
65+
t0 = $[0];
66+
}
67+
return t0;
68+
}
69+
70+
export const FIXTURE_ENTRYPOINT = {
71+
params: [],
72+
fn: Component,
73+
};
74+
75+
```
76+
77+
### Eval output
78+
(kind: ok) <div></div><div></div><div></div><div></div>
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,20 @@ function Component() {
44
// NOTE: `i` is a context variable because it's reassigned and also referenced
55
// within a closure, the `onClick` handler of each item
66
for (let i = MIN; i <= MAX; i += INCREMENT) {
7-
items.push(<Stringify key={i} onClick={() => data.set(i)} />);
7+
items.push(<div key={i} onClick={() => data.set(i)} />);
88
}
9-
return items;
9+
return <>{items}</>;
1010
}
11+
12+
const MIN = 0;
13+
const MAX = 3;
14+
const INCREMENT = 1;
15+
16+
function useData() {
17+
return new Map();
18+
}
19+
20+
export const FIXTURE_ENTRYPOINT = {
21+
params: [],
22+
fn: Component,
23+
};

0 commit comments

Comments
 (0)