@samitouri / QOS-React-2 / commits / 61462a43bc

Add support for ForOf statements

Teaches Forget to compile simple ForOf statements, where the init comprises of a variable declaration with an identifier or destructure.

Lauren Tan committed Mar 31, 2023 at 17:55 UTC 61462a43bc53de1d7a19d49f62bfe108a03cca45
35 files changed +768 -110
compiler/forget/src/HIR/BuildHIR.ts
+77 -1
@@ -712,7 +712,83 @@ function lowerStatement(
712 lowerStatement(builder, desugared.at(0)!);
713 return;
714 }
715 - case "ForOfStatement":
715 + case "ForOfStatement": {
716 + const stmt = stmtPath as NodePath<t.ForOfStatement>;
717 + const continuationBlock = builder.reserve("block");
718 + const initBlock = builder.reserve("loop");
719 +
720 + const loopBlock = builder.enter("block", (_blockId) => {
721 + return builder.loop(label, initBlock.id, continuationBlock.id, () => {
722 + lowerStatement(builder, stmt.get("body"));
723 + return {
724 + kind: "goto",
725 + block: initBlock.id,
726 + variant: GotoVariant.Continue,
727 + id: makeInstructionId(0),
728 + };
729 + });
730 + });
731 +
732 + const loc = stmt.node.loc ?? GeneratedSource;
733 + const value = lowerExpressionToTemporary(builder, stmt.get("right"));
734 + builder.terminateWithContinuation(
735 + {
736 + kind: "for-of",
737 + loc,
738 + init: initBlock.id,
739 + loop: loopBlock,
740 + fallthrough: continuationBlock.id,
741 + id: makeInstructionId(0),
742 + },
743 + initBlock
744 + );
745 +
746 + // The init of a ForOf statement is compound over a left (VariableDeclaration | LVal) and
747 + // right (Expression), so we synthesize a new InstrValue and assignment (potentially multiple
748 + // instructions when we handle other syntax like Patterns)
749 + const left = stmt.get("left");
750 + const leftLoc = left.node.loc ?? GeneratedSource;
751 + let test: Place;
752 + if (left.isVariableDeclaration()) {
753 + const declarations = left.get("declarations");
754 + invariant(
755 + declarations.length === 1,
756 + `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`
757 + );
758 + const id = declarations[0].get("id");
759 + const nextIterableOf = lowerValueToTemporary(builder, {
760 + kind: "NextIterableOf",
761 + loc: leftLoc,
762 + value,
763 + });
764 + const assign = lowerAssignment(
765 + builder,
766 + leftLoc,
767 + InstructionKind.Let,
768 + id,
769 + nextIterableOf
770 + );
771 + test = lowerValueToTemporary(builder, assign);
772 + } else {
773 + builder.errors.push({
774 + reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForOfStatement`,
775 + severity: ErrorSeverity.Todo,
776 + nodePath: left,
777 + });
778 + return;
779 + }
780 + builder.terminateWithContinuation(
781 + {
782 + id: makeInstructionId(0),
783 + kind: "branch",
784 + test,
785 + consequent: loopBlock,
786 + alternate: continuationBlock.id,
787 + },
788 + continuationBlock
789 + );
790 + return;
791 + }
792 case "ForInStatement":
793 case "ClassDeclaration":
794 case "DebuggerStatement":
compiler/forget/src/HIR/HIR.ts
+22
@@ -134,6 +134,7 @@ export type ReactiveTerminal =
134 | ReactiveDoWhileTerminal
135 | ReactiveWhileTerminal
136 | ReactiveForTerminal
137 + | ReactiveForOfTerminal
138 | ReactiveIfTerminal;
139
140 export type ReactiveBreakTerminal = {
@@ -187,6 +188,12 @@ export type ReactiveForTerminal = {
188 loop: ReactiveBlock;
189 id: InstructionId;
190 };
191 +export type ReactiveForOfTerminal = {
192 + kind: "for-of";
193 + init: ReactiveValue;
194 + loop: ReactiveBlock;
195 + id: InstructionId;
196 +};
197 export type ReactiveIfTerminal = {
198 kind: "if";
199 test: Place;
@@ -257,6 +264,7 @@ export type Terminal =
264 | BranchTerminal
265 | SwitchTerminal
266 | ForTerminal
267 + | ForOfTerminal
268 | DoWhileTerminal
269 | WhileTerminal
270 | LogicalTerminal
@@ -345,6 +353,15 @@ export type ForTerminal = {
353 id: InstructionId;
354 };
355
356 +export type ForOfTerminal = {
357 + kind: "for-of";
358 + loc: SourceLocation;
359 + init: BlockId;
360 + loop: BlockId;
361 + fallthrough: BlockId;
362 + id: InstructionId;
363 +};
364 +
365 export type LogicalTerminal = {
366 kind: "logical";
367 operator: t.LogicalExpression["operator"];
@@ -638,6 +655,11 @@ export type InstructionValue =
655 value: Place;
656 loc: SourceLocation;
657 }
658 + | {
659 + kind: "NextIterableOf";
660 + value: Place; // the collection
661 + loc: SourceLocation;
662 + }
663 /**
664 * Catch-all for statements such as type imports, nested class declarations, etc
665 * which are not directly represented, but included for completeness and to allow
compiler/forget/src/HIR/HIRBuilder.ts
+4
@@ -661,6 +661,10 @@ export function reversePostorderBlocks(func: HIR): void {
661 visit(terminal.init);
662 break;
663 }
664 + case "for-of": {
665 + visit(terminal.init);
666 + break;
667 + }
668 case "unsupported": {
669 break;
670 }
compiler/forget/src/HIR/PrintHIR.ts
+10 -1
@@ -91,7 +91,8 @@ export function printMixedHIR(
91 case "for":
92 case "unsupported":
93 case "goto":
94 - case "do-while": {
94 + case "do-while":
95 + case "for-of": {
96 const terminal = printTerminal(value);
97 if (Array.isArray(terminal)) {
98 return terminal.join("; ");
@@ -208,6 +209,10 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
209 value = `[${terminal.id}] For init=bb${terminal.init} test=bb${terminal.test} loop=bb${terminal.loop} update=bb${terminal.update} fallthrough=bb${terminal.fallthrough}`;
210 break;
211 }
212 + case "for-of": {
213 + value = `[${terminal.id}] ForOf init=bb${terminal.init} loop=bb${terminal.loop} fallthrough=bb${terminal.fallthrough}`;
214 + break;
215 + }
216 case "unsupported": {
217 value = `Unsupported`;
218 break;
@@ -452,6 +457,10 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
457 value = `Await ${printPlace(instrValue.value)}`;
458 break;
459 }
460 + case "NextIterableOf": {
461 + value = `NextIterableOf ${printPlace(instrValue.value)}`;
462 + break;
463 + }
464 default: {
465 assertExhaustive(
466 instrValue,
compiler/forget/src/HIR/visitors.ts
+27
@@ -174,6 +174,10 @@ export function* eachInstructionValueOperand(
174 yield instrValue.value;
175 break;
176 }
177 + case "NextIterableOf": {
178 + yield instrValue.value;
179 + break;
180 + }
181 case "RegExpLiteral":
182 case "LoadGlobal":
183 case "UnsupportedNode":
@@ -431,6 +435,10 @@ export function mapInstructionOperands(
435 instrValue.value = fn(instrValue.value);
436 break;
437 }
438 + case "NextIterableOf": {
439 + instrValue.value = fn(instrValue.value);
440 + break;
441 + }
442 case "RegExpLiteral":
443 case "LoadGlobal":
444 case "UnsupportedNode":
@@ -638,6 +646,19 @@ export function mapTerminalSuccessors(
646 id: makeInstructionId(0),
647 };
648 }
649 + case "for-of": {
650 + const init = fn(terminal.init);
651 + const loop = fn(terminal.loop);
652 + const fallthrough = fn(terminal.fallthrough);
653 + return {
654 + kind: "for-of",
655 + loc: terminal.loc,
656 + init,
657 + loop,
658 + fallthrough,
659 + id: makeInstructionId(0),
660 + };
661 + }
662 case "unsupported": {
663 return terminal;
664 }
@@ -701,6 +722,10 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
722 yield terminal.init;
723 break;
724 }
725 + case "for-of": {
726 + yield terminal.init;
727 + break;
728 + }
729 case "unsupported":
730 break;
731 default: {
@@ -748,6 +773,7 @@ export function mapTerminalOperands(
773 case "do-while":
774 case "while":
775 case "for":
776 + case "for-of":
777 case "goto":
778 case "unsupported": {
779 // no-op
@@ -795,6 +821,7 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
821 case "do-while":
822 case "while":
823 case "for":
824 + case "for-of":
825 case "goto":
826 case "unsupported": {
827 // no-op
compiler/forget/src/Inference/InferReferenceEffects.ts
+6
@@ -874,6 +874,12 @@ function inferBlock(
874 }
875 continue;
876 }
877 + case "NextIterableOf": {
878 + effectKind = Effect.Capture;
879 + lvalueEffect = Effect.Store;
880 + valueKind = ValueKind.Mutable;
881 + break;
882 + }
883 default: {
884 assertExhaustive(instrValue, "Unexpected instruction kind");
885 }
compiler/forget/src/Optimization/DeadCodeElimination.ts
+5
@@ -208,6 +208,11 @@ function pruneableValue(value: InstructionValue, state: State): boolean {
208 // Potentially safe to prune, since they should just be creating new values
209 return false;
210 }
211 + case "NextIterableOf": {
212 + // Technically a NextIterableOf will never be unused because it's always used later by
213 + // another StoreLocal or Destructure instruction, but conceptually we can't prune
214 + return false;
215 + }
216 case "RegExpLiteral":
217 case "LoadGlobal":
218 case "ArrayExpression":
compiler/forget/src/ReactiveScopes/BuildReactiveFunction.ts
+73
@@ -417,6 +417,79 @@ class Driver {
417 }
418 break;
419 }
420 + case "for-of": {
421 + const loopId =
422 + !this.cx.isScheduled(terminal.loop) &&
423 + terminal.loop !== terminal.fallthrough
424 + ? terminal.loop
425 + : null;
426 +
427 + const fallthroughId =
428 + terminal.fallthrough !== null &&
429 + !this.cx.isScheduled(terminal.fallthrough)
430 + ? terminal.fallthrough
431 + : null;
432 +
433 + const scheduleId = this.cx.scheduleLoop(
434 + terminal.fallthrough,
435 + terminal.init,
436 + terminal.loop
437 + );
438 + scheduleIds.push(scheduleId);
439 +
440 + const init = this.visitValueBlock(terminal.init, terminal.loc);
441 + const initBlock = this.cx.ir.blocks.get(init.block)!;
442 + let initValue = init.value;
443 + if (initValue.kind === "SequenceExpression") {
444 + const last = initBlock.instructions.at(-1)!;
445 + initValue.instructions.push(last);
446 + initValue.value = {
447 + kind: "Primitive",
448 + value: undefined,
449 + loc: terminal.loc,
450 + };
451 + } else {
452 + initValue = {
453 + kind: "SequenceExpression",
454 + instructions: [initBlock.instructions.at(-1)!],
455 + id: terminal.id,
456 + loc: terminal.loc,
457 + value: {
458 + kind: "Primitive",
459 + value: undefined,
460 + loc: terminal.loc,
461 + },
462 + };
463 + }
464 +
465 + let loopBody: ReactiveBlock;
466 + if (loopId) {
467 + loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
468 + } else {
469 + const break_ = this.visitBreak(terminal.loop, null);
470 + invariant(
471 + break_ !== null,
472 + "If loop body is already scheduled it must be a break"
473 + );
474 + loopBody = [break_];
475 + }
476 +
477 + this.cx.unscheduleAll(scheduleIds);
478 + blockValue.push({
479 + kind: "terminal",
480 + terminal: {
481 + kind: "for-of",
482 + init: initValue,
483 + loop: loopBody,
484 + id: terminal.id,
485 + },
486 + label: fallthroughId,
487 + });
488 + if (fallthroughId !== null) {
489 + this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
490 + }
491 + break;
492 + }
493 case "branch": {
494 let consequent: ReactiveBlock | null = null;
495 if (this.cx.isScheduled(terminal.consequent)) {
compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+64
@@ -310,6 +310,66 @@ function codegenTerminal(
310 codegenBlock(cx, terminal.loop)
311 );
312 }
313 + case "for-of": {
314 + if (terminal.init.kind !== "SequenceExpression") {
315 + CompilerError.invariant(
316 + `Expected a sequence expression init for ForOf, got: ${terminal.init.kind}`,
317 + terminal.init.loc
318 + );
319 + }
320 + if (terminal.init.instructions.length !== 2) {
321 + CompilerError.todo(
322 + "Support non-trivial ForOf inits",
323 + terminal.init.loc
324 + );
325 + }
326 + const iterableCollection = terminal.init.instructions[0];
327 + const iterableItem = terminal.init.instructions[1];
328 + let lval: t.LVal;
329 + switch (iterableItem.value.kind) {
330 + case "StoreLocal": {
331 + lval = codegenLValue(iterableItem.value.lvalue.place);
332 + break;
333 + }
334 + case "Destructure": {
335 + lval = codegenLValue(iterableItem.value.lvalue.pattern);
336 + break;
337 + }
338 + default:
339 + CompilerError.invariant(
340 + `Expected a StoreLocal or Destructure to be assigned to the collection, got: ${iterableItem.value.kind}`,
341 + iterableItem.value.loc
342 + );
343 + }
344 + let varDeclKind: "const" | "let";
345 + switch (iterableItem.value.lvalue.kind) {
346 + case InstructionKind.Const:
347 + varDeclKind = "const" as const;
348 + break;
349 + case InstructionKind.Let:
350 + varDeclKind = "let" as const;
351 + break;
352 + case InstructionKind.Reassign:
353 + CompilerError.invariant(
354 + "Destructure should never be Reassign as it would be an Object/ArrayPattern",
355 + iterableItem.loc
356 + );
357 + default:
358 + assertExhaustive(
359 + iterableItem.value.lvalue.kind,
360 + `Unhandled lvalue kind: ${iterableItem.value.lvalue.kind}`
361 + );
362 + }
363 + return t.forOfStatement(
364 + // Special handling here since we only want the VariableDeclarators without any inits
365 + // This needs to be updated when we handle non-trivial ForOf inits
366 + createVariableDeclaration(iterableItem.value.loc, varDeclKind, [
367 + t.variableDeclarator(lval, null),
368 + ]),
369 + codegenInstructionValue(cx, iterableCollection.value),
370 + codegenBlock(cx, terminal.loop)
371 + );
372 + }
373 case "if": {
374 return t.ifStatement(
375 codegenPlace(cx, terminal.test),
@@ -923,6 +983,10 @@ function codegenInstructionValue(
983 value = t.awaitExpression(codegenPlace(cx, instrValue.value));
984 break;
985 }
986 + case "NextIterableOf": {
987 + value = codegenPlace(cx, instrValue.value);
988 + break;
989 + }
990 case "DeclareLocal":
991 case "Destructure":
992 case "StoreLocal": {
compiler/forget/src/ReactiveScopes/FlattenReactiveLoops.ts
+2 -1
@@ -48,7 +48,8 @@ class Transform extends ReactiveFunctionTransform<boolean> {
48 // Loop terminals flatten nested scopes
49 case "do-while":
50 case "while":
51 - case "for": {
51 + case "for":
52 + case "for-of": {
53 this.traverseTerminal(stmt, true);
54 break;
55 }
compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts
+2 -1
@@ -233,7 +233,8 @@ function mayAllocate(value: InstructionValue): boolean {
233 case "JSXText":
234 case "UnaryExpression":
235 case "TemplateLiteral":
236 - case "Primitive": {
236 + case "Primitive":
237 + case "NextIterableOf": {
238 return false;
239 }
240 case "RegExpLiteral":
compiler/forget/src/ReactiveScopes/PrintReactiveFunction.ts
+8
@@ -234,6 +234,14 @@ function printTerminal(writer: Writer, terminal: ReactiveTerminal): void {
234 writer.writeLine("}");
235 break;
236 }
237 + case "for-of": {
238 + writer.writeLine(`[${terminal.id}] for-of (`);
239 + printReactiveValue(writer, terminal.init);
240 + writer.writeLine(") {");
241 + printReactiveInstructions(writer, terminal.loop);
242 + writer.writeLine("}");
243 + break;
244 + }
245 case "throw": {
246 writer.writeLine(`[${terminal.id}] throw ${printPlace(terminal.value)}`);
247 break;
compiler/forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+7
@@ -379,6 +379,13 @@ function visit(context: Context, block: ReactiveBlock): void {
379 });
380 break;
381 }
382 + case "for-of": {
383 + visitReactiveValue(context, terminal.id, terminal.init);
384 + context.enterConditional(() => {
385 + visit(context, terminal.loop);
386 + });
387 + break;
388 + }
389 case "do-while": {
390 visit(context, terminal.loop);
391 context.enterConditional(() => {
compiler/forget/src/ReactiveScopes/PruneNonEscapingScopes.ts
+2 -1
@@ -425,7 +425,8 @@ function computeMemoizationInputs(
425 };
426 }
427 case "Await":
428 - case "TypeCastExpression": {
428 + case "TypeCastExpression":
429 + case "NextIterableOf": {
430 return {
431 // Indirection for the inner value, memoized if the value is
432 lvalues:
compiler/forget/src/ReactiveScopes/visitors.ts
+9
@@ -112,6 +112,11 @@ export class ReactiveFunctionVisitor<TState = void> {
112 this.visitBlock(terminal.loop, state);
113 break;
114 }
115 + case "for-of": {
116 + this.visitValue(terminal.id, terminal.init, state);
117 + this.visitBlock(terminal.loop, state);
118 + break;
119 + }
120 case "do-while": {
121 this.visitBlock(terminal.loop, state);
122 this.visitValue(terminal.id, terminal.test, state);
@@ -324,6 +329,10 @@ export function mapTerminalBlocks(
329 terminal.loop = fn(terminal.loop);
330 break;
331 }
332 + case "for-of": {
333 + terminal.loop = fn(terminal.loop);
334 + break;
335 + }
336 case "do-while":
337 case "while": {
338 terminal.loop = fn(terminal.loop);
compiler/forget/src/SSA/LeaveSSA.ts
+8 -5
@@ -190,7 +190,7 @@ export function leaveSSA(fn: HIRFunction): void {
190 originalLVal === undefined ||
191 originalLVal.lvalue === value.lvalue
192 ) {
193 - if (originalLVal === undefined && block.kind !== "block") {
193 + if (originalLVal === undefined && block.kind === "value") {
194 CompilerError.invariant(
195 `TODO: Handle reassignment in a value block where the original declaration was removed by dead code elimination (DCE)`,
196 place.loc
@@ -294,7 +294,8 @@ export function leaveSSA(fn: HIRFunction): void {
294 (terminal.kind === "if" ||
295 terminal.kind === "switch" ||
296 terminal.kind === "while" ||
297 - terminal.kind === "for") &&
297 + terminal.kind === "for" ||
298 + terminal.kind === "for-of") &&
299 terminal.fallthrough !== null
300 ) {
301 const fallthrough = fn.body.blocks.get(terminal.fallthrough)!;
@@ -307,7 +308,7 @@ export function leaveSSA(fn: HIRFunction): void {
308 const loop = fn.body.blocks.get(terminal.loop)!;
309 pushPhis(loop);
310 }
310 - if (terminal.kind === "for") {
311 + if (terminal.kind === "for" || terminal.kind === "for-of") {
312 const init = fn.body.blocks.get(terminal.init)!;
313 pushPhis(init);
314
@@ -333,8 +334,10 @@ export function leaveSSA(fn: HIRFunction): void {
334 }
335 }
336
336 - const update = fn.body.blocks.get(terminal.update)!;
337 - pushPhis(update);
337 + if (terminal.kind === "for") {
338 + const update = fn.body.blocks.get(terminal.update)!;
339 + pushPhis(update);
340 + }
341 }
342 if (
343 terminal.kind === "logical" ||
compiler/forget/src/TypeInference/InferTypes.ts
+1
@@ -202,6 +202,7 @@ function* generateInstructionTypes(
202 case "TaggedTemplateExpression":
203 case "TemplateLiteral":
204 case "Await":
205 + case "NextIterableOf":
206 case "UnsupportedNode":
207 break;
208 default:
compiler/forget/src/Utils/VisualizeHIRMermaid.ts
+8
@@ -121,6 +121,9 @@ function printTerminalLabel(terminal: Terminal): string {
121 case "for":
122 buffer.push("For");
123 break;
124 + case "for-of":
125 + buffer.push("ForOf");
126 + break;
127 case "unsupported": {
128 break;
129 }
@@ -207,6 +210,11 @@ function printTerminalArrows(blockId: BlockId, terminal: Terminal): string {
210 buffer.push(printJumpArrow(blockId, terminal.fallthrough, "fallthrough"));
211 break;
212 }
213 + case "for-of": {
214 + buffer.push(printJumpArrow(blockId, terminal.init, "init"));
215 + buffer.push(printJumpArrow(blockId, terminal.loop, "loop"));
216 + break;
217 + }
218 case "throw":
219 case "return":
220 case "unsupported": {
compiler/forget/src/__tests__/fixtures/compiler/error.ssa-for-of.expect.md deleted
-32
@@ -1,32 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -function foo(cond) {
6 - let items = [];
7 - for (const item of items) {
8 - let y = 0;
9 - if (cond) {
10 - y = 1;
11 - }
12 - }
13 - return items;
14 -}
15 -
16 -```
17 -
18 -
19 -## Error
20 -
21 -```
22 -[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForOfStatement statements
23 - 1 | function foo(cond) {
24 - 2 | let items = [];
25 -> 3 | for (const item of items) {
26 - | ^
27 - 4 | let y = 0;
28 - 5 | if (cond) {
29 - 6 | y = 1;
30 -```
31 -
32 -
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md
+89 -67
@@ -37,7 +37,11 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
37
38 graphql`\\t\n`;
39
40 - for (const c of [1, 2]) {
40 + for (c of [1, 2]) {
41 + }
42 + for ([v] of [[1], [2]]) {
43 + }
44 + for ({ v } of [{ v: 1 }, { v: 2 }]) {
45 }
46
47 for (let x in { a: 1 }) {
@@ -191,107 +195,125 @@ let moduleLocal = false;
195 > 34 | graphql`\\t\n`;
196 | ^^^^^^^^^^^^^^
197 35 |
194 - 36 | for (const c of [1, 2]) {
198 + 36 | for (c of [1, 2]) {
199 37 | }
200
197 -[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForOfStatement statements
201 +[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle Identifier inits in ForOfStatement
202 34 | graphql`\\t\n`;
203 35 |
200 -> 36 | for (const c of [1, 2]) {
201 - | ^
204 +> 36 | for (c of [1, 2]) {
205 + | ^
206 37 | }
203 - 38 |
204 - 39 | for (let x in { a: 1 }) {
207 + 38 | for ([v] of [[1], [2]]) {
208 + 39 | }
209
206 -[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForInStatement statements
210 +[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ArrayPattern inits in ForOfStatement
211 + 36 | for (c of [1, 2]) {
212 37 | }
208 - 38 |
209 -> 39 | for (let x in { a: 1 }) {
213 +> 38 | for ([v] of [[1], [2]]) {
214 + | ^^^
215 + 39 | }
216 + 40 | for ({ v } of [{ v: 1 }, { v: 2 }]) {
217 + 41 | }
218 +
219 +[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ObjectPattern inits in ForOfStatement
220 + 38 | for ([v] of [[1], [2]]) {
221 + 39 | }
222 +> 40 | for ({ v } of [{ v: 1 }, { v: 2 }]) {
223 + | ^^^^^
224 + 41 | }
225 + 42 |
226 + 43 | for (let x in { a: 1 }) {
227 +
228 +[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle ForInStatement statements
229 + 41 | }
230 + 42 |
231 +> 43 | for (let x in { a: 1 }) {
232 | ^
211 - 40 | }
212 - 41 |
213 - 42 | let updateIdentifier = 0;
233 + 44 | }
234 + 45 |
235 + 46 | let updateIdentifier = 0;
236
237 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression
216 - 41 |
217 - 42 | let updateIdentifier = 0;
218 -> 43 | --updateIdentifier;
238 + 45 |
239 + 46 | let updateIdentifier = 0;
240 +> 47 | --updateIdentifier;
241 | ^^^^^^^^^^^^^^^^^^
220 - 44 | ++updateIdentifier;
221 - 45 | updateIdentifier.y++;
222 - 46 | updateIdentifier.y--;
242 + 48 | ++updateIdentifier;
243 + 49 | updateIdentifier.y++;
244 + 50 | updateIdentifier.y--;
245
246 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle prefix UpdateExpression
225 - 42 | let updateIdentifier = 0;
226 - 43 | --updateIdentifier;
227 -> 44 | ++updateIdentifier;
247 + 46 | let updateIdentifier = 0;
248 + 47 | --updateIdentifier;
249 +> 48 | ++updateIdentifier;
250 | ^^^^^^^^^^^^^^^^^^
229 - 45 | updateIdentifier.y++;
230 - 46 | updateIdentifier.y--;
231 - 47 |
251 + 49 | updateIdentifier.y++;
252 + 50 | updateIdentifier.y--;
253 + 51 |
254
255 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument
234 - 43 | --updateIdentifier;
235 - 44 | ++updateIdentifier;
236 -> 45 | updateIdentifier.y++;
256 + 47 | --updateIdentifier;
257 + 48 | ++updateIdentifier;
258 +> 49 | updateIdentifier.y++;
259 | ^^^^^^^^^^^^^^^^^^^^
238 - 46 | updateIdentifier.y--;
239 - 47 |
240 - 48 | switch (i) {
260 + 50 | updateIdentifier.y--;
261 + 51 |
262 + 52 | switch (i) {
263
264 [ReactForget] TodoError: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument
243 - 44 | ++updateIdentifier;
244 - 45 | updateIdentifier.y++;
245 -> 46 | updateIdentifier.y--;
265 + 48 | ++updateIdentifier;
266 + 49 | updateIdentifier.y++;
267 +> 50 | updateIdentifier.y--;
268 | ^^^^^^^^^^^^^^^^^^^^
247 - 47 |
248 - 48 | switch (i) {
249 - 49 | case 1 + 1: {
269 + 51 |
270 + 52 | switch (i) {
271 + 53 | case 1 + 1: {
272
273 [ReactForget] TodoError: (BuildHIR::node.lowerReorderableExpression) Expression type 'MemberExpression' cannot be safely reordered
252 - 51 | case foo(): {
253 - 52 | }
254 -> 53 | case x.y: {
255 - | ^^^
256 - 54 | }
257 - 55 | default: {
274 + 55 | case foo(): {
275 56 | }
276 +> 57 | case x.y: {
277 + | ^^^
278 + 58 | }
279 + 59 | default: {
280 + 60 | }
281
282 [ReactForget] TodoError: (BuildHIR::node.lowerReorderableExpression) Expression type 'CallExpression' cannot be safely reordered
261 - 49 | case 1 + 1: {
262 - 50 | }
263 -> 51 | case foo(): {
264 - | ^^^^^
265 - 52 | }
266 - 53 | case x.y: {
283 + 53 | case 1 + 1: {
284 54 | }
285 +> 55 | case foo(): {
286 + | ^^^^^
287 + 56 | }
288 + 57 | case x.y: {
289 + 58 | }
290
291 [ReactForget] TodoError: (BuildHIR::node.lowerReorderableExpression) Expression type 'BinaryExpression' cannot be safely reordered
270 - 47 |
271 - 48 | switch (i) {
272 -> 49 | case 1 + 1: {
292 + 51 |
293 + 52 | switch (i) {
294 +> 53 | case 1 + 1: {
295 | ^^^^^
274 - 50 | }
275 - 51 | case foo(): {
276 - 52 | }
296 + 54 | }
297 + 55 | case foo(): {
298 + 56 | }
299
300 [ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.
279 - 58 |
280 - 59 | // Cannot assign to globals
281 -> 60 | someUnknownGlobal = true;
282 - | ^^^^^^^^^^^^^^^^^
283 - 61 | moduleLocal = true;
301 62 |
285 - 63 | function component(a) {
302 + 63 | // Cannot assign to globals
303 +> 64 | someUnknownGlobal = true;
304 + | ^^^^^^^^^^^^^^^^^
305 + 65 | moduleLocal = true;
306 + 66 |
307 + 67 | function component(a) {
308
309 [ReactForget] InvalidInputError: (BuildHIR::lowerAssignment) Assigning to an identifier defined outside the function scope is not supported.
288 - 59 | // Cannot assign to globals
289 - 60 | someUnknownGlobal = true;
290 -> 61 | moduleLocal = true;
310 + 63 | // Cannot assign to globals
311 + 64 | someUnknownGlobal = true;
312 +> 65 | moduleLocal = true;
313 | ^^^^^^^^^^^
292 - 62 |
293 - 63 | function component(a) {
294 - 64 | // Add support for function declarations once we support `var` hoisting.
314 + 66 |
315 + 67 | function component(a) {
316 + 68 | // Add support for function declarations once we support `var` hoisting.
317 ```
318
319
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.js
+5 -1
@@ -33,7 +33,11 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
33
34 graphql`\\t\n`;
35
36 - for (const c of [1, 2]) {
36 + for (c of [1, 2]) {
37 + }
38 + for ([v] of [[1], [2]]) {
39 + }
40 + for ({ v } of [{ v: 1 }, { v: 2 }]) {
41 }
42
43 for (let x in { a: 1 }) {
compiler/forget/src/__tests__/fixtures/compiler/for-of-break.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component() {
6 + const x = [];
7 + for (const item of [1, 2]) {
8 + break;
9 + }
10 + return x;
11 +}
12 +
13 +```
14 +
15 +## Code
16 +
17 +```javascript
18 +function Component() {
19 + const $ = React.unstable_useMemoCache(1);
20 + let t0;
21 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
22 + t0 = [];
23 + $[0] = t0;
24 + } else {
25 + t0 = $[0];
26 + }
27 + const x = t0;
28 + for (const item of [1, 2]) {
29 + break;
30 + }
31 + return x;
32 +}
33 +
34 +```
35 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/for-of-break.js new
+7
@@ -0,0 +1,7 @@
1 +function Component() {
2 + const x = [];
3 + for (const item of [1, 2]) {
4 + break;
5 + }
6 + return x;
7 +}
compiler/forget/src/__tests__/fixtures/compiler/for-of-conditional-break.expect.md new
+48
@@ -0,0 +1,48 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component() {
6 + const x = [];
7 + for (const item of [1, 2]) {
8 + if (item === 1) {
9 + break;
10 + }
11 + x.push(item);
12 + }
13 + return x;
14 +}
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +function Component() {
22 + const $ = React.unstable_useMemoCache(2);
23 + let x;
24 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25 + x = [];
26 + let t0;
27 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
28 + t0 = [1, 2];
29 + $[1] = t0;
30 + } else {
31 + t0 = $[1];
32 + }
33 + for (const item of t0) {
34 + if (item === 1) {
35 + break;
36 + }
37 +
38 + x.push(item);
39 + }
40 + $[0] = x;
41 + } else {
42 + x = $[0];
43 + }
44 + return x;
45 +}
46 +
47 +```
48 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/for-of-conditional-break.js new
+10
@@ -0,0 +1,10 @@
1 +function Component() {
2 + const x = [];
3 + for (const item of [1, 2]) {
4 + if (item === 1) {
5 + break;
6 + }
7 + x.push(item);
8 + }
9 + return x;
10 +}
compiler/forget/src/__tests__/fixtures/compiler/for-of-continue.expect.md new
+50
@@ -0,0 +1,50 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component() {
6 + const x = [0, 1, 2, 3];
7 + const ret = [];
8 + for (const item of x) {
9 + if (item === 0) {
10 + continue;
11 + }
12 + ret.push(item / 2);
13 + }
14 + return ret;
15 +}
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function Component() {
23 + const $ = React.unstable_useMemoCache(2);
24 + let t0;
25 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
26 + t0 = [0, 1, 2, 3];
27 + $[0] = t0;
28 + } else {
29 + t0 = $[0];
30 + }
31 + const x = t0;
32 + let ret;
33 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
34 + ret = [];
35 + for (const item of x) {
36 + if (item === 0) {
37 + continue;
38 + }
39 +
40 + ret.push(item / 2);
41 + }
42 + $[1] = ret;
43 + } else {
44 + ret = $[1];
45 + }
46 + return ret;
47 +}
48 +
49 +```
50 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/for-of-continue.js new
+11
@@ -0,0 +1,11 @@
1 +function Component() {
2 + const x = [0, 1, 2, 3];
3 + const ret = [];
4 + for (const item of x) {
5 + if (item === 0) {
6 + continue;
7 + }
8 + ret.push(item / 2);
9 + }
10 + return ret;
11 +}
compiler/forget/src/__tests__/fixtures/compiler/for-of-destructure.expect.md new
+36
@@ -0,0 +1,36 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component() {
6 + let x = [];
7 + let items = [{ v: 0 }, { v: 1 }, { v: 2 }];
8 + for (const { v } of items) {
9 + x.push(v * 2);
10 + }
11 + return x;
12 +}
13 +
14 +```
15 +
16 +## Code
17 +
18 +```javascript
19 +function Component() {
20 + const $ = React.unstable_useMemoCache(1);
21 + let x;
22 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23 + x = [];
24 + const items = [{ v: 0 }, { v: 1 }, { v: 2 }];
25 + for (const { v } of items) {
26 + x.push(v * 2);
27 + }
28 + $[0] = x;
29 + } else {
30 + x = $[0];
31 + }
32 + return x;
33 +}
34 +
35 +```
36 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/for-of-destructure.js new
+8
@@ -0,0 +1,8 @@
1 +function Component() {
2 + let x = [];
3 + let items = [{ v: 0 }, { v: 1 }, { v: 2 }];
4 + for (const { v } of items) {
5 + x.push(v * 2);
6 + }
7 + return x;
8 +}
compiler/forget/src/__tests__/fixtures/compiler/for-of-mutate.expect.md new
+43
@@ -0,0 +1,43 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + const collection = [makeObject()];
7 + const results = [];
8 + for (const item of collection) {
9 + results.push(<div>{mutate(item)}</div>);
10 + }
11 + return <div>{results}</div>;
12 +}
13 +
14 +```
15 +
16 +## Code
17 +
18 +```javascript
19 +function Component(props) {
20 + const $ = React.unstable_useMemoCache(2);
21 + let results;
22 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23 + const collection = [makeObject()];
24 + results = [];
25 + for (const item of collection) {
26 + results.push(<div>{mutate(item)}</div>);
27 + }
28 + $[0] = results;
29 + } else {
30 + results = $[0];
31 + }
32 + let t0;
33 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
34 + t0 = <div>{results}</div>;
35 + $[1] = t0;
36 + } else {
37 + t0 = $[1];
38 + }
39 + return t0;
40 +}
41 +
42 +```
43 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/for-of-mutate.js new
+8
@@ -0,0 +1,8 @@
1 +function Component(props) {
2 + const collection = [makeObject()];
3 + const results = [];
4 + for (const item of collection) {
5 + results.push(<div>{mutate(item)}</div>);
6 + }
7 + return <div>{results}</div>;
8 +}
compiler/forget/src/__tests__/fixtures/compiler/for-of-simple.expect.md new
+36
@@ -0,0 +1,36 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component() {
6 + let x = [];
7 + let items = [0, 1, 2];
8 + for (const ii of items) {
9 + x.push(ii * 2);
10 + }
11 + return x;
12 +}
13 +
14 +```
15 +
16 +## Code
17 +
18 +```javascript
19 +function Component() {
20 + const $ = React.unstable_useMemoCache(1);
21 + let x;
22 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
23 + x = [];
24 + const items = [0, 1, 2];
25 + for (const ii of items) {
26 + x.push(ii * 2);
27 + }
28 + $[0] = x;
29 + } else {
30 + x = $[0];
31 + }
32 + return x;
33 +}
34 +
35 +```
36 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/for-of-simple.js new
+8
@@ -0,0 +1,8 @@
1 +function Component() {
2 + let x = [];
3 + let items = [0, 1, 2];
4 + for (const ii of items) {
5 + x.push(ii * 2);
6 + }
7 + return x;
8 +}
compiler/forget/src/__tests__/fixtures/compiler/ssa-for-of.expect.md new
+39
@@ -0,0 +1,39 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function foo(cond) {
6 + let items = [];
7 + for (const item of items) {
8 + let y = 0;
9 + if (cond) {
10 + y = 1;
11 + }
12 + }
13 + return items;
14 +}
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +function foo(cond) {
22 + const $ = React.unstable_useMemoCache(1);
23 + let t0;
24 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
25 + t0 = [];
26 + $[0] = t0;
27 + } else {
28 + t0 = $[0];
29 + }
30 + const items = t0;
31 + for (const item of items) {
32 + if (cond) {
33 + }
34 + }
35 + return items;
36 +}
37 +
38 +```
39 +
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/compiler/ssa-for-of.js renamed