From b1006e12cd996debe341fa27db548d4c794bbdaa Mon Sep 17 00:00:00 2001 From: Jan Kassens Date: Thu, 13 Oct 2022 16:17:39 -0400 Subject: [PATCH] Invariant for use before definition of reactive values (#662) --- compiler/forget/src/BackEnd/SanityCheck.ts | 97 ++++++++++++++++++++++ compiler/forget/src/BackEnd/index.ts | 1 + compiler/forget/src/CompilerDriver.ts | 1 + 3 files changed, 99 insertions(+) create mode 100644 compiler/forget/src/BackEnd/SanityCheck.ts diff --git a/compiler/forget/src/BackEnd/SanityCheck.ts b/compiler/forget/src/BackEnd/SanityCheck.ts new file mode 100644 index 0000000000..c07ef52451 --- /dev/null +++ b/compiler/forget/src/BackEnd/SanityCheck.ts @@ -0,0 +1,97 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type { NodePath } from "@babel/traverse"; +import * as t from "@babel/types"; +import type { CompilerContext } from "../CompilerContext"; +import { OutputKind } from "../CompilerOutputs"; +import * as LIR from "../LIR"; +import * as IR from "../IR"; +import { PassKind, PassName } from "../Pass"; +import { assertExhaustive } from "../Common/utils"; +import invariant from "invariant"; +import { isReactiveBlock, isRenderBlock } from "../LIR"; + +/** + * An optional debug step when the output {@link OutputKind.IR} is enabled. + * Records the output IR in the compiler context. + */ +export default { + name: PassName.DumpIR, + kind: PassKind.LIRFunc as const, + run, +}; + +export function run( + lirFunc: LIR.Func, + _func: NodePath, + _context: CompilerContext +) { + const irFunc = lirFunc.ir; + + const reactiveValues = new Set(); + function defineReactiveVal(value: IR.ReactiveVal) { + const name = value.binding.identifier.name; + reactiveValues.add(name); + } + + function useReactiveValues(values: Set) { + for (const reactiveValue of values) { + const name = reactiveValue.binding.identifier.name; + invariant( + reactiveValues.has(name), + `Reactive value "${name}" not yet defined.` + ); + } + } + + for (const param of irFunc.params) { + if (IR.isReactiveVal(param)) { + defineReactiveVal(param); + } + } + + function visitJSX(expr: IR.ExprVal) { + if (IR.isJSXTagVal(expr)) { + expr.children.forEach((child) => visitJSX(child)); + } + if (!expr.stable) { + const entry = lirFunc.memoCache.entries.get(expr); + if (entry) { + invariant(LIR.MemoCache.isExprEntry(entry), ""); + const { inputs } = irFunc.depGraph.getOrCreateVertex(entry.value); + useReactiveValues(inputs); + } + } + } + + for (const block of lirFunc.blocks) { + switch (block.kind) { + case LIR.BlockKind.Render: + invariant(isRenderBlock(block), "Expected render block"); + for (const instr of block.body) { + for (const decl of instr.ir.decls) { + if (IR.isReactiveVal(decl)) { + defineReactiveVal(decl); + } + } + } + break; + case LIR.BlockKind.Reactive: + invariant(isReactiveBlock(block), "Expected reactive block"); + useReactiveValues(block.inputs); + for (const instr of block.body) { + instr.ir.jsxTreeRoots.forEach((root) => { + visitJSX(root); + }); + } + break; + default: + assertExhaustive(block.kind, `Unhandled block ${block}`); + } + } +} diff --git a/compiler/forget/src/BackEnd/index.ts b/compiler/forget/src/BackEnd/index.ts index 8a4a5de40b..2c2e1c49f5 100644 --- a/compiler/forget/src/BackEnd/index.ts +++ b/compiler/forget/src/BackEnd/index.ts @@ -14,3 +14,4 @@ export { default as DumpLIR } from "./DumpLIR"; export { default as JSGen } from "./JSGen"; export { default as LIRGen } from "./LIRGen"; export { default as MemoCacheAlloc } from "./MemoCacheAlloc"; +export { default as SanityCheck } from "./SanityCheck"; diff --git a/compiler/forget/src/CompilerDriver.ts b/compiler/forget/src/CompilerDriver.ts index a7342242a7..8a883eb70b 100644 --- a/compiler/forget/src/CompilerDriver.ts +++ b/compiler/forget/src/CompilerDriver.ts @@ -53,6 +53,7 @@ export function createCompilerDriver( passManager.addPass(BE.LIRGen); passManager.addPass(BE.MemoCacheAlloc); passManager.addPass(BE.DumpLIR); + passManager.addPass(BE.SanityCheck); // JS Generation. passManager.addPass(BE.JSGen);