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