From 24ea3fa4bcd7becafab5858a3ef83586ac9fd4f6 Mon Sep 17 00:00:00 2001 From: Joseph Savona Date: Fri, 4 Nov 2022 11:16:50 -0700 Subject: [PATCH] Support JSX fragments (#736) * handle jsx fragments * update effects for jsx fragments --- compiler/forget/src/HIR/BuildHIR.ts | 13 ++++- compiler/forget/src/HIR/Codegen.ts | 8 +++ compiler/forget/src/HIR/HIR.ts | 1 + .../forget/src/HIR/InferReferenceEffects.ts | 7 +++ compiler/forget/src/HIR/PrintHIR.ts | 6 +++ compiler/forget/src/HIR/SSAify.ts | 4 ++ .../fixtures/hir/jsx-fragment.expect.md | 49 +++++++++++++++++++ .../__tests__/fixtures/hir/jsx-fragment.js | 10 ++++ 8 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 compiler/forget/src/__tests__/fixtures/hir/jsx-fragment.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/jsx-fragment.js diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index ef46205ce7..057cee0afa 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -957,6 +957,17 @@ function lowerExpression( loc: exprLoc, }; } + case "JSXFragment": { + const expr = exprPath as NodePath; + const children = expr + .get("children") + .map((child) => lowerJsxElement(builder, child)); + return { + kind: "JsxFragment", + children, + loc: exprLoc, + }; + } default: { todo(`lowerExpression(${exprNode.type})`); // assertExhaustive( @@ -1082,7 +1093,7 @@ function lowerJsxElement( ): Place { const exprNode = exprPath.node; const exprLoc = exprNode.loc ?? GeneratedSource; - if (exprPath.isJSXElement()) { + if (exprPath.isJSXElement() || exprPath.isJSXFragment()) { return lowerExpressionToPlace(builder, exprPath); } else if (exprPath.isJSXExpressionContainer()) { const expression = exprPath.get("expression"); diff --git a/compiler/forget/src/HIR/Codegen.ts b/compiler/forget/src/HIR/Codegen.ts index a0ec126edd..32cccf4b90 100644 --- a/compiler/forget/src/HIR/Codegen.ts +++ b/compiler/forget/src/HIR/Codegen.ts @@ -247,6 +247,14 @@ function writeInstr(cx: Context, instr: Instruction, body: Array) { ); break; } + case "JsxFragment": { + value = t.jsxFragment( + t.jsxOpeningFragment(), + t.jsxClosingFragment(), + instrValue.children.map((child) => codegenJsxElement(cx, child)) + ); + break; + } case "OtherStatement": { const node = instrValue.node; if (t.isStatement(node)) { diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index 0c46d85d4c..e95f27b7e7 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -207,6 +207,7 @@ export type InstructionData = properties: Map | null; // null === empty object } | { kind: "ArrayExpression"; elements: Array } + | { kind: "JsxFragment"; children: Array } /** * Catch-all for statements such as type imports, nested class declarations, etc diff --git a/compiler/forget/src/HIR/InferReferenceEffects.ts b/compiler/forget/src/HIR/InferReferenceEffects.ts index 7b1dd27983..1101c310d1 100644 --- a/compiler/forget/src/HIR/InferReferenceEffects.ts +++ b/compiler/forget/src/HIR/InferReferenceEffects.ts @@ -574,6 +574,13 @@ function inferBlock(env: Environment, block: BasicBlock) { } break; } + case "JsxFragment": { + valueKind = ValueKind.Frozen; + for (const child of instrValue.children) { + env.reference(child, Effect.Freeze); + } + break; + } case "JSXText": case "Primitive": { valueKind = ValueKind.Immutable; diff --git a/compiler/forget/src/HIR/PrintHIR.ts b/compiler/forget/src/HIR/PrintHIR.ts index ee8181753c..e0b1499467 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -215,6 +215,12 @@ function printInstructionValue(instrValue: InstructionValue): string { } break; } + case "JsxFragment": { + value = `JsxFragment [${instrValue.children + .map((child) => printPlace(child)) + .join(", ")}]`; + break; + } case "NewExpression": { value = `New ${printPlace(instrValue.callee)}(${instrValue.args .map((arg) => printPlace(arg)) diff --git a/compiler/forget/src/HIR/SSAify.ts b/compiler/forget/src/HIR/SSAify.ts index 59db037367..43dc18f7e5 100644 --- a/compiler/forget/src/HIR/SSAify.ts +++ b/compiler/forget/src/HIR/SSAify.ts @@ -344,6 +344,10 @@ function rewriteUses(instr: Instruction, builder: SSABuilder) { instrValue.elements = instrValue.elements.map((e) => builder.getPlace(e)); break; } + case "JsxFragment": { + instrValue.children = instrValue.children.map((e) => builder.getPlace(e)); + break; + } case "OtherStatement": case "Primitive": case "JSXText": { diff --git a/compiler/forget/src/__tests__/fixtures/hir/jsx-fragment.expect.md b/compiler/forget/src/__tests__/fixtures/hir/jsx-fragment.expect.md new file mode 100644 index 0000000000..f1e1ff95da --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/jsx-fragment.expect.md @@ -0,0 +1,49 @@ + +## Input + +```javascript +function Foo(props) { + return ( + <> + Hello {props.greeting}{" "} +
+ <>Text +
+ + ); +} + +``` + +## HIR + +``` +bb0: + Const mutate $14 = "\n Hello " + Const mutate $15 = " " + Const mutate $16 = "\n " + Const mutate $17 = "div" + Const mutate $18 = "\n " + Const mutate $19 = "Text" + Const mutate $20 = JsxFragment [read $19] + Const mutate $21 = "\n " + Const mutate $22 = JSX {read $18}{read $20}{read $21} + Const mutate $23 = "\n " + Const mutate $24 = JsxFragment [read $14, read props$13.greeting, read $15, read $16, read $22, read $23] + Return read $24 +``` + +## Code + +```javascript +function Foo$0(props$13) { + return ( + <> + Hello {props$13.greeting} + {
{<>Text}
} + + ); +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/jsx-fragment.js b/compiler/forget/src/__tests__/fixtures/hir/jsx-fragment.js new file mode 100644 index 0000000000..a8142a9539 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/jsx-fragment.js @@ -0,0 +1,10 @@ +function Foo(props) { + return ( + <> + Hello {props.greeting}{" "} +
+ <>Text +
+ + ); +}