[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
This commit is contained in:
Mofei Zhang
2024-04-29 14:08:30 -04:00
parent 6db5f36aa7
commit 18f4a388a9
4 changed files with 60 additions and 5 deletions
@@ -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",
@@ -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<Set<BlockId>> {
override visitTerminal(
stmt: ReactiveTerminalStatement,
seenLabels: Set<BlockId>
): 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,
});
}
}
}
@@ -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<ReactiveBreakTerminal> {
): ReactiveTerminalStatement<ReactiveBreakTerminal> | 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<BlockId> = new Set();
scopeFallthroughs: Set<BlockId> = 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,
});
}
/*
@@ -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 {