Skip to content

Commit b1006e1

Browse files
committed
Invariant for use before definition of reactive values (#662)
1 parent 0000ab6 commit b1006e1

File tree

3 files changed

+99
-0
lines changed

3 files changed

+99
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export { default as DumpLIR } from "./DumpLIR";
1414
export { default as JSGen } from "./JSGen";
1515
export { default as LIRGen } from "./LIRGen";
1616
export { default as MemoCacheAlloc } from "./MemoCacheAlloc";
17+
export { default as SanityCheck } from "./SanityCheck";

compiler/forget/src/CompilerDriver.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export function createCompilerDriver(
5353
passManager.addPass(BE.LIRGen);
5454
passManager.addPass(BE.MemoCacheAlloc);
5555
passManager.addPass(BE.DumpLIR);
56+
passManager.addPass(BE.SanityCheck);
5657

5758
// JS Generation.
5859
passManager.addPass(BE.JSGen);

0 commit comments

Comments
 (0)