Skip to content

Commit 75f7946

Browse files
committed
[compiler] Fix for destructuring with mixed declaration/reassignment
Destructing statements that start off as declarations can end up becoming reassignments if the variable is a scope declaration, so we have existing logic to handle cases where some parts of a destructure need to be converted into new locals, with a reassignment to the hoisted scope variable afterwards. However, there is an edge case where all of the values are reassigned, in which case we don't need to rewrite and can just set the instruction kind to reassign.
1 parent 19b769f commit 75f7946

File tree

5 files changed

+164
-27
lines changed

5 files changed

+164
-27
lines changed

compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
BasicBlock,
1212
BlockId,
1313
Instruction,
14+
InstructionKind,
1415
InstructionValue,
1516
makeInstructionId,
1617
Pattern,
@@ -32,6 +33,32 @@ export function* eachInstructionLValue(
3233
yield* eachInstructionValueLValue(instr.value);
3334
}
3435

36+
export function* eachInstructionLValueWithKind(
37+
instr: ReactiveInstruction,
38+
): Iterable<[Place, InstructionKind]> {
39+
switch (instr.value.kind) {
40+
case 'DeclareContext':
41+
case 'StoreContext':
42+
case 'DeclareLocal':
43+
case 'StoreLocal': {
44+
yield [instr.value.lvalue.place, instr.value.lvalue.kind];
45+
break;
46+
}
47+
case 'Destructure': {
48+
const kind = instr.value.lvalue.kind;
49+
for (const place of eachPatternOperand(instr.value.lvalue.pattern)) {
50+
yield [place, kind];
51+
}
52+
break;
53+
}
54+
case 'PostfixUpdate':
55+
case 'PrefixUpdate': {
56+
yield [instr.value.lvalue, InstructionKind.Reassign];
57+
break;
58+
}
59+
}
60+
}
61+
3562
export function* eachInstructionValueLValue(
3663
value: ReactiveValue,
3764
): Iterable<Place> {

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

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,35 +1359,13 @@ function codegenInstructionNullable(
13591359
value = null;
13601360
} else {
13611361
lvalue = instr.value.lvalue.pattern;
1362-
let hasReassign = false;
1363-
let hasDeclaration = false;
13641362
for (const place of eachPatternOperand(lvalue)) {
13651363
if (
13661364
kind !== InstructionKind.Reassign &&
13671365
place.identifier.name === null
13681366
) {
13691367
cx.temp.set(place.identifier.declarationId, null);
13701368
}
1371-
const isDeclared = cx.hasDeclared(place.identifier);
1372-
hasReassign ||= isDeclared;
1373-
hasDeclaration ||= !isDeclared;
1374-
}
1375-
if (hasReassign && hasDeclaration) {
1376-
CompilerError.invariant(false, {
1377-
reason:
1378-
'Encountered a destructuring operation where some identifiers are already declared (reassignments) but others are not (declarations)',
1379-
description: null,
1380-
details: [
1381-
{
1382-
kind: 'error',
1383-
loc: instr.loc,
1384-
message: null,
1385-
},
1386-
],
1387-
suggestions: null,
1388-
});
1389-
} else if (hasReassign) {
1390-
kind = InstructionKind.Reassign;
13911369
}
13921370
value = codegenPlaceToExpression(cx, instr.value.value);
13931371
}

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

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ import {
1919
promoteTemporary,
2020
} from '../HIR';
2121
import {clonePlaceToTemporary} from '../HIR/HIRBuilder';
22-
import {eachPatternOperand, mapPatternOperands} from '../HIR/visitors';
22+
import {
23+
eachInstructionLValueWithKind,
24+
eachPatternOperand,
25+
mapPatternOperands,
26+
} from '../HIR/visitors';
2327
import {
2428
ReactiveFunctionTransform,
2529
Transformed,
@@ -113,14 +117,18 @@ class Visitor extends ReactiveFunctionTransform<State> {
113117
): Transformed<ReactiveStatement> {
114118
this.visitInstruction(instruction, state);
115119

120+
let instructionsToProcess: Array<ReactiveInstruction> = [instruction];
121+
let result: Transformed<ReactiveStatement> = {kind: 'keep'};
122+
116123
if (instruction.value.kind === 'Destructure') {
117124
const transformed = transformDestructuring(
118125
state,
119126
instruction,
120127
instruction.value,
121128
);
122129
if (transformed) {
123-
return {
130+
instructionsToProcess = transformed;
131+
result = {
124132
kind: 'replace-many',
125133
value: transformed.map(instruction => ({
126134
kind: 'instruction',
@@ -129,7 +137,17 @@ class Visitor extends ReactiveFunctionTransform<State> {
129137
};
130138
}
131139
}
132-
return {kind: 'keep'};
140+
141+
// Update state.declared with declarations from the instruction(s)
142+
for (const instr of instructionsToProcess) {
143+
for (const [place, kind] of eachInstructionLValueWithKind(instr)) {
144+
if (kind !== InstructionKind.Reassign) {
145+
state.declared.add(place.identifier.declarationId);
146+
}
147+
}
148+
}
149+
150+
return result;
133151
}
134152
}
135153

@@ -144,10 +162,13 @@ function transformDestructuring(
144162
const isDeclared = state.declared.has(place.identifier.declarationId);
145163
if (isDeclared) {
146164
reassigned.add(place.identifier.id);
165+
} else {
166+
hasDeclaration = true;
147167
}
148-
hasDeclaration ||= !isDeclared;
149168
}
150-
if (reassigned.size === 0 || !hasDeclaration) {
169+
if (!hasDeclaration) {
170+
// all reassignments
171+
destructure.lvalue.kind = InstructionKind.Reassign;
151172
return null;
152173
}
153174
/*
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @flow
6+
'use strict';
7+
8+
component Calendar(user, defaultFirstDay, currentDate, view) {
9+
const weekendDays = getWeekendDays(user);
10+
let firstDay = defaultFirstDay;
11+
let daysToDisplay = 7;
12+
if (view === 'week') {
13+
let lastDay;
14+
// this assignment produces invalid code
15+
[firstDay, lastDay] = getConfig(weekendDays);
16+
daysToDisplay = ((7 + lastDay - firstDay) % 7) + 1;
17+
} else if (view === 'day') {
18+
firstDay = currentDate.getDayOfWeek();
19+
daysToDisplay = 1;
20+
}
21+
22+
return [currentDate, firstDay, daysToDisplay];
23+
}
24+
25+
```
26+
27+
## Code
28+
29+
```javascript
30+
"use strict";
31+
import { c as _c } from "react/compiler-runtime";
32+
33+
function Calendar(t0) {
34+
const $ = _c(12);
35+
const { user, defaultFirstDay, currentDate, view } = t0;
36+
let daysToDisplay;
37+
let firstDay;
38+
if (
39+
$[0] !== currentDate ||
40+
$[1] !== defaultFirstDay ||
41+
$[2] !== user ||
42+
$[3] !== view
43+
) {
44+
const weekendDays = getWeekendDays(user);
45+
firstDay = defaultFirstDay;
46+
daysToDisplay = 7;
47+
if (view === "week") {
48+
let lastDay;
49+
50+
[firstDay, lastDay] = getConfig(weekendDays);
51+
daysToDisplay = ((7 + lastDay - firstDay) % 7) + 1;
52+
} else {
53+
if (view === "day") {
54+
let t1;
55+
if ($[6] !== currentDate) {
56+
t1 = currentDate.getDayOfWeek();
57+
$[6] = currentDate;
58+
$[7] = t1;
59+
} else {
60+
t1 = $[7];
61+
}
62+
firstDay = t1;
63+
daysToDisplay = 1;
64+
}
65+
}
66+
$[0] = currentDate;
67+
$[1] = defaultFirstDay;
68+
$[2] = user;
69+
$[3] = view;
70+
$[4] = daysToDisplay;
71+
$[5] = firstDay;
72+
} else {
73+
daysToDisplay = $[4];
74+
firstDay = $[5];
75+
}
76+
let t1;
77+
if ($[8] !== currentDate || $[9] !== daysToDisplay || $[10] !== firstDay) {
78+
t1 = [currentDate, firstDay, daysToDisplay];
79+
$[8] = currentDate;
80+
$[9] = daysToDisplay;
81+
$[10] = firstDay;
82+
$[11] = t1;
83+
} else {
84+
t1 = $[11];
85+
}
86+
return t1;
87+
}
88+
89+
```
90+
91+
### Eval output
92+
(kind: exception) Fixture not implemented
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// @flow
2+
'use strict';
3+
4+
component Calendar(user, defaultFirstDay, currentDate, view) {
5+
const weekendDays = getWeekendDays(user);
6+
let firstDay = defaultFirstDay;
7+
let daysToDisplay = 7;
8+
if (view === 'week') {
9+
let lastDay;
10+
// this assignment produces invalid code
11+
[firstDay, lastDay] = getConfig(weekendDays);
12+
daysToDisplay = ((7 + lastDay - firstDay) % 7) + 1;
13+
} else if (view === 'day') {
14+
firstDay = currentDate.getDayOfWeek();
15+
daysToDisplay = 1;
16+
}
17+
18+
return [currentDate, firstDay, daysToDisplay];
19+
}

0 commit comments

Comments
 (0)