From e30a9e9258cebdf4e017443aa1606eecb2143f9a Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Fri, 3 Feb 2023 15:44:37 -0500 Subject: [PATCH] [hir syntax] Handle TemplateLiteral syntax --- compiler/forget/src/HIR/BuildHIR.ts | 34 +++++++++++++++ compiler/forget/src/HIR/HIR.ts | 6 ++- compiler/forget/src/HIR/PrintHIR.ts | 14 ++++++ compiler/forget/src/HIR/visitors.ts | 8 ++++ .../src/Inference/InferReferenceEffects.ts | 7 +++ .../ReactiveScopes/CodegenReactiveFunction.ts | 9 ++++ .../InferReactiveScopeVariables.ts | 1 + .../fixtures/hir/template-literal.expect.md | 43 +++++++++++++++++++ .../fixtures/hir/template-literal.js | 10 +++++ 9 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 compiler/forget/src/__tests__/fixtures/hir/template-literal.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/template-literal.js diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 5fc8eba5d0..888dd79b2a 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -1420,6 +1420,40 @@ function lowerExpression( loc: exprLoc, }; } + case "TemplateLiteral": { + const expr = exprPath as NodePath; + const subexprs = expr.get("expressions"); + const quasis = expr.get("quasis"); + + if (subexprs.length !== quasis.length - 1) { + builder.errors.push({ + reason: `(BuildHIR::lowerAssignment) Unexpected quasi and subexpression lengths in TemplateLiteral.`, + severity: ErrorSeverity.InvalidInput, + nodePath: exprPath, + }); + return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }; + } + + if (subexprs.some((e) => !e.isExpression())) { + builder.errors.push({ + reason: `(BuildHIR::lowerAssignment) Handle TSType in TemplateLiteral.`, + severity: ErrorSeverity.Todo, + nodePath: exprPath, + }); + return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc }; + } + + const subexprPlaces = subexprs.map((e) => + lowerExpressionToPlace(builder, e as NodePath) + ); + + return { + kind: "TemplateLiteral", + subexprs: subexprPlaces, + quasis: expr.get("quasis").map((q) => q.node.value), + loc: exprLoc, + }; + } case "UnaryExpression": { let expr = exprPath as NodePath; return { diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index e978407bfc..238ec89c7a 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -446,7 +446,11 @@ export type InstructionData = tag: Place; value: { raw: string; cooked?: string }; } - + | { + kind: "TemplateLiteral"; + subexprs: Array; + quasis: Array<{ raw: string; cooked?: string }>; + } /** * Catch-all for statements such as type imports, nested class declarations, etc * which are not directly represented, but included for completeness and to allow diff --git a/compiler/forget/src/HIR/PrintHIR.ts b/compiler/forget/src/HIR/PrintHIR.ts index 8ca6738666..8972160dec 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -6,6 +6,7 @@ */ import generate from "@babel/generator"; +import invariant from "invariant"; import DisjointSet from "../Utils/DisjointSet"; import { assertExhaustive } from "../Utils/utils"; import { @@ -370,6 +371,19 @@ export function printInstructionValue(instrValue: ReactiveValue): string { )} : ${printInstructionValue(instrValue.alternate)}`; break; } + case "TemplateLiteral": { + value = "`"; + invariant( + instrValue.subexprs.length === instrValue.quasis.length - 1, + "Bad assumption about quasi length." + ); + for (let i = 0; i < instrValue.subexprs.length; i++) { + value += instrValue.quasis[i].raw; + value += `\${${printPlace(instrValue.subexprs[i])}}`; + } + value += instrValue.quasis.at(-1)!.raw + "`"; + break; + } default: { assertExhaustive( instrValue, diff --git a/compiler/forget/src/HIR/visitors.ts b/compiler/forget/src/HIR/visitors.ts index 4ed13fc8e3..0d376e66fc 100644 --- a/compiler/forget/src/HIR/visitors.ts +++ b/compiler/forget/src/HIR/visitors.ts @@ -127,6 +127,10 @@ export function* eachInstructionValueOperand( yield instrValue.value; break; } + case "TemplateLiteral": { + yield* instrValue.subexprs; + break; + } case "UnsupportedNode": case "Primitive": case "JSXText": { @@ -251,6 +255,10 @@ export function mapInstructionOperands( instrValue.value = fn(instrValue.value); break; } + case "TemplateLiteral": { + instrValue.subexprs = instrValue.subexprs.map(fn); + break; + } case "UnsupportedNode": case "Primitive": case "JSXText": { diff --git a/compiler/forget/src/Inference/InferReferenceEffects.ts b/compiler/forget/src/Inference/InferReferenceEffects.ts index fdf2a03f08..5f28a04d1b 100644 --- a/compiler/forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/forget/src/Inference/InferReferenceEffects.ts @@ -599,6 +599,13 @@ function inferBlock(env: Environment, block: BasicBlock) { effectKind = Effect.Mutate; break; } + case "TemplateLiteral": { + // template literal (with no tag function) always produces + // an immutable string + valueKind = ValueKind.Immutable; + effectKind = Effect.Read; + break; + } case "JSXText": case "Primitive": { valueKind = ValueKind.Immutable; diff --git a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts index 5ad5e480bf..cba3bc20d7 100644 --- a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -415,6 +415,7 @@ const createTaggedTemplateExpression = withLoc(t.taggedTemplateExpression); const createLogicalExpression = withLoc(t.logicalExpression); const createSequenceExpression = withLoc(t.sequenceExpression); const createConditionalExpression = withLoc(t.conditionalExpression); +const createTemplateLiteral = withLoc(t.templateLiteral); type Temporaries = Map; @@ -751,6 +752,14 @@ function codegenInstructionValue( } break; } + case "TemplateLiteral": { + value = createTemplateLiteral( + instrValue.loc, + instrValue.quasis.map((q) => t.templateElement(q)), + instrValue.subexprs.map((p) => codegenPlace(cx, p)) + ); + break; + } default: { assertExhaustive( instrValue, diff --git a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts index 01924935ad..6ad35c57b1 100644 --- a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -174,6 +174,7 @@ function mayAllocate(value: InstructionValue): boolean { case "ComputedLoad": case "JSXText": case "UnaryExpression": + case "TemplateLiteral": case "Primitive": { return false; } diff --git a/compiler/forget/src/__tests__/fixtures/hir/template-literal.expect.md b/compiler/forget/src/__tests__/fixtures/hir/template-literal.expect.md new file mode 100644 index 0000000000..ee74cfccdd --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/template-literal.expect.md @@ -0,0 +1,43 @@ + +## Input + +```javascript +function componentA(props) { + let t = `hello ${props.a}, ${props.b}!`; + t += ``; + return t; +} + +function componentB(props) { + let x = useFoo(`hello ${props.a}`); + return x; +} + +``` + +## Code + +```javascript +function componentA(props) { + const t = `hello ${props.a}, ${props.b}!`; + const t$0 = t + ``; + return t$0; +} + +function componentB(props) { + const $ = React.useMemoCache(); + const t0 = `hello ${props.a}`; + const c_0 = $[0] !== t0; + let x; + if (c_0) { + x = useFoo(t0); + $[0] = t0; + $[1] = x; + } else { + x = $[1]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/template-literal.js b/compiler/forget/src/__tests__/fixtures/hir/template-literal.js new file mode 100644 index 0000000000..512dfb7ff8 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/template-literal.js @@ -0,0 +1,10 @@ +function componentA(props) { + let t = `hello ${props.a}, ${props.b}!`; + t += ``; + return t; +} + +function componentB(props) { + let x = useFoo(`hello ${props.a}`); + return x; +}