[hir syntax] Handle TemplateLiteral syntax
Mofei Zhang committed
Feb 3, 2023 at 15:44 UTC
e30a9e9258cebdf4e017443aa1606eecb2143f9a
9 files changed
+131
-1
compiler/forget/src/HIR/BuildHIR.ts
+34
@@ -1420,6 +1420,40 @@ function lowerExpression(
1420
loc: exprLoc,
1421
};
1422
}
1423
+ case "TemplateLiteral": {
1424
+ const expr = exprPath as NodePath<t.TemplateLiteral>;
1425
+ const subexprs = expr.get("expressions");
1426
+ const quasis = expr.get("quasis");
1427
+
1428
+ if (subexprs.length !== quasis.length - 1) {
1429
+ builder.errors.push({
1430
+ reason: `(BuildHIR::lowerAssignment) Unexpected quasi and subexpression lengths in TemplateLiteral.`,
1431
+ severity: ErrorSeverity.InvalidInput,
1432
+ nodePath: exprPath,
1433
+ });
1434
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1435
+ }
1436
+
1437
+ if (subexprs.some((e) => !e.isExpression())) {
1438
+ builder.errors.push({
1439
+ reason: `(BuildHIR::lowerAssignment) Handle TSType in TemplateLiteral.`,
1440
+ severity: ErrorSeverity.Todo,
1441
+ nodePath: exprPath,
1442
+ });
1443
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1444
+ }
1445
+
1446
+ const subexprPlaces = subexprs.map((e) =>
1447
+ lowerExpressionToPlace(builder, e as NodePath<t.Expression>)
1448
+ );
1449
+
1450
+ return {
1451
+ kind: "TemplateLiteral",
1452
+ subexprs: subexprPlaces,
1453
+ quasis: expr.get("quasis").map((q) => q.node.value),
1454
+ loc: exprLoc,
1455
+ };
1456
+ }
1457
case "UnaryExpression": {
1458
let expr = exprPath as NodePath<t.UnaryExpression>;
1459
return {
compiler/forget/src/HIR/HIR.ts
+5
-1
@@ -446,7 +446,11 @@ export type InstructionData =
446
tag: Place;
447
value: { raw: string; cooked?: string };
448
}
449
-
449
+ | {
450
+ kind: "TemplateLiteral";
451
+ subexprs: Array<Place>;
452
+ quasis: Array<{ raw: string; cooked?: string }>;
453
+ }
454
/**
455
* Catch-all for statements such as type imports, nested class declarations, etc
456
* which are not directly represented, but included for completeness and to allow
compiler/forget/src/HIR/PrintHIR.ts
+14
@@ -6,6 +6,7 @@
6
*/
7
8
import generate from "@babel/generator";
9
+import invariant from "invariant";
10
import DisjointSet from "../Utils/DisjointSet";
11
import { assertExhaustive } from "../Utils/utils";
12
import {
@@ -370,6 +371,19 @@ export function printInstructionValue(instrValue: ReactiveValue): string {
371
)} : ${printInstructionValue(instrValue.alternate)}`;
372
break;
373
}
374
+ case "TemplateLiteral": {
375
+ value = "`";
376
+ invariant(
377
+ instrValue.subexprs.length === instrValue.quasis.length - 1,
378
+ "Bad assumption about quasi length."
379
+ );
380
+ for (let i = 0; i < instrValue.subexprs.length; i++) {
381
+ value += instrValue.quasis[i].raw;
382
+ value += `\${${printPlace(instrValue.subexprs[i])}}`;
383
+ }
384
+ value += instrValue.quasis.at(-1)!.raw + "`";
385
+ break;
386
+ }
387
default: {
388
assertExhaustive(
389
instrValue,
compiler/forget/src/HIR/visitors.ts
+8
@@ -127,6 +127,10 @@ export function* eachInstructionValueOperand(
127
yield instrValue.value;
128
break;
129
}
130
+ case "TemplateLiteral": {
131
+ yield* instrValue.subexprs;
132
+ break;
133
+ }
134
case "UnsupportedNode":
135
case "Primitive":
136
case "JSXText": {
@@ -251,6 +255,10 @@ export function mapInstructionOperands(
255
instrValue.value = fn(instrValue.value);
256
break;
257
}
258
+ case "TemplateLiteral": {
259
+ instrValue.subexprs = instrValue.subexprs.map(fn);
260
+ break;
261
+ }
262
case "UnsupportedNode":
263
case "Primitive":
264
case "JSXText": {
compiler/forget/src/Inference/InferReferenceEffects.ts
+7
@@ -599,6 +599,13 @@ function inferBlock(env: Environment, block: BasicBlock) {
599
effectKind = Effect.Mutate;
600
break;
601
}
602
+ case "TemplateLiteral": {
603
+ // template literal (with no tag function) always produces
604
+ // an immutable string
605
+ valueKind = ValueKind.Immutable;
606
+ effectKind = Effect.Read;
607
+ break;
608
+ }
609
case "JSXText":
610
case "Primitive": {
611
valueKind = ValueKind.Immutable;
compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+9
@@ -415,6 +415,7 @@ const createTaggedTemplateExpression = withLoc(t.taggedTemplateExpression);
415
const createLogicalExpression = withLoc(t.logicalExpression);
416
const createSequenceExpression = withLoc(t.sequenceExpression);
417
const createConditionalExpression = withLoc(t.conditionalExpression);
418
+const createTemplateLiteral = withLoc(t.templateLiteral);
419
420
type Temporaries = Map<IdentifierId, t.Expression>;
421
@@ -751,6 +752,14 @@ function codegenInstructionValue(
752
}
753
break;
754
}
755
+ case "TemplateLiteral": {
756
+ value = createTemplateLiteral(
757
+ instrValue.loc,
758
+ instrValue.quasis.map((q) => t.templateElement(q)),
759
+ instrValue.subexprs.map((p) => codegenPlace(cx, p))
760
+ );
761
+ break;
762
+ }
763
default: {
764
assertExhaustive(
765
instrValue,
compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts
+1
@@ -174,6 +174,7 @@ function mayAllocate(value: InstructionValue): boolean {
174
case "ComputedLoad":
175
case "JSXText":
176
case "UnaryExpression":
177
+ case "TemplateLiteral":
178
case "Primitive": {
179
return false;
180
}
compiler/forget/src/__tests__/fixtures/hir/template-literal.expect.md
new
+43
@@ -0,0 +1,43 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function componentA(props) {
6
+ let t = `hello ${props.a}, ${props.b}!`;
7
+ t += ``;
8
+ return t;
9
+}
10
+
11
+function componentB(props) {
12
+ let x = useFoo(`hello ${props.a}`);
13
+ return x;
14
+}
15
+
16
+```
17
+
18
+## Code
19
+
20
+```javascript
21
+function componentA(props) {
22
+ const t = `hello ${props.a}, ${props.b}!`;
23
+ const t$0 = t + ``;
24
+ return t$0;
25
+}
26
+
27
+function componentB(props) {
28
+ const $ = React.useMemoCache();
29
+ const t0 = `hello ${props.a}`;
30
+ const c_0 = $[0] !== t0;
31
+ let x;
32
+ if (c_0) {
33
+ x = useFoo(t0);
34
+ $[0] = t0;
35
+ $[1] = x;
36
+ } else {
37
+ x = $[1];
38
+ }
39
+ return x;
40
+}
41
+
42
+```
43
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/template-literal.js
new
+10
@@ -0,0 +1,10 @@
1
+function componentA(props) {
2
+ let t = `hello ${props.a}, ${props.b}!`;
3
+ t += ``;
4
+ return t;
5
+}
6
+
7
+function componentB(props) {
8
+ let x = useFoo(`hello ${props.a}`);
9
+ return x;
10
+}