Invariant for use before definition of reactive values (#662)
Jan Kassens committed
Oct 13, 2022 at 16:17 UTC
b1006e12cd996debe341fa27db548d4c794bbdaa
3 files changed
+99
compiler/forget/src/BackEnd/SanityCheck.ts
new
+97
@@ -0,0 +1,97 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+import type { NodePath } from "@babel/traverse";
9
+import * as t from "@babel/types";
10
+import type { CompilerContext } from "../CompilerContext";
11
+import { OutputKind } from "../CompilerOutputs";
12
+import * as LIR from "../LIR";
13
+import * as IR from "../IR";
14
+import { PassKind, PassName } from "../Pass";
15
+import { assertExhaustive } from "../Common/utils";
16
+import invariant from "invariant";
17
+import { isReactiveBlock, isRenderBlock } from "../LIR";
18
+
19
+/**
20
+ * An optional debug step when the output {@link OutputKind.IR} is enabled.
21
+ * Records the output IR in the compiler context.
22
+ */
23
+export default {
24
+ name: PassName.DumpIR,
25
+ kind: PassKind.LIRFunc as const,
26
+ run,
27
+};
28
+
29
+export function run(
30
+ lirFunc: LIR.Func,
31
+ _func: NodePath<t.Function>,
32
+ _context: CompilerContext
33
+) {
34
+ const irFunc = lirFunc.ir;
35
+
36
+ const reactiveValues = new Set<string>();
37
+ function defineReactiveVal(value: IR.ReactiveVal) {
38
+ const name = value.binding.identifier.name;
39
+ reactiveValues.add(name);
40
+ }
41
+
42
+ function useReactiveValues(values: Set<IR.ReactiveVal>) {
43
+ for (const reactiveValue of values) {
44
+ const name = reactiveValue.binding.identifier.name;
45
+ invariant(
46
+ reactiveValues.has(name),
47
+ `Reactive value "${name}" not yet defined.`
48
+ );
49
+ }
50
+ }
51
+
52
+ for (const param of irFunc.params) {
53
+ if (IR.isReactiveVal(param)) {
54
+ defineReactiveVal(param);
55
+ }
56
+ }
57
+
58
+ function visitJSX(expr: IR.ExprVal) {
59
+ if (IR.isJSXTagVal(expr)) {
60
+ expr.children.forEach((child) => visitJSX(child));
61
+ }
62
+ if (!expr.stable) {
63
+ const entry = lirFunc.memoCache.entries.get(expr);
64
+ if (entry) {
65
+ invariant(LIR.MemoCache.isExprEntry(entry), "");
66
+ const { inputs } = irFunc.depGraph.getOrCreateVertex(entry.value);
67
+ useReactiveValues(inputs);
68
+ }
69
+ }
70
+ }
71
+
72
+ for (const block of lirFunc.blocks) {
73
+ switch (block.kind) {
74
+ case LIR.BlockKind.Render:
75
+ invariant(isRenderBlock(block), "Expected render block");
76
+ for (const instr of block.body) {
77
+ for (const decl of instr.ir.decls) {
78
+ if (IR.isReactiveVal(decl)) {
79
+ defineReactiveVal(decl);
80
+ }
81
+ }
82
+ }
83
+ break;
84
+ case LIR.BlockKind.Reactive:
85
+ invariant(isReactiveBlock(block), "Expected reactive block");
86
+ useReactiveValues(block.inputs);
87
+ for (const instr of block.body) {
88
+ instr.ir.jsxTreeRoots.forEach((root) => {
89
+ visitJSX(root);
90
+ });
91
+ }
92
+ break;
93
+ default:
94
+ assertExhaustive(block.kind, `Unhandled block ${block}`);
95
+ }
96
+ }
97
+}
compiler/forget/src/BackEnd/index.ts
+1
@@ -14,3 +14,4 @@ export { default as DumpLIR } from "./DumpLIR";
14
export { default as JSGen } from "./JSGen";
15
export { default as LIRGen } from "./LIRGen";
16
export { default as MemoCacheAlloc } from "./MemoCacheAlloc";
17
+export { default as SanityCheck } from "./SanityCheck";
compiler/forget/src/CompilerDriver.ts
+1
@@ -53,6 +53,7 @@ export function createCompilerDriver(
53
passManager.addPass(BE.LIRGen);
54
passManager.addPass(BE.MemoCacheAlloc);
55
passManager.addPass(BE.DumpLIR);
56
+ passManager.addPass(BE.SanityCheck);
57
58
// JS Generation.
59
passManager.addPass(BE.JSGen);