@samitouri / QOS-React-1 / commits / 51497c8c59

Basic ForInStatement support

First pass of ForInStatement support. This mostly just copies our handling of ForOfStatement, but the next PR updates to use a different instruction instead of `NextIterableOf`.

Joe Savona committed Sep 11, 2023 at 15:56 UTC 51497c8c59d008983a40609158f75dc793093730
24 files changed +582 -18
compiler/packages/babel-plugin-react-forget/src/HIR/BuildHIR.ts
+83 -1
@@ -910,6 +910,89 @@ function lowerStatement(
910 );
911 return;
912 }
913 + case "ForInStatement": {
914 + const stmt = stmtPath as NodePath<t.ForInStatement>;
915 + const continuationBlock = builder.reserve("block");
916 + const initBlock = builder.reserve("loop");
917 +
918 + const loopBlock = builder.enter("block", (_blockId) => {
919 + return builder.loop(label, initBlock.id, continuationBlock.id, () => {
920 + const body = stmt.get("body");
921 + lowerStatement(builder, body);
922 + return {
923 + kind: "goto",
924 + block: initBlock.id,
925 + variant: GotoVariant.Continue,
926 + id: makeInstructionId(0),
927 + loc: body.node.loc ?? GeneratedSource,
928 + };
929 + });
930 + });
931 +
932 + const loc = stmt.node.loc ?? GeneratedSource;
933 + const value = lowerExpressionToTemporary(builder, stmt.get("right"));
934 + builder.terminateWithContinuation(
935 + {
936 + kind: "for-in",
937 + loc,
938 + init: initBlock.id,
939 + loop: loopBlock,
940 + fallthrough: continuationBlock.id,
941 + id: makeInstructionId(0),
942 + },
943 + initBlock
944 + );
945 +
946 + // The init of a ForOf statement is compound over a left (VariableDeclaration | LVal) and
947 + // right (Expression), so we synthesize a new InstrValue and assignment (potentially multiple
948 + // instructions when we handle other syntax like Patterns)
949 + const left = stmt.get("left");
950 + const leftLoc = left.node.loc ?? GeneratedSource;
951 + let test: Place;
952 + if (left.isVariableDeclaration()) {
953 + const declarations = left.get("declarations");
954 + CompilerError.invariant(declarations.length === 1, {
955 + reason: `Expected only one declaration in the init of a ForOfStatement, got ${declarations.length}`,
956 + description: null,
957 + loc: left.node.loc ?? null,
958 + suggestions: null,
959 + });
960 + const id = declarations[0].get("id");
961 + const nextIterableOf = lowerValueToTemporary(builder, {
962 + kind: "NextIterableOf", // TODO: change this to reflect for-in semantics (returns immutable keys, does not modify collection)
963 + loc: leftLoc,
964 + value,
965 + });
966 + const assign = lowerAssignment(
967 + builder,
968 + leftLoc,
969 + InstructionKind.Let,
970 + id,
971 + nextIterableOf
972 + );
973 + test = lowerValueToTemporary(builder, assign);
974 + } else {
975 + builder.errors.push({
976 + reason: `(BuildHIR::lowerStatement) Handle ${left.type} inits in ForOfStatement`,
977 + severity: ErrorSeverity.Todo,
978 + loc: left.node.loc ?? null,
979 + suggestions: null,
980 + });
981 + return;
982 + }
983 + builder.terminateWithContinuation(
984 + {
985 + id: makeInstructionId(0),
986 + kind: "branch",
987 + test,
988 + consequent: loopBlock,
989 + alternate: continuationBlock.id,
990 + loc: stmt.node.loc ?? GeneratedSource,
991 + },
992 + continuationBlock
993 + );
994 + return;
995 + }
996 case "DebuggerStatement": {
997 const stmt = stmtPath as NodePath<t.DebuggerStatement>;
998 const loc = stmt.node.loc ?? GeneratedSource;
@@ -1028,7 +1111,6 @@ function lowerStatement(
1111
1112 return;
1113 }
1031 - case "ForInStatement":
1114 case "ClassDeclaration":
1115 case "DeclareClass":
1116 case "DeclareExportAllDeclaration":
compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts
+17
@@ -137,6 +137,7 @@ export type ReactiveTerminal =
137 | ReactiveWhileTerminal
138 | ReactiveForTerminal
139 | ReactiveForOfTerminal
140 + | ReactiveForInTerminal
141 | ReactiveIfTerminal
142 | ReactiveLabelTerminal
143 | ReactiveTryTerminal;
@@ -198,6 +199,12 @@ export type ReactiveForOfTerminal = {
199 loop: ReactiveBlock;
200 id: InstructionId;
201 };
202 +export type ReactiveForInTerminal = {
203 + kind: "for-in";
204 + init: ReactiveValue;
205 + loop: ReactiveBlock;
206 + id: InstructionId;
207 +};
208 export type ReactiveIfTerminal = {
209 kind: "if";
210 test: Place;
@@ -281,6 +288,7 @@ export type Terminal =
288 | SwitchTerminal
289 | ForTerminal
290 | ForOfTerminal
291 + | ForInTerminal
292 | DoWhileTerminal
293 | WhileTerminal
294 | LogicalTerminal
@@ -409,6 +417,15 @@ export type ForOfTerminal = {
417 id: InstructionId;
418 };
419
420 +export type ForInTerminal = {
421 + kind: "for-in";
422 + loc: SourceLocation;
423 + init: BlockId;
424 + loop: BlockId;
425 + fallthrough: BlockId;
426 + id: InstructionId;
427 +};
428 +
429 export type LogicalTerminal = {
430 kind: "logical";
431 operator: t.LogicalExpression["operator"];
compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts
+2 -4
@@ -745,10 +745,8 @@ export function reversePostorderBlocks(func: HIR): void {
745 visit(terminal.test);
746 break;
747 }
748 - case "for": {
749 - visit(terminal.init);
750 - break;
751 - }
748 + case "for":
749 + case "for-in":
750 case "for-of": {
751 visit(terminal.init);
752 break;
compiler/packages/babel-plugin-react-forget/src/HIR/PrintHIR.ts
+5
@@ -97,6 +97,7 @@ export function printMixedHIR(
97 case "unsupported":
98 case "goto":
99 case "do-while":
100 + case "for-in":
101 case "for-of": {
102 const terminal = printTerminal(value);
103 if (Array.isArray(terminal)) {
@@ -218,6 +219,10 @@ export function printTerminal(terminal: Terminal): Array<string> | string {
219 value = `[${terminal.id}] ForOf init=bb${terminal.init} loop=bb${terminal.loop} fallthrough=bb${terminal.fallthrough}`;
220 break;
221 }
222 + case "for-in": {
223 + value = `[${terminal.id}] ForIn init=bb${terminal.init} loop=bb${terminal.loop} fallthrough=bb${terminal.fallthrough}`;
224 + break;
225 + }
226 case "label": {
227 value = `[${terminal.id}] Label block=bb${terminal.block} fallthrough=${
228 terminal.fallthrough ? `bb${terminal.fallthrough}` : ""
compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts
+24
@@ -708,6 +708,19 @@ export function mapTerminalSuccessors(
708 id: makeInstructionId(0),
709 };
710 }
711 + case "for-in": {
712 + const init = fn(terminal.init);
713 + const loop = fn(terminal.loop);
714 + const fallthrough = fn(terminal.fallthrough);
715 + return {
716 + kind: "for-in",
717 + loc: terminal.loc,
718 + init,
719 + loop,
720 + fallthrough,
721 + id: makeInstructionId(0),
722 + };
723 + }
724 case "label": {
725 const block = fn(terminal.block);
726 const fallthrough =
@@ -787,6 +800,7 @@ export function terminalFallthrough(terminal: Terminal): BlockId | null {
800 case "try":
801 case "do-while":
802 case "for-of":
803 + case "for-in":
804 case "for":
805 case "if":
806 case "label":
@@ -832,6 +846,10 @@ export function mapOptionalFallthroughs(
846 const _: BlockId = terminal.fallthrough;
847 break;
848 }
849 + case "for-in": {
850 + const _: BlockId = terminal.fallthrough;
851 + break;
852 + }
853 case "for": {
854 const _: BlockId = terminal.fallthrough;
855 break;
@@ -946,6 +964,10 @@ export function* eachTerminalSuccessor(terminal: Terminal): Iterable<BlockId> {
964 yield terminal.init;
965 break;
966 }
967 + case "for-in": {
968 + yield terminal.init;
969 + break;
970 + }
971 case "label": {
972 yield terminal.block;
973 break;
@@ -1020,6 +1042,7 @@ export function mapTerminalOperands(
1042 case "while":
1043 case "for":
1044 case "for-of":
1045 + case "for-in":
1046 case "goto":
1047 case "unsupported": {
1048 // no-op
@@ -1070,6 +1093,7 @@ export function* eachTerminalOperand(terminal: Terminal): Iterable<Place> {
1093 case "while":
1094 case "for":
1095 case "for-of":
1096 + case "for-in":
1097 case "goto":
1098 case "unsupported": {
1099 // no-op
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+75
@@ -499,6 +499,81 @@ class Driver {
499 }
500 break;
501 }
502 + case "for-in": {
503 + const loopId =
504 + !this.cx.isScheduled(terminal.loop) &&
505 + terminal.loop !== terminal.fallthrough
506 + ? terminal.loop
507 + : null;
508 +
509 + const fallthroughId =
510 + terminal.fallthrough !== null &&
511 + !this.cx.isScheduled(terminal.fallthrough)
512 + ? terminal.fallthrough
513 + : null;
514 +
515 + const scheduleId = this.cx.scheduleLoop(
516 + terminal.fallthrough,
517 + terminal.init,
518 + terminal.loop
519 + );
520 + scheduleIds.push(scheduleId);
521 +
522 + const init = this.visitValueBlock(terminal.init, terminal.loc);
523 + const initBlock = this.cx.ir.blocks.get(init.block)!;
524 + let initValue = init.value;
525 + if (initValue.kind === "SequenceExpression") {
526 + const last = initBlock.instructions.at(-1)!;
527 + initValue.instructions.push(last);
528 + initValue.value = {
529 + kind: "Primitive",
530 + value: undefined,
531 + loc: terminal.loc,
532 + };
533 + } else {
534 + initValue = {
535 + kind: "SequenceExpression",
536 + instructions: [initBlock.instructions.at(-1)!],
537 + id: terminal.id,
538 + loc: terminal.loc,
539 + value: {
540 + kind: "Primitive",
541 + value: undefined,
542 + loc: terminal.loc,
543 + },
544 + };
545 + }
546 +
547 + let loopBody: ReactiveBlock;
548 + if (loopId) {
549 + loopBody = this.traverseBlock(this.cx.ir.blocks.get(loopId)!);
550 + } else {
551 + const break_ = this.visitBreak(terminal.loop, null);
552 + CompilerError.invariant(break_ !== null, {
553 + reason: "If loop body is already scheduled it must be a break",
554 + description: null,
555 + loc: null,
556 + suggestions: null,
557 + });
558 + loopBody = [break_];
559 + }
560 +
561 + this.cx.unscheduleAll(scheduleIds);
562 + blockValue.push({
563 + kind: "terminal",
564 + terminal: {
565 + kind: "for-in",
566 + init: initValue,
567 + loop: loopBody,
568 + id: terminal.id,
569 + },
570 + label: fallthroughId,
571 + });
572 + if (fallthroughId !== null) {
573 + this.visitBlock(this.cx.ir.blocks.get(fallthroughId)!, blockValue);
574 + }
575 + break;
576 + }
577 case "branch": {
578 let consequent: ReactiveBlock | null = null;
579 if (this.cx.isScheduled(terminal.consequent)) {
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+22 -9
@@ -403,6 +403,7 @@ function codegenTerminal(
403 codegenBlock(cx, terminal.loop)
404 );
405 }
406 + case "for-in":
407 case "for-of": {
408 CompilerError.invariant(terminal.init.kind === "SequenceExpression", {
409 reason: `Expected a sequence expression init for ForOf`,
@@ -467,15 +468,27 @@ function codegenTerminal(
468 `Unhandled lvalue kind: ${iterableItem.value.lvalue.kind}`
469 );
470 }
470 - return t.forOfStatement(
471 - // Special handling here since we only want the VariableDeclarators without any inits
472 - // This needs to be updated when we handle non-trivial ForOf inits
473 - createVariableDeclaration(iterableItem.value.loc, varDeclKind, [
474 - t.variableDeclarator(lval, null),
475 - ]),
476 - codegenInstructionValue(cx, iterableCollection.value),
477 - codegenBlock(cx, terminal.loop)
478 - );
471 + if (terminal.kind === "for-of") {
472 + return t.forOfStatement(
473 + // Special handling here since we only want the VariableDeclarators without any inits
474 + // This needs to be updated when we handle non-trivial ForOf inits
475 + createVariableDeclaration(iterableItem.value.loc, varDeclKind, [
476 + t.variableDeclarator(lval, null),
477 + ]),
478 + codegenInstructionValue(cx, iterableCollection.value),
479 + codegenBlock(cx, terminal.loop)
480 + );
481 + } else {
482 + return t.forInStatement(
483 + // Special handling here since we only want the VariableDeclarators without any inits
484 + // This needs to be updated when we handle non-trivial ForOf inits
485 + createVariableDeclaration(iterableItem.value.loc, varDeclKind, [
486 + t.variableDeclarator(lval, null),
487 + ]),
488 + codegenInstructionValue(cx, iterableCollection.value),
489 + codegenBlock(cx, terminal.loop)
490 + );
491 + }
492 }
493 case "if": {
494 const test = codegenPlace(cx, terminal.test);
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/FlattenReactiveLoops.ts
+2 -1
@@ -49,7 +49,8 @@ class Transform extends ReactiveFunctionTransform<boolean> {
49 case "do-while":
50 case "while":
51 case "for":
52 - case "for-of": {
52 + case "for-of":
53 + case "for-in": {
54 this.traverseTerminal(stmt, true);
55 break;
56 }
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PrintReactiveFunction.ts
+8
@@ -262,6 +262,14 @@ function printTerminal(writer: Writer, terminal: ReactiveTerminal): void {
262 writer.writeLine("}");
263 break;
264 }
265 + case "for-in": {
266 + writer.writeLine(`[${terminal.id}] for-in (`);
267 + printReactiveValue(writer, terminal.init);
268 + writer.writeLine(") {");
269 + printReactiveInstructions(writer, terminal.loop);
270 + writer.writeLine("}");
271 + break;
272 + }
273 case "throw": {
274 writer.writeLine(`[${terminal.id}] throw ${printPlace(terminal.value)}`);
275 break;
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/PropagateScopeDependencies.ts
+7
@@ -612,6 +612,13 @@ class PropagationVisitor extends ReactiveFunctionVisitor<Context> {
612 });
613 break;
614 }
615 + case "for-in": {
616 + this.visitReactiveValue(context, terminal.id, terminal.init);
617 + context.enterConditional(() => {
618 + this.visitBlock(terminal.loop, context);
619 + });
620 + break;
621 + }
622 case "do-while": {
623 this.visitBlock(terminal.loop, context);
624 context.enterConditional(() => {
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/visitors.ts
+9
@@ -117,6 +117,11 @@ export class ReactiveFunctionVisitor<TState = void> {
117 this.visitBlock(terminal.loop, state);
118 break;
119 }
120 + case "for-in": {
121 + this.visitValue(terminal.id, terminal.init, state);
122 + this.visitBlock(terminal.loop, state);
123 + break;
124 + }
125 case "do-while": {
126 this.visitBlock(terminal.loop, state);
127 this.visitValue(terminal.id, terminal.test, state);
@@ -342,6 +347,10 @@ export function mapTerminalBlocks(
347 terminal.loop = fn(terminal.loop);
348 break;
349 }
350 + case "for-in": {
351 + terminal.loop = fn(terminal.loop);
352 + break;
353 + }
354 case "do-while":
355 case "while": {
356 terminal.loop = fn(terminal.loop);
compiler/packages/babel-plugin-react-forget/src/SSA/LeaveSSA.ts
+5 -1
@@ -352,7 +352,11 @@ export function leaveSSA(fn: HIRFunction): void {
352 const loop = fn.body.blocks.get(terminal.loop)!;
353 pushPhis(loop);
354 }
355 - if (terminal.kind === "for" || terminal.kind === "for-of") {
355 + if (
356 + terminal.kind === "for" ||
357 + terminal.kind === "for-of" ||
358 + terminal.kind === "for-in"
359 + ) {
360 const init = fn.body.blocks.get(terminal.init)!;
361 pushPhis(init);
362
compiler/packages/babel-plugin-react-forget/src/Utils/VisualizeHIRMermaid.ts
+2
@@ -139,6 +139,7 @@ function printTerminalLabel(terminal: Terminal): string {
139 buffer.push("Sequence");
140 break;
141 }
142 + case "for-in":
143 case "try":
144 case "maybe-throw":
145 case "unsupported": {
@@ -248,6 +249,7 @@ function printTerminalArrows(blockId: BlockId, terminal: Terminal): string {
249 buffer.push(printJumpArrow(blockId, terminal.fallthrough, "fallthrough"));
250 break;
251 }
252 + case "for-in":
253 case "try":
254 case "maybe-throw":
255 case "throw":
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/error.todo-kitchensink.expect.md
-2
@@ -107,8 +107,6 @@ let moduleLocal = false;
107
108 [ReactForget] Todo: (BuildHIR::lowerStatement) Handle ObjectPattern inits in ForOfStatement (40:40)
109
110 -[ReactForget] Todo: (BuildHIR::lowerStatement) Handle ForInStatement statements (43:44)
111 -
110 [ReactForget] Todo: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument (49:49)
111
112 [ReactForget] Todo: (BuildHIR::lowerExpression) Handle UpdateExpression with MemberExpression argument (50:50)
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-body-always-returns.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + for (const x in props.value) {
7 + return x;
8 + }
9 + return null;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{ value: { a: "A!" } }],
15 +};
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function Component(props) {
23 + for (const x in props.value) {
24 + return x;
25 + }
26 + return null;
27 +}
28 +
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: Component,
31 + params: [{ value: { a: "A!" } }],
32 +};
33 +
34 +```
35 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-body-always-returns.js new
+11
@@ -0,0 +1,11 @@
1 +function Component(props) {
2 + for (const x in props.value) {
3 + return x;
4 + }
5 + return null;
6 +}
7 +
8 +export const FIXTURE_ENTRYPOINT = {
9 + fn: Component,
10 + params: [{ value: { a: "A!" } }],
11 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-break.expect.md new
+66
@@ -0,0 +1,66 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let x;
7 + const object = { ...props.value };
8 + for (const y in object) {
9 + if (y === "break") {
10 + break;
11 + }
12 + x = object[y];
13 + }
14 + return x;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + // should return 'a'
20 + params: [{ a: "a", break: null, c: "C!" }],
21 +};
22 +
23 +```
24 +
25 +## Code
26 +
27 +```javascript
28 +import { unstable_useMemoCache as useMemoCache } from "react";
29 +function Component(props) {
30 + const $ = useMemoCache(4);
31 + const c_0 = $[0] !== props.value;
32 + let x;
33 + if (c_0) {
34 + const c_2 = $[2] !== props.value;
35 + let t0;
36 + if (c_2) {
37 + t0 = { ...props.value };
38 + $[2] = props.value;
39 + $[3] = t0;
40 + } else {
41 + t0 = $[3];
42 + }
43 + const object = t0;
44 + for (const y in object) {
45 + if (y === "break") {
46 + break;
47 + }
48 +
49 + x = object[y];
50 + }
51 + $[0] = props.value;
52 + $[1] = x;
53 + } else {
54 + x = $[1];
55 + }
56 + return x;
57 +}
58 +
59 +export const FIXTURE_ENTRYPOINT = {
60 + fn: Component,
61 + // should return 'a'
62 + params: [{ a: "a", break: null, c: "C!" }],
63 +};
64 +
65 +```
66 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-break.js new
+17
@@ -0,0 +1,17 @@
1 +function Component(props) {
2 + let x;
3 + const object = { ...props.value };
4 + for (const y in object) {
5 + if (y === "break") {
6 + break;
7 + }
8 + x = object[y];
9 + }
10 + return x;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + // should return 'a'
16 + params: [{ a: "a", break: null, c: "C!" }],
17 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-continue.expect.md new
+62
@@ -0,0 +1,62 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let x;
7 + const object = { ...props.value };
8 + for (const y in object) {
9 + if (y === "continue") {
10 + continue;
11 + }
12 + x = object[y];
13 + }
14 + return x;
15 +}
16 +
17 +export const FIXTURE_ENTRYPOINT = {
18 + fn: Component,
19 + params: [{ value: { a: "a", continue: "skip", b: "b!" } }],
20 +};
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { unstable_useMemoCache as useMemoCache } from "react";
28 +function Component(props) {
29 + const $ = useMemoCache(3);
30 + let x;
31 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
32 + const c_1 = $[1] !== props.value;
33 + let t0;
34 + if (c_1) {
35 + t0 = { ...props.value };
36 + $[1] = props.value;
37 + $[2] = t0;
38 + } else {
39 + t0 = $[2];
40 + }
41 + const object = t0;
42 + for (const y in object) {
43 + if (y === "continue") {
44 + continue;
45 + }
46 +
47 + x = object[y];
48 + }
49 + $[0] = x;
50 + } else {
51 + x = $[0];
52 + }
53 + return x;
54 +}
55 +
56 +export const FIXTURE_ENTRYPOINT = {
57 + fn: Component,
58 + params: [{ value: { a: "a", continue: "skip", b: "b!" } }],
59 +};
60 +
61 +```
62 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-continue.js new
+16
@@ -0,0 +1,16 @@
1 +function Component(props) {
2 + let x;
3 + const object = { ...props.value };
4 + for (const y in object) {
5 + if (y === "continue") {
6 + continue;
7 + }
8 + x = object[y];
9 + }
10 + return x;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{ value: { a: "a", continue: "skip", b: "b!" } }],
16 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-empty-body.expect.md new
+35
@@ -0,0 +1,35 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let x;
7 + for (const y in props.value) {
8 + }
9 + return x;
10 +}
11 +
12 +export const FIXTURE_ENTRYPOINT = {
13 + fn: Component,
14 + params: [{ value: { a: "a", b: "B", c: "C!" } }],
15 +};
16 +
17 +```
18 +
19 +## Code
20 +
21 +```javascript
22 +function Component(props) {
23 + let x;
24 + for (const y in props.value) {
25 + }
26 + return x;
27 +}
28 +
29 +export const FIXTURE_ENTRYPOINT = {
30 + fn: Component,
31 + params: [{ value: { a: "a", b: "B", c: "C!" } }],
32 +};
33 +
34 +```
35 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement-empty-body.js new
+11
@@ -0,0 +1,11 @@
1 +function Component(props) {
2 + let x;
3 + for (const y in props.value) {
4 + }
5 + return x;
6 +}
7 +
8 +export const FIXTURE_ENTRYPOINT = {
9 + fn: Component,
10 + params: [{ value: { a: "a", b: "B", c: "C!" } }],
11 +};
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement.expect.md new
+56
@@ -0,0 +1,56 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +function Component(props) {
6 + let items = [];
7 + for (const key in props) {
8 + items.push(<div key={key}>{key}</div>);
9 + }
10 + return <div>{items}</div>;
11 +}
12 +
13 +export const FIXTURE_ENTRYPOINT = {
14 + fn: Component,
15 + params: [{ hello: null, world: undefined, "!": true }],
16 +};
17 +
18 +```
19 +
20 +## Code
21 +
22 +```javascript
23 +import { unstable_useMemoCache as useMemoCache } from "react";
24 +function Component(props) {
25 + const $ = useMemoCache(4);
26 + const c_0 = $[0] !== props;
27 + let items;
28 + if (c_0) {
29 + items = [];
30 + for (const key in props) {
31 + items.push(<div key={key}>{key}</div>);
32 + }
33 + $[0] = props;
34 + $[1] = items;
35 + } else {
36 + items = $[1];
37 + }
38 + const c_2 = $[2] !== items;
39 + let t0;
40 + if (c_2) {
41 + t0 = <div>{items}</div>;
42 + $[2] = items;
43 + $[3] = t0;
44 + } else {
45 + t0 = $[3];
46 + }
47 + return t0;
48 +}
49 +
50 +export const FIXTURE_ENTRYPOINT = {
51 + fn: Component,
52 + params: [{ hello: null, world: undefined, "!": true }],
53 +};
54 +
55 +```
56 +
\ No newline at end of file
compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/for-in-statement.js new
+12
@@ -0,0 +1,12 @@
1 +function Component(props) {
2 + let items = [];
3 + for (const key in props) {
4 + items.push(<div key={key}>{key}</div>);
5 + }
6 + return <div>{items}</div>;
7 +}
8 +
9 +export const FIXTURE_ENTRYPOINT = {
10 + fn: Component,
11 + params: [{ hello: null, world: undefined, "!": true }],
12 +};