[hir] Parse and codegen TaggedTemplateExpression
Basic support to parse relay fragments
Sathya Gunasekaran committed
Jan 24, 2023 at 16:41 UTC
79d53f911707c4b25f638d9ad79d9792dc6bf6b0
11 files changed
+175
-10
compiler/forget/src/HIR/BuildHIR.ts
+34
@@ -7,6 +7,7 @@
7
8
import { NodePath, Scope } from "@babel/traverse";
9
import * as t from "@babel/types";
10
+import invariant from "invariant";
11
import { CompilerErrorDetail, ErrorSeverity } from "../CompilerError";
12
import { Err, Ok, Result } from "../lib/Result";
13
import { assertExhaustive } from "../Utils/utils";
@@ -1377,6 +1378,39 @@ function lowerExpression(
1378
loc: exprLoc,
1379
};
1380
}
1381
+ case "TaggedTemplateExpression": {
1382
+ const expr = exprPath as NodePath<t.TaggedTemplateExpression>;
1383
+ if (expr.get("quasi").get("expressions").length !== 0) {
1384
+ builder.pushError({
1385
+ reason: "Unhandled tagged template with interpolations",
1386
+ severity: ErrorSeverity.Todo,
1387
+ nodePath: exprPath,
1388
+ });
1389
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1390
+ }
1391
+ invariant(
1392
+ expr.get("quasi").get("quasis").length == 1,
1393
+ "there should be only one quasi as we don't support interpolations yet"
1394
+ );
1395
+
1396
+ const value = expr.get("quasi").get("quasis").at(0)!.node.value;
1397
+ if (value.raw !== value.cooked) {
1398
+ builder.pushError({
1399
+ reason:
1400
+ "Unhandled tagged template where cooked value is different from raw value",
1401
+ severity: ErrorSeverity.Todo,
1402
+ nodePath: exprPath,
1403
+ });
1404
+ return { kind: "UnsupportedNode", node: exprNode, loc: exprLoc };
1405
+ }
1406
+
1407
+ return {
1408
+ kind: "TaggedTemplateExpression",
1409
+ tag: lowerExpressionToPlace(builder, expr.get("tag")),
1410
+ value,
1411
+ loc: exprLoc,
1412
+ };
1413
+ }
1414
default: {
1415
builder.pushError({
1416
reason: `(BuildHIR::lowerExpression) Handle ${exprPath.type} expressions`,
compiler/forget/src/HIR/HIR.ts
+5
@@ -409,6 +409,11 @@ export type InstructionData =
409
loweredFunc: HIRFunction;
410
expr: t.ArrowFunctionExpression | t.FunctionExpression;
411
}
412
+ | {
413
+ kind: "TaggedTemplateExpression";
414
+ tag: Place;
415
+ value: { raw: string; cooked?: string };
416
+ }
417
418
/**
419
* Catch-all for statements such as type imports, nested class declarations, etc
compiler/forget/src/HIR/PrintHIR.ts
+4
@@ -321,6 +321,10 @@ export function printInstructionValue(instrValue: InstructionValue): string {
321
}){${body}}`;
322
break;
323
}
324
+ case "TaggedTemplateExpression": {
325
+ value = `${instrValue.tag}\`${instrValue.value.raw}\``;
326
+ break;
327
+ }
328
default: {
329
assertExhaustive(
330
instrValue,
compiler/forget/src/HIR/visitors.ts
+8
@@ -102,6 +102,10 @@ export function* eachInstructionValueOperand(
102
yield* instrValue.dependencies;
103
break;
104
}
105
+ case "TaggedTemplateExpression": {
106
+ yield instrValue.tag;
107
+ break;
108
+ }
109
case "UnsupportedNode":
110
case "Primitive":
111
case "JSXText": {
@@ -203,6 +207,10 @@ export function mapInstructionOperands(
207
instrValue.dependencies = instrValue.dependencies.map((d) => fn(d));
208
break;
209
}
210
+ case "TaggedTemplateExpression": {
211
+ instrValue.tag = fn(instrValue.tag);
212
+ break;
213
+ }
214
case "UnsupportedNode":
215
case "Primitive":
216
case "JSXText": {
compiler/forget/src/Inference/InferReferenceEffects.ts
+5
@@ -579,6 +579,11 @@ function inferBlock(env: Environment, block: BasicBlock) {
579
effectKind = Effect.Freeze;
580
break;
581
}
582
+ case "TaggedTemplateExpression": {
583
+ valueKind = ValueKind.Mutable;
584
+ effectKind = Effect.Mutate;
585
+ break;
586
+ }
587
case "JSXText":
588
case "Primitive": {
589
valueKind = ValueKind.Immutable;
compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts
+9
@@ -429,6 +429,7 @@ const createFunctionDeclaration = withLoc(t.functionDeclaration);
429
const createLabelledStatement = withLoc(t.labeledStatement);
430
const createVariableDeclaration = withLoc(t.variableDeclaration);
431
const createWhileStatement = withLoc(t.whileStatement);
432
+const createTaggedTemplateExpression = withLoc(t.taggedTemplateExpression);
433
434
type Temporaries = Map<IdentifierId, t.Expression>;
435
@@ -667,6 +668,14 @@ function codegenInstructionValue(
668
value = instrValue.expr;
669
break;
670
}
671
+ case "TaggedTemplateExpression": {
672
+ value = createTaggedTemplateExpression(
673
+ instrValue.loc,
674
+ codegenPlace(temp, instrValue.tag),
675
+ t.templateLiteral([t.templateElement(instrValue.value)], [])
676
+ );
677
+ break;
678
+ }
679
default: {
680
assertExhaustive(
681
instrValue,
compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts
+1
@@ -187,6 +187,7 @@ function mayAllocate(value: InstructionValue): boolean {
187
case "ObjectExpression":
188
case "UnsupportedNode":
189
case "FunctionExpression":
190
+ case "TaggedTemplateExpression":
191
case "UnaryExpression": {
192
return true;
193
}
compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.expect.md
+36
-10
@@ -34,6 +34,12 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
34
}
35
for (; i < 3; ) {}
36
for (;;) {}
37
+
38
+ graphql`
39
+ ${g}
40
+ `;
41
+
42
+ graphql`\\t\n`;
43
}
44
45
```
@@ -238,8 +244,8 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
244
> 31 | for (; i < 3; ) {}
245
| ^^^^^^^^^^^^^^^^^^
246
32 | for (;;) {}
241
- 33 | }
242
- 34 |
247
+ 33 |
248
+ 34 | graphql`
249
250
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
251
29 | x.push(i);
@@ -247,32 +253,52 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
253
> 31 | for (; i < 3; ) {}
254
| ^^^^^^^^^^^^^^^^^^
255
32 | for (;;) {}
250
- 33 | }
251
- 34 |
256
+ 33 |
257
+ 34 | graphql`
258
259
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle non-variable initialization in ForStatement
260
30 | }
261
31 | for (; i < 3; ) {}
262
> 32 | for (;;) {}
263
| ^^^^^^^^^^^
258
- 33 | }
259
- 34 |
264
+ 33 |
265
+ 34 | graphql`
266
+ 35 | ${g}
267
268
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty update in ForStatement
269
30 | }
270
31 | for (; i < 3; ) {}
271
> 32 | for (;;) {}
272
| ^^^^^^^^^^^
266
- 33 | }
267
- 34 |
273
+ 33 |
274
+ 34 | graphql`
275
+ 35 | ${g}
276
277
[ReactForget] TodoError: (BuildHIR::lowerStatement) Handle empty test in ForStatement
278
30 | }
279
31 | for (; i < 3; ) {}
280
> 32 | for (;;) {}
281
| ^^^^^^^^^^^
274
- 33 | }
275
- 34 |
282
+ 33 |
283
+ 34 | graphql`
284
+ 35 | ${g}
285
+
286
+[ReactForget] TodoError: Unhandled tagged template with interpolations
287
+ 32 | for (;;) {}
288
+ 33 |
289
+> 34 | graphql`
290
+ | ^
291
+ 35 | ${g}
292
+ 36 | `;
293
+ 37 |
294
+
295
+[ReactForget] TodoError: Unhandled tagged template where cooked value is different from raw value
296
+ 36 | `;
297
+ 37 |
298
+> 38 | graphql`\\t\n`;
299
+ | ^^^^^^^^^^^^^^
300
+ 39 | }
301
+ 40 |
302
```
303
304
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/error.todo-kitchensink.js
+6
@@ -30,4 +30,10 @@ function foo([a, b], { c, d, e = "e" }, f = "f", ...args) {
30
}
31
for (; i < 3; ) {}
32
for (;;) {}
33
+
34
+ graphql`
35
+ ${g}
36
+ `;
37
+
38
+ graphql`\\t\n`;
39
}
compiler/forget/src/__tests__/fixtures/hir/tagged-template-literal.expect.md
new
+51
@@ -0,0 +1,51 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+function component() {
6
+ let t = graphql`
7
+ fragment List_viewer on Viewer
8
+ @argumentDefinitions(
9
+ count: {
10
+ type: "Int"
11
+ defaultValue: 10
12
+ directives: ["@int_max_value(logged_in: 10)"]
13
+ }
14
+ cursor: { type: "ID" }
15
+ )
16
+
17
+ `;
18
+
19
+ return t;
20
+}
21
+
22
+```
23
+
24
+## Code
25
+
26
+```javascript
27
+function component() {
28
+ const $ = React.useMemoCache();
29
+ let t;
30
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
31
+ t = graphql`
32
+ fragment List_viewer on Viewer
33
+ @argumentDefinitions(
34
+ count: {
35
+ type: "Int"
36
+ defaultValue: 10
37
+ directives: ["@int_max_value(logged_in: 10)"]
38
+ }
39
+ cursor: { type: "ID" }
40
+ )
41
+
42
+ `;
43
+ $[0] = t;
44
+ } else {
45
+ t = $[0];
46
+ }
47
+ return t;
48
+}
49
+
50
+```
51
+
\ No newline at end of file
compiler/forget/src/__tests__/fixtures/hir/tagged-template-literal.js
new
+16
@@ -0,0 +1,16 @@
1
+function component() {
2
+ let t = graphql`
3
+ fragment List_viewer on Viewer
4
+ @argumentDefinitions(
5
+ count: {
6
+ type: "Int"
7
+ defaultValue: 10
8
+ directives: ["@int_max_value(logged_in: 10)"]
9
+ }
10
+ cursor: { type: "ID" }
11
+ )
12
+
13
+ `;
14
+
15
+ return t;
16
+}