[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 {