mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Support for with empty update expression
Adds support for `for` statements with an empty or unreachable update expression. In both cases, reversePostorderBlocks() will remove the empty/unreachable update block, leaving the ForTerminal.update pointing to a non-existent block. We explicitly rewrite this (much like we null out unreachable fallthroughs after shrink). When transforming to ReactiveFunction, we emit the update block as null if it was the same as the test block.
This commit is contained in:
@@ -307,35 +307,35 @@ function lowerStatement(
|
||||
};
|
||||
});
|
||||
|
||||
const updateBlock = builder.enter("loop", (_blockId) => {
|
||||
const update = stmt.get("update");
|
||||
if (update.node == null) {
|
||||
builder.errors.push({
|
||||
reason: `(BuildHIR::lowerStatement) Handle empty update in ForStatement`,
|
||||
severity: ErrorSeverity.Todo,
|
||||
nodePath: stmt,
|
||||
});
|
||||
return { kind: "unsupported", id: makeInstructionId(0) };
|
||||
}
|
||||
lowerExpressionToTemporary(builder, update as NodePath<t.Expression>);
|
||||
return {
|
||||
kind: "goto",
|
||||
block: testBlock.id,
|
||||
variant: GotoVariant.Break,
|
||||
id: makeInstructionId(0),
|
||||
};
|
||||
});
|
||||
|
||||
const bodyBlock = builder.enter("block", (_blockId) => {
|
||||
return builder.loop(label, updateBlock, continuationBlock.id, () => {
|
||||
lowerStatement(builder, stmt.get("body"));
|
||||
let updateBlock: BlockId | null = null;
|
||||
const update = stmt.get("update");
|
||||
if (update.node != null) {
|
||||
updateBlock = builder.enter("loop", (_blockId) => {
|
||||
lowerExpressionToTemporary(builder, update as NodePath<t.Expression>);
|
||||
return {
|
||||
kind: "goto",
|
||||
block: updateBlock,
|
||||
variant: GotoVariant.Continue,
|
||||
block: testBlock.id,
|
||||
variant: GotoVariant.Break,
|
||||
id: makeInstructionId(0),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
const bodyBlock = builder.enter("block", (_blockId) => {
|
||||
return builder.loop(
|
||||
label,
|
||||
updateBlock ?? testBlock.id,
|
||||
continuationBlock.id,
|
||||
() => {
|
||||
lowerStatement(builder, stmt.get("body"));
|
||||
return {
|
||||
kind: "goto",
|
||||
block: updateBlock ?? testBlock.id,
|
||||
variant: GotoVariant.Continue,
|
||||
id: makeInstructionId(0),
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
builder.terminateWithContinuation(
|
||||
|
||||
@@ -347,7 +347,7 @@ export type ForTerminal = {
|
||||
loc: SourceLocation;
|
||||
init: BlockId;
|
||||
test: BlockId;
|
||||
update: BlockId;
|
||||
update: BlockId | null;
|
||||
loop: BlockId;
|
||||
fallthrough: BlockId;
|
||||
id: InstructionId;
|
||||
|
||||
@@ -282,6 +282,7 @@ export default class HIRBuilder {
|
||||
logHIR("Build (shrunk)", ir);
|
||||
// then convert to reverse postorder
|
||||
reversePostorderBlocks(ir);
|
||||
removeUnreachableForUpdates(ir);
|
||||
removeUnreachableFallthroughs(ir);
|
||||
removeDeadDoWhileStatements(ir);
|
||||
markInstructionIds(ir);
|
||||
@@ -528,6 +529,18 @@ export function shrink(func: HIR): void {
|
||||
}
|
||||
}
|
||||
|
||||
export function removeUnreachableForUpdates(fn: HIR): void {
|
||||
for (const [, block] of fn.blocks) {
|
||||
if (
|
||||
block.terminal.kind === "for" &&
|
||||
block.terminal.update !== null &&
|
||||
!fn.blocks.has(block.terminal.update)
|
||||
) {
|
||||
block.terminal.update = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function removeUnreachableFallthroughs(func: HIR): void {
|
||||
const visited: Set<BlockId> = new Set();
|
||||
for (const [_, block] of func.blocks) {
|
||||
|
||||
@@ -632,7 +632,7 @@ export function mapTerminalSuccessors(
|
||||
case "for": {
|
||||
const init = fn(terminal.init);
|
||||
const test = fn(terminal.test);
|
||||
const update = fn(terminal.update);
|
||||
const update = terminal.update !== null ? fn(terminal.update) : null;
|
||||
const loop = fn(terminal.loop);
|
||||
const fallthrough = fn(terminal.fallthrough);
|
||||
return {
|
||||
|
||||
@@ -24,7 +24,10 @@ import {
|
||||
validateConsistentIdentifiers,
|
||||
validateTerminalSuccessors,
|
||||
} from "../HIR";
|
||||
import { removeDeadDoWhileStatements } from "../HIR/HIRBuilder";
|
||||
import {
|
||||
removeDeadDoWhileStatements,
|
||||
removeUnreachableForUpdates,
|
||||
} from "../HIR/HIRBuilder";
|
||||
import { eliminateRedundantPhi } from "../SSA";
|
||||
|
||||
/**
|
||||
@@ -52,6 +55,7 @@ export function constantPropagation(fn: HIRFunction): void {
|
||||
shrink(fn.body);
|
||||
reversePostorderBlocks(fn.body);
|
||||
removeUnreachableFallthroughs(fn.body);
|
||||
removeUnreachableForUpdates(fn.body);
|
||||
removeDeadDoWhileStatements(fn.body);
|
||||
markInstructionIds(fn.body);
|
||||
markPredecessors(fn.body);
|
||||
|
||||
@@ -347,7 +347,7 @@ class Driver {
|
||||
|
||||
const scheduleId = this.cx.scheduleLoop(
|
||||
terminal.fallthrough,
|
||||
terminal.update,
|
||||
terminal.update ?? terminal.test,
|
||||
terminal.loop
|
||||
);
|
||||
scheduleIds.push(scheduleId);
|
||||
@@ -382,10 +382,10 @@ class Driver {
|
||||
terminal.loc
|
||||
).value;
|
||||
|
||||
const updateValue = this.visitValueBlock(
|
||||
terminal.update,
|
||||
terminal.loc
|
||||
).value;
|
||||
const updateValue =
|
||||
terminal.update !== null
|
||||
? this.visitValueBlock(terminal.update, terminal.loc).value
|
||||
: null;
|
||||
|
||||
let loopBody: ReactiveBlock;
|
||||
if (loopId) {
|
||||
|
||||
@@ -335,7 +335,7 @@ export function leaveSSA(fn: HIRFunction): void {
|
||||
}
|
||||
}
|
||||
|
||||
if (terminal.kind === "for") {
|
||||
if (terminal.kind === "for" && terminal.update !== null) {
|
||||
const update = fn.body.blocks.get(terminal.update)!;
|
||||
pushPhis(update);
|
||||
}
|
||||
|
||||
@@ -205,7 +205,9 @@ function printTerminalArrows(blockId: BlockId, terminal: Terminal): string {
|
||||
case "for": {
|
||||
buffer.push(printJumpArrow(blockId, terminal.init, "init"));
|
||||
buffer.push(printJumpArrow(blockId, terminal.test, "test"));
|
||||
buffer.push(printJumpArrow(blockId, terminal.update, "update"));
|
||||
if (terminal.update !== null) {
|
||||
buffer.push(printJumpArrow(blockId, terminal.update, "update"));
|
||||
}
|
||||
buffer.push(printJumpArrow(blockId, terminal.loop, "loop"));
|
||||
buffer.push(printJumpArrow(blockId, terminal.fallthrough, "fallthrough"));
|
||||
break;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
for (let i = 0; i < props.count; i++) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
[ReactForget] Invariant: Block bb4 does not exist for terminal '[1] For init=bb3 test=bb1 loop=bb5 update=bb4 fallthrough=bb2' (2:4)
|
||||
```
|
||||
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function foo(props) {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Cannot read properties of undefined (reading 'phis')
|
||||
```
|
||||
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
function foo(props) {
|
||||
for (let i = 0; i < 10; i += 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -144,15 +144,6 @@ let moduleLocal = false;
|
||||
25 | }
|
||||
26 | for (;;) {
|
||||
|
||||
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
|
||||
21 | x.push(i);
|
||||
22 | }
|
||||
> 23 | for (; i < 3; ) {
|
||||
| ^
|
||||
24 | break;
|
||||
25 | }
|
||||
26 | for (;;) {
|
||||
|
||||
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
|
||||
24 | break;
|
||||
25 | }
|
||||
@@ -162,15 +153,6 @@ let moduleLocal = false;
|
||||
28 | }
|
||||
29 |
|
||||
|
||||
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
|
||||
24 | break;
|
||||
25 | }
|
||||
> 26 | for (;;) {
|
||||
| ^
|
||||
27 | break;
|
||||
28 | }
|
||||
29 |
|
||||
|
||||
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty test in ForStatement
|
||||
24 | break;
|
||||
25 | }
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
let x = 0;
|
||||
for (let i = 0; i < props.count; ) {
|
||||
x += i;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
let x = 0;
|
||||
for (let i = 0; i < props.count; ) {
|
||||
x = x + i;
|
||||
i = i + 1;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
function Component(props) {
|
||||
let x = 0;
|
||||
for (let i = 0; i < props.count; ) {
|
||||
x += i;
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
let x = 0;
|
||||
for (let i = 0; i < props.count; ) {
|
||||
x += i;
|
||||
if (x > 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
let x = 0;
|
||||
for (const i = 0; 0 < props.count; ) {
|
||||
x = x + 0;
|
||||
if (x > 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
function Component(props) {
|
||||
let x = 0;
|
||||
for (let i = 0; i < props.count; ) {
|
||||
x += i;
|
||||
if (x > 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
for (let i = 0; i < props.count; i++) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
function Component(props) {
|
||||
for (const i = 0; 0 < props.count; ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user