@samitouri / QOS-React / commits / 18f4a388a9

[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

Mofei Zhang committed Apr 29, 2024 at 14:08 UTC 18f4a388a90ea39fd33fc2dc6b644c4ae1cce41f
4 files changed +60 -5
compiler/packages/babel-plugin-react-forget/src/Entrypoint/Pipeline.ts
+3
@@ -46,6 +46,7 @@ import {
46 alignObjectMethodScopes,
47 alignReactiveScopesToBlockScopes,
48 assertScopeInstructionsWithinScopes,
49 + assertWellFormedBreakTargets,
50 buildReactiveBlocks,
51 buildReactiveFunction,
52 codegenFunction,
@@ -279,6 +280,8 @@ function* runWithEnvironment(
280 value: reactiveFunction,
281 });
282
283 + assertWellFormedBreakTargets(reactiveFunction);
284 +
285 pruneUnusedLabels(reactiveFunction);
286 yield log({
287 kind: "reactive",
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/AssertWellFormedBreakTargets.ts new
+35
@@ -0,0 +1,35 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and 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 { CompilerError } from "..";
9 +import { BlockId, ReactiveFunction, ReactiveTerminalStatement } from "../HIR";
10 +import { ReactiveFunctionVisitor, visitReactiveFunction } from "./visitors";
11 +
12 +/**
13 + * Assert that all break/continue targets reference existent labels.
14 + */
15 +export function assertWellFormedBreakTargets(fn: ReactiveFunction): void {
16 + visitReactiveFunction(fn, new Visitor(), new Set());
17 +}
18 +
19 +class Visitor extends ReactiveFunctionVisitor<Set<BlockId>> {
20 + override visitTerminal(
21 + stmt: ReactiveTerminalStatement,
22 + seenLabels: Set<BlockId>
23 + ): void {
24 + if (stmt.label != null) {
25 + seenLabels.add(stmt.label.id);
26 + }
27 + const terminal = stmt.terminal;
28 + if (terminal.kind === "break" || terminal.kind === "continue") {
29 + CompilerError.invariant(seenLabels.has(terminal.target), {
30 + reason: "Unexpected break to invalid label",
31 + loc: stmt.terminal.loc,
32 + });
33 + }
34 + }
35 +}
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts
+21 -5
@@ -813,6 +813,7 @@ class Driver {
813 if (fallthroughId !== null) {
814 const scheduleId = this.cx.schedule(fallthroughId, "if");
815 scheduleIds.push(scheduleId);
816 + this.cx.scopeFallthroughs.add(fallthroughId);
817 }
818
819 let block: ReactiveBlock;
@@ -1169,7 +1170,7 @@ class Driver {
1170 block: BlockId,
1171 id: InstructionId,
1172 loc: SourceLocation
1172 - ): ReactiveTerminalStatement<ReactiveBreakTerminal> {
1173 + ): ReactiveTerminalStatement<ReactiveBreakTerminal> | null {
1174 const target = this.cx.getBreakTarget(block);
1175 if (target === null) {
1176 CompilerError.invariant(false, {
@@ -1179,6 +1180,13 @@ class Driver {
1180 suggestions: null,
1181 });
1182 }
1183 + if (this.cx.scopeFallthroughs.has(target.block)) {
1184 + CompilerError.invariant(target.type === "implicit", {
1185 + reason: "Expected reactive scope to implicitly break to fallthrough",
1186 + loc,
1187 + });
1188 + return null;
1189 + }
1190 return {
1191 kind: "terminal",
1192 terminal: {
@@ -1231,6 +1239,7 @@ class Context {
1239 */
1240 emitted: Set<BlockId> = new Set();
1241
1242 + scopeFallthroughs: Set<BlockId> = new Set();
1243 /*
1244 * A set of blocks that are already scheduled to be emitted by eg a parent.
1245 * This allows child nodes to avoid re-emitting the same block and emit eg
@@ -1361,9 +1370,10 @@ class Context {
1370 *
1371 * The returned 'block' value should be used as the label if necessary.
1372 */
1364 - getBreakTarget(
1365 - block: BlockId
1366 - ): { block: BlockId; type: ReactiveTerminalTargetKind } | null {
1373 + getBreakTarget(block: BlockId): {
1374 + block: BlockId;
1375 + type: ReactiveTerminalTargetKind;
1376 + } {
1377 let hasPrecedingLoop = false;
1378 for (let i = this.#controlFlowStack.length - 1; i >= 0; i--) {
1379 const target = this.#controlFlowStack[i]!;
@@ -1392,7 +1402,13 @@ class Context {
1402 }
1403 hasPrecedingLoop ||= target.type === "loop";
1404 }
1395 - return null;
1405 +
1406 + CompilerError.invariant(false, {
1407 + reason: "Expected a break target",
1408 + description: null,
1409 + loc: null,
1410 + suggestions: null,
1411 + });
1412 }
1413
1414 /*
compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/index.ts
+1
@@ -8,6 +8,7 @@
8 export { alignObjectMethodScopes } from "./AlignObjectMethodScopes";
9 export { alignReactiveScopesToBlockScopes } from "./AlignReactiveScopesToBlockScopes";
10 export { assertScopeInstructionsWithinScopes } from "./AssertScopeInstructionsWithinScope";
11 +export { assertWellFormedBreakTargets } from "./AssertWellFormedBreakTargets";
12 export { buildReactiveBlocks } from "./BuildReactiveBlocks";
13 export { buildReactiveFunction } from "./BuildReactiveFunction";
14 export {