From 18f4a388a90ea39fd33fc2dc6b644c4ae1cce41f Mon Sep 17 00:00:00 2001 From: Mofei Zhang Date: Mon, 29 Apr 2024 14:08:30 -0400 Subject: [PATCH] [hir-rewrite] Remove breaks to HIR ScopeTerminal after converting to reactiveFunction IR ghstack-source-id: 67e979e1533b23ced762973e239a4290111e2242 Pull Request resolved: https://github.com/facebook/react-forget/pull/2911 --- .../src/Entrypoint/Pipeline.ts | 3 ++ .../AssertWellFormedBreakTargets.ts | 35 +++++++++++++++++++ .../ReactiveScopes/BuildReactiveFunction.ts | 26 +++++++++++--- .../src/ReactiveScopes/index.ts | 1 + 4 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/AssertWellFormedBreakTargets.ts diff --git a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts index eadcf59a00..f99112d619 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts @@ -46,6 +46,7 @@ import { alignObjectMethodScopes, alignReactiveScopesToBlockScopes, assertScopeInstructionsWithinScopes, + assertWellFormedBreakTargets, buildReactiveBlocks, buildReactiveFunction, codegenFunction, @@ -279,6 +280,8 @@ function* runWithEnvironment( value: reactiveFunction, }); + assertWellFormedBreakTargets(reactiveFunction); + pruneUnusedLabels(reactiveFunction); yield log({ kind: "reactive", diff --git a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/AssertWellFormedBreakTargets.ts b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/AssertWellFormedBreakTargets.ts new file mode 100644 index 0000000000..0c4d26aa5a --- /dev/null +++ b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/AssertWellFormedBreakTargets.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { CompilerError } from ".."; +import { BlockId, ReactiveFunction, ReactiveTerminalStatement } from "../HIR"; +import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors"; + +/** + * Assert that all break/continue targets reference existent labels. + */ +export function assertWellFormedBreakTargets(fn: ReactiveFunction): void { + visitReactiveFunction(fn, new Visitor(), new Set()); +} + +class Visitor extends ReactiveFunctionVisitor> { + override visitTerminal( + stmt: ReactiveTerminalStatement, + seenLabels: Set + ): void { + if (stmt.label != null) { + seenLabels.add(stmt.label.id); + } + const terminal = stmt.terminal; + if (terminal.kind === "break" || terminal.kind === "continue") { + CompilerError.invariant(seenLabels.has(terminal.target), { + reason: "Unexpected break to invalid label", + loc: stmt.terminal.loc, + }); + } + } +} diff --git a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts index 8218adf098..cc7e917589 100644 --- a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts @@ -813,6 +813,7 @@ class Driver { if (fallthroughId !== null) { const scheduleId = this.cx.schedule(fallthroughId, "if"); scheduleIds.push(scheduleId); + this.cx.scopeFallthroughs.add(fallthroughId); } let block: ReactiveBlock; @@ -1169,7 +1170,7 @@ class Driver { block: BlockId, id: InstructionId, loc: SourceLocation - ): ReactiveTerminalStatement { + ): ReactiveTerminalStatement | null { const target = this.cx.getBreakTarget(block); if (target === null) { CompilerError.invariant(false, { @@ -1179,6 +1180,13 @@ class Driver { suggestions: null, }); } + if (this.cx.scopeFallthroughs.has(target.block)) { + CompilerError.invariant(target.type === "implicit", { + reason: "Expected reactive scope to implicitly break to fallthrough", + loc, + }); + return null; + } return { kind: "terminal", terminal: { @@ -1231,6 +1239,7 @@ class Context { */ emitted: Set = new Set(); + scopeFallthroughs: Set = new Set(); /* * A set of blocks that are already scheduled to be emitted by eg a parent. * This allows child nodes to avoid re-emitting the same block and emit eg @@ -1361,9 +1370,10 @@ class Context { * * The returned 'block' value should be used as the label if necessary. */ - getBreakTarget( - block: BlockId - ): { block: BlockId; type: ReactiveTerminalTargetKind } | null { + getBreakTarget(block: BlockId): { + block: BlockId; + type: ReactiveTerminalTargetKind; + } { let hasPrecedingLoop = false; for (let i = this.#controlFlowStack.length - 1; i >= 0; i--) { const target = this.#controlFlowStack[i]!; @@ -1392,7 +1402,13 @@ class Context { } hasPrecedingLoop ||= target.type === "loop"; } - return null; + + CompilerError.invariant(false, { + reason: "Expected a break target", + description: null, + loc: null, + suggestions: null, + }); } /* diff --git a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/index.ts b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/index.ts index 733ddc6f72..d8321a7eff 100644 --- a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/index.ts +++ b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/index.ts @@ -8,6 +8,7 @@ export { alignObjectMethodScopes } from "./AlignObjectMethodScopes"; export { alignReactiveScopesToBlockScopes } from "./AlignReactiveScopesToBlockScopes"; export { assertScopeInstructionsWithinScopes } from "./AssertScopeInstructionsWithinScope"; +export { assertWellFormedBreakTargets } from "./AssertWellFormedBreakTargets"; export { buildReactiveBlocks } from "./BuildReactiveBlocks"; export { buildReactiveFunction } from "./BuildReactiveFunction"; export {