From 440fd1f24abe340801baeca594d844d609c902b9 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Wed, 1 Mar 2023 19:45:02 -0500 Subject: [PATCH] Add support for DoWhile statements Adds support for DoWhileStatements. It's pretty similar to how we handle While, except in the case where a test block is unreachable (for example, an early unconditional `break` within the loop body). In this scenario we eliminate the terminal altogether and replace it with a goto to the loop block. --- compiler/forget/src/HIR/BuildHIR.ts | 54 +++++++- compiler/forget/src/HIR/HIR.ts | 17 +++ compiler/forget/src/HIR/HIRBuilder.ts | 29 +++++ compiler/forget/src/HIR/PrintHIR.ts | 9 +- compiler/forget/src/HIR/visitors.ts | 19 +++ .../src/Optimization/ConstantPropagation.ts | 2 + .../ReactiveScopes/BuildReactiveFunction.ts | 49 +++++++ .../ReactiveScopes/CodegenReactiveFunction.ts | 4 + .../ReactiveScopes/FlattenReactiveLoops.ts | 1 + .../PropagateScopeDependencies.ts | 5 + .../forget/src/ReactiveScopes/visitors.ts | 6 + .../forget/src/Utils/VisualizeHIRMermaid.ts | 10 ++ .../fixtures/hir/do-while-break.expect.md | 22 ++++ .../__tests__/fixtures/hir/do-while-break.js | 6 + .../hir/do-while-compound-test.expect.md | 40 ++++++ .../fixtures/hir/do-while-compound-test.js | 9 ++ .../hir/do-while-conditional-break.expect.md | 43 +++++++ .../hir/do-while-conditional-break.js | 10 ++ .../fixtures/hir/do-while-continue.expect.md | 46 +++++++ .../fixtures/hir/do-while-continue.js | 13 ++ ...-while-early-unconditional-break.expect.md | 34 +++++ .../hir/do-while-early-unconditional-break.js | 8 ++ .../fixtures/hir/do-while-simple.expect.md | 38 ++++++ .../__tests__/fixtures/hir/do-while-simple.js | 9 ++ .../hir/error.todo-kitchensink.expect.md | 121 ++++++++---------- .../fixtures/hir/error.todo-kitchensink.js | 2 - 26 files changed, 536 insertions(+), 70 deletions(-) create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-break.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-break.js create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-compound-test.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-compound-test.js create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-conditional-break.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-conditional-break.js create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-continue.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-continue.js create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-early-unconditional-break.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-early-unconditional-break.js create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-simple.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/do-while-simple.js diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 191a6e5f6e..66f202d6a6 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -644,9 +644,61 @@ function lowerStatement( }); return; } + case "DoWhileStatement": { + const stmt = stmtPath as NodePath; + // Block used to evaluate whether to (re)enter or exit the loop + const conditionalBlock = builder.reserve("loop"); + // Block for code following the loop + const continuationBlock = builder.reserve("block"); + // Loop body, executed at least once uncondtionally prior to exit + const loopBlock = builder.enter("block", (_loopBlockId) => { + return builder.loop( + label, + conditionalBlock.id, + continuationBlock.id, + () => { + lowerStatement(builder, stmt.get("body")); + return { + kind: "goto", + block: conditionalBlock.id, + variant: GotoVariant.Continue, + id: makeInstructionId(0), + }; + } + ); + }); + // Jump to the conditional block to evaluate whether to (re)enter the loop or exit to the + // continuation block. + const loc = stmt.node.loc ?? GeneratedSource; + builder.terminateWithContinuation( + { + kind: "do-while", + loc, + test: conditionalBlock.id, + loop: loopBlock, + fallthrough: continuationBlock.id, + id: makeInstructionId(0), + }, + conditionalBlock + ); + /** + * The conditional block is empty and exists solely as conditional for + * (re)entering or exiting the loop + */ + const test = lowerExpressionToTemporary(builder, stmt.get("test")); + const terminal: BranchTerminal = { + kind: "branch", + test, + consequent: loopBlock, + alternate: continuationBlock.id, + id: makeInstructionId(0), + }; + // Complete the conditional and continue with code after the loop + builder.terminateWithContinuation(terminal, continuationBlock); + return; + } case "ForOfStatement": case "ForInStatement": - case "DoWhileStatement": case "ClassDeclaration": case "DebuggerStatement": case "DeclareClass": diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index 6a4d320259..0db41d1b74 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -122,6 +122,7 @@ export type ReactiveTerminal = | ReactiveReturnTerminal | ReactiveThrowTerminal | ReactiveSwitchTerminal + | ReactiveDoWhileTerminal | ReactiveWhileTerminal | ReactiveForTerminal | ReactiveIfTerminal; @@ -157,6 +158,12 @@ export type ReactiveSwitchTerminal = { }>; id: InstructionId; }; +export type ReactiveDoWhileTerminal = { + kind: "do-while"; + loop: ReactiveBlock; + test: ReactiveValue; + id: InstructionId; +}; export type ReactiveWhileTerminal = { kind: "while"; test: ReactiveValue; @@ -241,6 +248,7 @@ export type Terminal = | BranchTerminal | SwitchTerminal | ForTerminal + | DoWhileTerminal | WhileTerminal | LogicalTerminal | TernaryTerminal; @@ -298,6 +306,15 @@ export type SwitchTerminal = { id: InstructionId; }; +export type DoWhileTerminal = { + kind: "do-while"; + loop: BlockId; + test: BlockId; + fallthrough: BlockId; + id: InstructionId; + loc: SourceLocation; +}; + export type WhileTerminal = { kind: "while"; loc: SourceLocation; diff --git a/compiler/forget/src/HIR/HIRBuilder.ts b/compiler/forget/src/HIR/HIRBuilder.ts index 818ad5f185..6a7d38320e 100644 --- a/compiler/forget/src/HIR/HIRBuilder.ts +++ b/compiler/forget/src/HIR/HIRBuilder.ts @@ -264,6 +264,7 @@ export default class HIRBuilder { // then convert to reverse postorder reversePostorderBlocks(ir); removeUnreachableFallthroughs(ir); + removeDeadDoWhileStatements(ir); markInstructionIds(ir); markPredecessors(ir); @@ -530,6 +531,30 @@ export function removeUnreachableFallthroughs(func: HIR): void { } } } + +export function removeDeadDoWhileStatements(func: HIR): void { + const visited: Set = new Set(); + for (const [_, block] of func.blocks) { + visited.add(block.id); + } + + // If the test condition of a DoWhile is unreachable, the terminal is effectively deadcode and we + // can just inline the loop body. We replace the terminal with a goto to the loop block and + // MergeConsecutiveBlocks figures out how to merge as appropriate. + for (const [_, block] of func.blocks) { + if (block.terminal.kind === "do-while") { + if (!visited.has(block.terminal.test)) { + block.terminal = { + kind: "goto", + block: block.terminal.loop, + variant: GotoVariant.Break, + id: block.terminal.id, + }; + } + } + } +} + /** * Converts the graph to reverse-postorder, with predecessor blocks appearing * before successors except in the case of back links (ie loops). @@ -604,6 +629,10 @@ export function reversePostorderBlocks(func: HIR): void { visit(terminal.test); break; } + case "do-while": { + visit(terminal.loop); + break; + } case "while": { visit(terminal.test); break; diff --git a/compiler/forget/src/HIR/PrintHIR.ts b/compiler/forget/src/HIR/PrintHIR.ts index f89e5a48c5..1168dc5e22 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -87,7 +87,8 @@ export function printMixedHIR( case "while": case "for": case "unsupported": - case "goto": { + case "goto": + case "do-while": { const terminal = printTerminal(value); if (Array.isArray(terminal)) { return terminal.join("; "); @@ -184,6 +185,12 @@ export function printTerminal(terminal: Terminal): Array | string { value = output; break; } + case "do-while": { + value = `[${terminal.id}] DoWhile loop=${`bb${terminal.loop}`} test=bb${ + terminal.test + } fallthrough=${`bb${terminal.fallthrough}`}`; + break; + } case "while": { value = `[${terminal.id}] While test=bb${terminal.test} loop=${ terminal.loop !== null ? `bb${terminal.loop}` : "" diff --git a/compiler/forget/src/HIR/visitors.ts b/compiler/forget/src/HIR/visitors.ts index c8485f3d92..2da106e5e3 100644 --- a/compiler/forget/src/HIR/visitors.ts +++ b/compiler/forget/src/HIR/visitors.ts @@ -366,6 +366,19 @@ export function mapTerminalSuccessors( case "throw": { return terminal; } + case "do-while": { + const loop = fn(terminal.loop); + const test = fn(terminal.test); + const fallthrough = fn(terminal.fallthrough); + return { + kind: "do-while", + loc: terminal.loc, + test, + loop, + fallthrough, + id: makeInstructionId(0), + }; + } case "while": { const test = fn(terminal.test); const loop = fn(terminal.loop); @@ -446,6 +459,10 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable { case "throw": { break; } + case "do-while": { + yield terminal.loop; + break; + } case "while": { yield terminal.test; break; @@ -497,6 +514,7 @@ export function mapTerminalOperands( } case "ternary": case "logical": + case "do-while": case "while": case "for": case "goto": @@ -542,6 +560,7 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable { } case "ternary": case "logical": + case "do-while": case "while": case "for": case "goto": diff --git a/compiler/forget/src/Optimization/ConstantPropagation.ts b/compiler/forget/src/Optimization/ConstantPropagation.ts index a6e46a8483..77a5504c5d 100644 --- a/compiler/forget/src/Optimization/ConstantPropagation.ts +++ b/compiler/forget/src/Optimization/ConstantPropagation.ts @@ -22,6 +22,7 @@ import { reversePostorderBlocks, shrink, } from "../HIR"; +import { removeDeadDoWhileStatements } from "../HIR/HIRBuilder"; import { eliminateRedundantPhi } from "../SSA"; /** @@ -49,6 +50,7 @@ export function constantPropagation(fn: HIRFunction): void { shrink(fn.body); reversePostorderBlocks(fn.body); removeUnreachableFallthroughs(fn.body); + removeDeadDoWhileStatements(fn.body); markInstructionIds(fn.body); markPredecessors(fn.body); diff --git a/compiler/forget/src/ReactiveScopes/BuildReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/BuildReactiveFunction.ts index 0abbdfcb9a..28bcf0d542 100644 --- a/compiler/forget/src/ReactiveScopes/BuildReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/BuildReactiveFunction.ts @@ -231,6 +231,55 @@ class Driver { } break; } + case "do-while": { + const fallthroughId = !this.cx.isScheduled(terminal.fallthrough) + ? terminal.fallthrough + : null; + const loopId = + !this.cx.isScheduled(terminal.loop) && + terminal.loop !== terminal.fallthrough + ? terminal.loop + : null; + const scheduleId = this.cx.scheduleLoop( + terminal.fallthrough, + terminal.test, + terminal.loop + ); + scheduleIds.push(scheduleId); + + let loopBody: ReactiveBlock; + if (loopId) { + loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!); + } else { + const break_ = this.visitBreak(terminal.loop, null); + invariant( + break_ !== null, + "If loop body is already scheduled it must be a break" + ); + loopBody = [break_]; + } + + const testValue = this.visitValueBlock( + terminal.test, + terminal.loc + ).value; + + this.cx.unscheduleAll(scheduleIds); + blockValue.push({ + kind: "terminal", + terminal: { + kind: "do-while", + test: testValue, + loop: loopBody, + id: terminal.id, + }, + label: fallthroughId, + }); + if (fallthroughId !== null) { + this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue); + } + break; + } case "while": { const fallthroughId = terminal.fallthrough !== null && diff --git a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts index 035775c8a6..bbb2a72867 100644 --- a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -340,6 +340,10 @@ function codegenTerminal( case "throw": { return t.throwStatement(codegenPlace(cx, terminal.value)); } + case "do-while": { + const test = codegenInstructionValue(cx, terminal.test); + return t.doWhileStatement(test, codegenBlock(cx, terminal.loop)); + } case "while": { const test = codegenInstructionValue(cx, terminal.test); return t.whileStatement(test, codegenBlock(cx, terminal.loop)); diff --git a/compiler/forget/src/ReactiveScopes/FlattenReactiveLoops.ts b/compiler/forget/src/ReactiveScopes/FlattenReactiveLoops.ts index 21d00a0c8a..a29e58a4ce 100644 --- a/compiler/forget/src/ReactiveScopes/FlattenReactiveLoops.ts +++ b/compiler/forget/src/ReactiveScopes/FlattenReactiveLoops.ts @@ -46,6 +46,7 @@ class Transform extends ReactiveFunctionTransform { ): void { switch (stmt.terminal.kind) { // Loop terminals flatten nested scopes + case "do-while": case "while": case "for": { this.traverseTerminal(stmt, true); diff --git a/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts b/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts index 2f1652e0fb..0c81b7fdff 100644 --- a/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts +++ b/compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts @@ -578,6 +578,11 @@ function visit(context: Context, block: ReactiveBlock): void { }); break; } + case "do-while": { + visit(context, terminal.loop); + visitReactiveValue(context, terminal.test); + break; + } case "while": { visitReactiveValue(context, terminal.test); context.enterConditional(() => { diff --git a/compiler/forget/src/ReactiveScopes/visitors.ts b/compiler/forget/src/ReactiveScopes/visitors.ts index 8abaa361b4..7ecdf0b1c3 100644 --- a/compiler/forget/src/ReactiveScopes/visitors.ts +++ b/compiler/forget/src/ReactiveScopes/visitors.ts @@ -106,6 +106,11 @@ export class ReactiveFunctionVisitor { this.visitBlock(terminal.loop, state); break; } + case "do-while": { + this.visitBlock(terminal.loop, state); + this.visitValue(terminal.id, terminal.test, state); + break; + } case "while": { this.visitValue(terminal.id, terminal.test, state); this.visitBlock(terminal.loop, state); @@ -309,6 +314,7 @@ export function mapTerminalBlocks( terminal.loop = fn(terminal.loop); break; } + case "do-while": case "while": { terminal.loop = fn(terminal.loop); break; diff --git a/compiler/forget/src/Utils/VisualizeHIRMermaid.ts b/compiler/forget/src/Utils/VisualizeHIRMermaid.ts index 4ff708e79c..da2e50571f 100644 --- a/compiler/forget/src/Utils/VisualizeHIRMermaid.ts +++ b/compiler/forget/src/Utils/VisualizeHIRMermaid.ts @@ -107,6 +107,10 @@ function printTerminalLabel(terminal: Terminal): string { buffer.push("Ternary"); break; } + case "do-while": { + buffer.push("DoWhile"); + break; + } case "while": buffer.push("While"); break; @@ -174,6 +178,12 @@ function printTerminalArrows(blockId: BlockId, terminal: Terminal): string { } break; } + case "do-while": { + buffer.push(printJumpArrow(blockId, terminal.loop, "loop")); + buffer.push(printJumpArrow(blockId, terminal.test, "test")); + buffer.push(printJumpArrow(blockId, terminal.fallthrough, "fallthrough")); + break; + } case "while": { buffer.push(printJumpArrow(blockId, terminal.test, "test")); buffer.push(printJumpArrow(blockId, terminal.loop, "loop")); diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-break.expect.md b/compiler/forget/src/__tests__/fixtures/hir/do-while-break.expect.md new file mode 100644 index 0000000000..60cc7e0b91 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-break.expect.md @@ -0,0 +1,22 @@ + +## Input + +```javascript +function Component(props) { + do { + break; + } while (props.cond); + return props; +} + +``` + +## Code + +```javascript +function Component(props) { + return props; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-break.js b/compiler/forget/src/__tests__/fixtures/hir/do-while-break.js new file mode 100644 index 0000000000..5a311fe9a3 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-break.js @@ -0,0 +1,6 @@ +function Component(props) { + do { + break; + } while (props.cond); + return props; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-compound-test.expect.md b/compiler/forget/src/__tests__/fixtures/hir/do-while-compound-test.expect.md new file mode 100644 index 0000000000..109b58a107 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-compound-test.expect.md @@ -0,0 +1,40 @@ + +## Input + +```javascript +function Component(props) { + let x = [1, 2, 3]; + let ret = []; + do { + let item = x.pop(); + ret.push(item * 2); + } while (x.length && props.cond); + return ret; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props; + let ret; + if (c_0) { + const x = [1, 2, 3]; + ret = []; + do { + const item = x.pop(); + ret.push(item * 2); + } while (x.length && props.cond); + $[0] = props; + $[1] = ret; + } else { + ret = $[1]; + } + return ret; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-compound-test.js b/compiler/forget/src/__tests__/fixtures/hir/do-while-compound-test.js new file mode 100644 index 0000000000..3588f94706 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-compound-test.js @@ -0,0 +1,9 @@ +function Component(props) { + let x = [1, 2, 3]; + let ret = []; + do { + let item = x.pop(); + ret.push(item * 2); + } while (x.length && props.cond); + return ret; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-conditional-break.expect.md b/compiler/forget/src/__tests__/fixtures/hir/do-while-conditional-break.expect.md new file mode 100644 index 0000000000..f2759479b8 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-conditional-break.expect.md @@ -0,0 +1,43 @@ + +## Input + +```javascript +function Component(props) { + let x = [0, 1, 2, 3]; + do { + if (x === 0) { + break; + } + mutate(x); + } while (props.cond); + return x; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(2); + const c_0 = $[0] !== props.cond; + let x; + if (c_0) { + x = [0, 1, 2, 3]; + do { + if (x === 0) { + break; + } + + mutate(x); + } while (props.cond); + $[0] = props.cond; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-conditional-break.js b/compiler/forget/src/__tests__/fixtures/hir/do-while-conditional-break.js new file mode 100644 index 0000000000..c64286efc3 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-conditional-break.js @@ -0,0 +1,10 @@ +function Component(props) { + let x = [0, 1, 2, 3]; + do { + if (x === 0) { + break; + } + mutate(x); + } while (props.cond); + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-continue.expect.md b/compiler/forget/src/__tests__/fixtures/hir/do-while-continue.expect.md new file mode 100644 index 0000000000..1584a36a0c --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-continue.expect.md @@ -0,0 +1,46 @@ + +## Input + +```javascript +function Component() { + const x = [0, 1, 2, 3]; + const ret = []; + do { + const item = x.pop(); + if (item === 0) { + continue; + } + ret.push(item / 2); + } while (x.length); + + return ret; +} + +``` + +## Code + +```javascript +function Component() { + const $ = React.unstable_useMemoCache(1); + let ret; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + const x = [0, 1, 2, 3]; + ret = []; + do { + const item = x.pop(); + if (item === 0) { + continue; + } + + ret.push(item / 2); + } while (x.length); + $[0] = ret; + } else { + ret = $[0]; + } + return ret; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-continue.js b/compiler/forget/src/__tests__/fixtures/hir/do-while-continue.js new file mode 100644 index 0000000000..a4792f7891 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-continue.js @@ -0,0 +1,13 @@ +function Component() { + const x = [0, 1, 2, 3]; + const ret = []; + do { + const item = x.pop(); + if (item === 0) { + continue; + } + ret.push(item / 2); + } while (x.length); + + return ret; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-early-unconditional-break.expect.md b/compiler/forget/src/__tests__/fixtures/hir/do-while-early-unconditional-break.expect.md new file mode 100644 index 0000000000..bd02429583 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-early-unconditional-break.expect.md @@ -0,0 +1,34 @@ + +## Input + +```javascript +function Component(props) { + let x = [1, 2, 3]; + do { + mutate(x); + break; + } while (props.cond); + return x; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(1); + let x; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + x = [1, 2, 3]; + + mutate(x); + $[0] = x; + } else { + x = $[0]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-early-unconditional-break.js b/compiler/forget/src/__tests__/fixtures/hir/do-while-early-unconditional-break.js new file mode 100644 index 0000000000..764f2d4be9 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-early-unconditional-break.js @@ -0,0 +1,8 @@ +function Component(props) { + let x = [1, 2, 3]; + do { + mutate(x); + break; + } while (props.cond); + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-simple.expect.md b/compiler/forget/src/__tests__/fixtures/hir/do-while-simple.expect.md new file mode 100644 index 0000000000..ae8022e626 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-simple.expect.md @@ -0,0 +1,38 @@ + +## Input + +```javascript +function Component() { + let x = [1, 2, 3]; + let ret = []; + do { + let item = x.pop(); + ret.push(item * 2); + } while (x.length); + return ret; +} + +``` + +## Code + +```javascript +function Component() { + const $ = React.unstable_useMemoCache(1); + let ret; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + const x = [1, 2, 3]; + ret = []; + do { + const item = x.pop(); + ret.push(item * 2); + } while (x.length); + $[0] = ret; + } else { + ret = $[0]; + } + return ret; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/do-while-simple.js b/compiler/forget/src/__tests__/fixtures/hir/do-while-simple.js new file mode 100644 index 0000000000..b1d79aaaf5 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/do-while-simple.js @@ -0,0 +1,9 @@ +function Component() { + let x = [1, 2, 3]; + let ret = []; + do { + let item = x.pop(); + ret.push(item * 2); + } while (x.length); + return ret; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md b/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md index 8fb7d6d9cd..c004bd6556 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md @@ -50,8 +50,6 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) { for (let x in { a: 1 }) { } - do {} while (i < 3); - let updateIdentifier = 0; --updateIdentifier; ++updateIdentifier; @@ -333,97 +331,88 @@ let moduleLocal = false; | ^ 47 | } 48 | - 49 | do {} while (i < 3); + 49 | let updateIdentifier = 0; -[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle DoWhileStatement statements - 47 | } +[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression 48 | -> 49 | do {} while (i < 3); - | ^^^^^^^^^^^^^^^^^^^^ - 50 | - 51 | let updateIdentifier = 0; - 52 | --updateIdentifier; + 49 | let updateIdentifier = 0; +> 50 | --updateIdentifier; + | ^^^^^^^^^^^^^^^^^^ + 51 | ++updateIdentifier; + 52 | updateIdentifier.y++; + 53 | updateIdentifier.y--; [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression - 50 | - 51 | let updateIdentifier = 0; -> 52 | --updateIdentifier; + 49 | let updateIdentifier = 0; + 50 | --updateIdentifier; +> 51 | ++updateIdentifier; | ^^^^^^^^^^^^^^^^^^ - 53 | ++updateIdentifier; - 54 | updateIdentifier.y++; - 55 | updateIdentifier.y--; - -[ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression - 51 | let updateIdentifier = 0; - 52 | --updateIdentifier; -> 53 | ++updateIdentifier; - | ^^^^^^^^^^^^^^^^^^ - 54 | updateIdentifier.y++; - 55 | updateIdentifier.y--; - 56 | + 52 | updateIdentifier.y++; + 53 | updateIdentifier.y--; + 54 | [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument - 52 | --updateIdentifier; - 53 | ++updateIdentifier; -> 54 | updateIdentifier.y++; + 50 | --updateIdentifier; + 51 | ++updateIdentifier; +> 52 | updateIdentifier.y++; | ^^^^^^^^^^^^^^^^^^^^ - 55 | updateIdentifier.y--; - 56 | - 57 | switch (i) { + 53 | updateIdentifier.y--; + 54 | + 55 | switch (i) { [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument - 53 | ++updateIdentifier; - 54 | updateIdentifier.y++; -> 55 | updateIdentifier.y--; + 51 | ++updateIdentifier; + 52 | updateIdentifier.y++; +> 53 | updateIdentifier.y--; | ^^^^^^^^^^^^^^^^^^^^ - 56 | - 57 | switch (i) { - 58 | case 1 + 1: { + 54 | + 55 | switch (i) { + 56 | case 1 + 1: { [ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported - 60 | case foo(): { - 61 | } -> 62 | case x.y: { + 58 | case foo(): { + 59 | } +> 60 | case x.y: { | ^^^ - 63 | } - 64 | default: { - 65 | } - -[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported - 58 | case 1 + 1: { - 59 | } -> 60 | case foo(): { - | ^^^^^ 61 | } - 62 | case x.y: { + 62 | default: { 63 | } [ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported - 56 | - 57 | switch (i) { -> 58 | case 1 + 1: { + 56 | case 1 + 1: { + 57 | } +> 58 | case foo(): { | ^^^^^ 59 | } - 60 | case foo(): { + 60 | case x.y: { 61 | } +[ReactForget] TodoError: (BuildHIR::lowerStatement) Switch case test values must be identifiers or primitives, compound values are not yet supported + 54 | + 55 | switch (i) { +> 56 | case 1 + 1: { + | ^^^^^ + 57 | } + 58 | case foo(): { + 59 | } + [ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported. - 67 | - 68 | // Cannot assign to globals -> 69 | someUnknownGlobal = true; + 65 | + 66 | // Cannot assign to globals +> 67 | someUnknownGlobal = true; | ^^^^^^^^^^^^^^^^^ - 70 | moduleLocal = true; - 71 | } - 72 | + 68 | moduleLocal = true; + 69 | } + 70 | [ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported. - 68 | // Cannot assign to globals - 69 | someUnknownGlobal = true; -> 70 | moduleLocal = true; + 66 | // Cannot assign to globals + 67 | someUnknownGlobal = true; +> 68 | moduleLocal = true; | ^^^^^^^^^^^ - 71 | } - 72 | - 73 | let moduleLocal = false; + 69 | } + 70 | + 71 | let moduleLocal = false; ``` \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js b/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js index 77452e1bce..3c94e92198 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js +++ b/compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js @@ -46,8 +46,6 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) { for (let x in { a: 1 }) { } - do {} while (i < 3); - let updateIdentifier = 0; --updateIdentifier; ++updateIdentifier;