@samitouri / QOS-React / commits / 79d53f9117

[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 +}