diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts index c5be036759..95d7855a39 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/HIR.ts @@ -362,6 +362,8 @@ export type Terminal = | TryTerminal | ReactiveScopeTerminal; +export type TerminalWithFallthrough = Terminal & { fallthrough: BlockId }; + function _staticInvariantTerminalHasLocation( terminal: Terminal ): SourceLocation { @@ -376,6 +378,13 @@ function _staticInvariantTerminalHasInstructionId( return terminal.id; } +function _staticInvariantTerminalHasFallthrough( + terminal: Terminal +): BlockId | never | undefined { + // If this fails, it is because a variant of Terminal is missing a fallthrough annotation + return terminal.fallthrough; +} + /* * Terminal nodes allowed for a value block * A terminal that couldn't be lowered correctly. @@ -384,6 +393,7 @@ export type UnsupportedTerminal = { kind: "unsupported"; id: InstructionId; loc: SourceLocation; + fallthrough?: never; }; /** @@ -395,6 +405,7 @@ export type UnreachableTerminal = { kind: "unreachable"; id: InstructionId; loc: SourceLocation; + fallthrough?: never; }; export type ThrowTerminal = { @@ -402,6 +413,7 @@ export type ThrowTerminal = { value: Place; id: InstructionId; loc: SourceLocation; + fallthrough?: never; }; export type Case = { test: Place | null; block: BlockId }; @@ -410,6 +422,7 @@ export type ReturnTerminal = { loc: SourceLocation; value: Place; id: InstructionId; + fallthrough?: never; }; export type GotoTerminal = { @@ -418,6 +431,7 @@ export type GotoTerminal = { variant: GotoVariant; id: InstructionId; loc: SourceLocation; + fallthrough?: never; }; export enum GotoVariant { @@ -431,7 +445,7 @@ export type IfTerminal = { test: Place; consequent: BlockId; alternate: BlockId; - fallthrough: BlockId | null; + fallthrough: BlockId; id: InstructionId; loc: SourceLocation; }; @@ -443,13 +457,14 @@ export type BranchTerminal = { alternate: BlockId; id: InstructionId; loc: SourceLocation; + fallthrough?: never; }; export type SwitchTerminal = { kind: "switch"; test: Place; cases: Array; - fallthrough: BlockId | null; + fallthrough: BlockId; id: InstructionId; loc: SourceLocation; }; @@ -521,7 +536,7 @@ export type TernaryTerminal = { export type LabelTerminal = { kind: "label"; block: BlockId; - fallthrough: BlockId | null; + fallthrough: BlockId; id: InstructionId; loc: SourceLocation; }; @@ -555,7 +570,7 @@ export type TryTerminal = { handlerBinding: Place | null; handler: BlockId; // TODO: support `finally` - fallthrough: BlockId | null; + fallthrough: BlockId; id: InstructionId; loc: SourceLocation; }; @@ -566,6 +581,7 @@ export type MaybeThrowTerminal = { handler: BlockId; id: InstructionId; loc: SourceLocation; + fallthrough?: never; }; export type ReactiveScopeTerminal = { diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts index 3c24a2bcf2..da2dcf3b49 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/HIRBuilder.ts @@ -8,7 +8,6 @@ import { Binding, NodePath } from "@babel/traverse"; import * as t from "@babel/types"; import { CompilerError } from "../CompilerError"; -import { assertExhaustive } from "../Utils/utils"; import { Environment } from "./Environment"; import { Global } from "./Globals"; import { @@ -32,8 +31,8 @@ import { import { printInstruction } from "./PrintHIR"; import { eachTerminalSuccessor, - mapOptionalFallthroughs, mapTerminalSuccessors, + terminalFallthrough, } from "./visitors"; /* @@ -329,7 +328,6 @@ export default class HIRBuilder { ir.blocks = rpoBlocks; removeUnreachableForUpdates(ir); - removeUnreachableFallthroughs(ir); removeDeadDoWhileStatements(ir); removeUnnecessaryTryCatch(ir); markInstructionIds(ir); @@ -629,24 +627,6 @@ export function removeUnreachableForUpdates(fn: HIR): void { } } -export function removeUnreachableFallthroughs(func: HIR): void { - const visited: Set = new Set(); - for (const [_, block] of func.blocks) { - visited.add(block.id); - } - - // Cleanup any fallthrough blocks that weren't visited - for (const [_, block] of func.blocks) { - mapOptionalFallthroughs(block.terminal, (fallthrough) => { - if (visited.has(fallthrough)) { - return fallthrough; - } else { - return null; - } - }); - } -} - export function removeDeadDoWhileStatements(func: HIR): void { const visited: Set = new Set(); for (const [_, block] of func.blocks) { @@ -691,14 +671,19 @@ export function reversePostorderBlocks(func: HIR): void { */ function getReversePostorderedBlocks(func: HIR): HIR["blocks"] { const visited: Set = new Set(); + const used: Set = new Set(); + const usedFallthroughs: Set = new Set(); const postorder: Array = []; - function visit(blockId: BlockId): void { - if (visited.has(blockId)) { + function visit(blockId: BlockId, isUsed: boolean): void { + const wasUsed = used.has(blockId); + const wasVisited = visited.has(blockId); + visited.add(blockId); + if (isUsed) { + used.add(blockId); + } + if (wasVisited && (wasUsed || !isUsed)) { return; } - visited.add(blockId); - const block = func.blocks.get(blockId)!; - const { terminal } = block; /* * Note that we visit successors in reverse order. This ensures that when we @@ -710,7 +695,7 @@ function getReversePostorderedBlocks(func: HIR): HIR["blocks"] { * // bb1 * x = 1; * } else { - * // b2 + * // bb2 * x = 2; * } * // bb3 @@ -721,104 +706,50 @@ function getReversePostorderedBlocks(func: HIR): HIR["blocks"] { * program order for visual debugging. By visiting the successors in reverse order * (eg bb2 then bb1), we ensure that they get reversed back to the correct order. */ - switch (terminal.kind) { - case "return": - case "throw": { - // no-op, no successors - break; - } - case "goto": { - visit(terminal.block); - break; - } - case "if": { - /* - * can ignore fallthrough, if its reachable it will be reached through - * consequent/alternate - */ - const { consequent, alternate } = terminal; - visit(alternate); - visit(consequent); - break; - } - case "branch": { - const { consequent, alternate } = terminal; - visit(alternate); - visit(consequent); - break; - } - case "switch": { - /* - * can ignore fallthrough, if its reachable it will be reached through - * a case - */ - const { cases } = terminal; - for (const case_ of [...cases].reverse()) { - visit(case_.block); - } - break; - } - case "optional": - case "ternary": - case "logical": { - visit(terminal.test); - break; - } - case "do-while": { - visit(terminal.loop); - break; - } - case "while": { - visit(terminal.test); - break; - } - case "for": - case "for-in": - case "for-of": { - visit(terminal.init); - break; - } - case "label": { - visit(terminal.block); - break; - } - case "sequence": { - visit(terminal.block); - break; - } - case "maybe-throw": { - visit(terminal.handler); - visit(terminal.continuation); - break; - } - case "try": { - visit(terminal.block); - break; - } - case "scope": { - visit(terminal.block); - break; - } - case "unreachable": - case "unsupported": { - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\`` - ); + const block = func.blocks.get(blockId)!; + const successors = [...eachTerminalSuccessor(block.terminal)].reverse(); + const fallthrough = terminalFallthrough(block.terminal); + + /** + * Fallthrough blocks are only used to record original program block structure. If the + * fallthrough is actually reachable, it will be reached through terminal successors. + * To retain program structure, we visit fallthrough blocks first (marking them as not + * actually used yet) to ensure their block IDs emitted in the correct order. + */ + if (fallthrough != null) { + if (isUsed) { + usedFallthroughs.add(fallthrough); } + visit(fallthrough, false); + } + for (const successor of successors) { + visit(successor, isUsed); } - postorder.push(blockId); + if (!wasVisited) { + postorder.push(blockId); + } } - visit(func.entry); - - const blocks = new Map(); + visit(func.entry, true); + const blocks = new Map(); for (const blockId of postorder.reverse()) { - blocks.set(blockId, func.blocks.get(blockId)!); + const block = func.blocks.get(blockId)!; + if (used.has(blockId)) { + blocks.set(blockId, func.blocks.get(blockId)!); + } else if (usedFallthroughs.has(blockId)) { + blocks.set(blockId, { + ...block, + instructions: [], + terminal: { + kind: "unreachable", + id: block.terminal.id, + loc: block.terminal.loc, + }, + }); + } + // otherwise this block is unreachable } + return blocks; } @@ -847,6 +778,14 @@ export function markPredecessors(func: HIR): void { const visited: Set = new Set(); function visit(blockId: BlockId, prevBlock: BasicBlock | null): void { const block = func.blocks.get(blockId)!; + if (block == null) { + return; + } + CompilerError.invariant(block != null, { + reason: "unexpected missing block", + description: `block ${blockId}`, + loc: GeneratedSource, + }); if (prevBlock) { block.preds.add(prevBlock.id); } @@ -879,7 +818,7 @@ function getTargetIfIndirection(block: BasicBlock): number | null { /* * Finds try terminals where the handler is unreachable, and converts the try - * to a goto(terminal.fallthrough) + * to a goto(terminal.block) */ export function removeUnnecessaryTryCatch(fn: HIR): void { for (const [, block] of fn.blocks) { @@ -887,6 +826,9 @@ export function removeUnnecessaryTryCatch(fn: HIR): void { block.terminal.kind === "try" && !fn.blocks.has(block.terminal.handler) ) { + const handlerId = block.terminal.handler; + const fallthroughId = block.terminal.fallthrough; + const fallthrough = fn.blocks.get(fallthroughId); block.terminal = { kind: "goto", block: block.terminal.block, @@ -894,6 +836,15 @@ export function removeUnnecessaryTryCatch(fn: HIR): void { loc: block.terminal.loc, variant: GotoVariant.Break, }; + + if (fallthrough != null) { + if (fallthrough.preds.size === 1 && fallthrough.preds.has(handlerId)) { + // delete fallthrough + fn.blocks.delete(fallthroughId); + } else { + fallthrough.preds.delete(handlerId); + } + } } } } diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts index 6311eb5def..44b50a8b49 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/MergeConsecutiveBlocks.ts @@ -13,8 +13,8 @@ import { HIRFunction, Instruction, } from "./HIR"; -import { markPredecessors, removeUnreachableFallthroughs } from "./HIRBuilder"; -import { mapOptionalFallthroughs, terminalFallthrough } from "./visitors"; +import { markPredecessors } from "./HIRBuilder"; +import { terminalFallthrough, terminalHasFallthrough } from "./visitors"; /* * Merges sequences of blocks that will always execute consecutively — @@ -113,10 +113,11 @@ export function mergeConsecutiveBlocks(fn: HIRFunction): void { fn.body.blocks.delete(block.id); } markPredecessors(fn.body); - for (const [, block] of fn.body.blocks) { - mapOptionalFallthroughs(block.terminal, (blockId) => merged.get(blockId)); + for (const [, { terminal }] of fn.body.blocks) { + if (terminalHasFallthrough(terminal)) { + terminal.fallthrough = merged.get(terminal.fallthrough); + } } - removeUnreachableFallthroughs(fn.body); } class MergedBlocks { diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/index.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/index.ts index b90bc8bd18..bb44717058 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/index.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/index.ts @@ -23,7 +23,6 @@ export { markInstructionIds, markPredecessors, removeUnnecessaryTryCatch, - removeUnreachableFallthroughs, reversePostorderBlocks, } from "./HIRBuilder"; export { mergeConsecutiveBlocks } from "./MergeConsecutiveBlocks"; diff --git a/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts index e56259835c..1662bbd07c 100644 --- a/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-forget/src/HIR/visitors.ts @@ -634,8 +634,7 @@ export function mapTerminalSuccessors( case "if": { const consequent = fn(terminal.consequent); const alternate = fn(terminal.alternate); - const fallthrough = - terminal.fallthrough !== null ? fn(terminal.fallthrough) : null; + const fallthrough = fn(terminal.fallthrough); return { kind: "if", test: terminal.test, @@ -666,8 +665,7 @@ export function mapTerminalSuccessors( block: target, }; }); - const fallthrough = - terminal.fallthrough !== null ? fn(terminal.fallthrough) : null; + const fallthrough = fn(terminal.fallthrough); return { kind: "switch", test: terminal.test, @@ -794,8 +792,7 @@ export function mapTerminalSuccessors( } case "label": { const block = fn(terminal.block); - const fallthrough = - terminal.fallthrough !== null ? fn(terminal.fallthrough) : null; + const fallthrough = fn(terminal.fallthrough); return { kind: "label", block, @@ -829,8 +826,7 @@ export function mapTerminalSuccessors( case "try": { const block = fn(terminal.block); const handler = fn(terminal.handler); - const fallthrough = - terminal.fallthrough !== null ? fn(terminal.fallthrough) : null; + const fallthrough = fn(terminal.fallthrough); return { kind: "try", block, @@ -866,12 +862,10 @@ export function mapTerminalSuccessors( } } -/* - * Helper to get a terminal's fallthrough. The main reason to extract this as a helper - * function is to ensure that we use an exhaustive switch to ensure that we add new terminal - * variants as appropriate. - */ -export function terminalFallthrough(terminal: Terminal): BlockId | null { +export function terminalHasFallthrough< + T extends Terminal, + U extends T & { fallthrough: BlockId } +>(terminal: T): terminal is U { switch (terminal.kind) { case "maybe-throw": case "branch": @@ -880,7 +874,8 @@ export function terminalFallthrough(terminal: Terminal): BlockId | null { case "throw": case "unreachable": case "unsupported": { - return null; + const _: undefined = terminal.fallthrough; + return false; } case "try": case "do-while": @@ -896,7 +891,8 @@ export function terminalFallthrough(terminal: Terminal): BlockId | null { case "ternary": case "while": case "scope": { - return terminal.fallthrough; + const _: BlockId = terminal.fallthrough; + return true; } default: { assertExhaustive( @@ -907,104 +903,16 @@ export function terminalFallthrough(terminal: Terminal): BlockId | null { } } -export function mapOptionalFallthroughs( - terminal: Terminal, - fn: (block: BlockId) => BlockId | null -): void { - switch (terminal.kind) { - case "maybe-throw": - case "branch": - case "goto": - case "return": - case "throw": - case "unreachable": - case "unsupported": { - return; - } - /* - * NOTE: TypeScript has a bug where it does not correctly model properties whose values are - * non-null in some cases and nullable in other cases, if those cases are joined together. - * Thus we use one block per case here to ensure that any changes to the types will cause - * a compiler error. - */ - case "do-while": { - const _: BlockId = terminal.fallthrough; - break; - } - case "for-of": { - const _: BlockId = terminal.fallthrough; - break; - } - case "for-in": { - const _: BlockId = terminal.fallthrough; - break; - } - case "for": { - const _: BlockId = terminal.fallthrough; - break; - } - case "logical": { - const _: BlockId = terminal.fallthrough; - break; - } - case "optional": { - const _: BlockId = terminal.fallthrough; - break; - } - case "ternary": { - const _: BlockId = terminal.fallthrough; - break; - } - case "while": { - const _: BlockId = terminal.fallthrough; - break; - } - case "scope": { - const _: BlockId = terminal.fallthrough; - break; - } - case "switch": { - if (terminal.fallthrough !== null) { - terminal.fallthrough = fn(terminal.fallthrough); - } else { - terminal.fallthrough = null; - } - break; - } - case "if": { - if (terminal.fallthrough !== null) { - terminal.fallthrough = fn(terminal.fallthrough); - } else { - terminal.fallthrough = null; - } - break; - } - case "label": { - if (terminal.fallthrough !== null) { - terminal.fallthrough = fn(terminal.fallthrough); - } else { - terminal.fallthrough = null; - } - break; - } - case "sequence": { - const _: BlockId = terminal.fallthrough; - break; - } - case "try": { - if (terminal.fallthrough !== null) { - terminal.fallthrough = fn(terminal.fallthrough); - } else { - terminal.fallthrough = null; - } - break; - } - default: { - assertExhaustive( - terminal, - `Unexpected terminal kind \`${(terminal as any).kind}\`` - ); - } +/* + * Helper to get a terminal's fallthrough. The main reason to extract this as a helper + * function is to ensure that we use an exhaustive switch to ensure that we add new terminal + * variants as appropriate. + */ +export function terminalFallthrough(terminal: Terminal): BlockId | null { + if (terminalHasFallthrough(terminal)) { + return terminal.fallthrough; + } else { + return null; } } diff --git a/compiler/packages/babel-plugin-react-forget/src/Optimization/ConstantPropagation.ts b/compiler/packages/babel-plugin-react-forget/src/Optimization/ConstantPropagation.ts index 58ffa08eb0..4b79f7ddff 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Optimization/ConstantPropagation.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Optimization/ConstantPropagation.ts @@ -23,7 +23,6 @@ import { markInstructionIds, markPredecessors, mergeConsecutiveBlocks, - removeUnreachableFallthroughs, reversePostorderBlocks, } from "../HIR"; import { @@ -72,7 +71,6 @@ function constantPropagationImpl(fn: HIRFunction, constants: Constants): void { * Re-run minification of the graph (incl reordering instruction ids) */ reversePostorderBlocks(fn.body); - removeUnreachableFallthroughs(fn.body); removeUnreachableForUpdates(fn.body); removeDeadDoWhileStatements(fn.body); removeUnnecessaryTryCatch(fn.body); diff --git a/compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts b/compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts index 22758f0e7c..f73432e81f 100644 --- a/compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts +++ b/compiler/packages/babel-plugin-react-forget/src/Optimization/PruneMaybeThrows.ts @@ -15,7 +15,6 @@ import { assertConsistentIdentifiers, assertTerminalSuccessorsExist, mergeConsecutiveBlocks, - removeUnreachableFallthroughs, reversePostorderBlocks, } from "../HIR"; import { @@ -39,7 +38,6 @@ export function pruneMaybeThrows(fn: HIRFunction): void { * Re-run minification of the graph (incl reordering instruction ids) */ reversePostorderBlocks(fn.body); - removeUnreachableFallthroughs(fn.body); removeUnreachableForUpdates(fn.body); removeDeadDoWhileStatements(fn.body); removeUnnecessaryTryCatch(fn.body); 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 fab70abed6..994649429f 100644 --- a/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts +++ b/compiler/packages/babel-plugin-react-forget/src/ReactiveScopes/BuildReactiveFunction.ts @@ -112,7 +112,7 @@ class Driver { } case "if": { const fallthroughId = - terminal.fallthrough !== null && + this.cx.reachable(terminal.fallthrough) && !this.cx.isScheduled(terminal.fallthrough) ? terminal.fallthrough : null; @@ -176,7 +176,7 @@ class Driver { } case "switch": { const fallthroughId = - terminal.fallthrough !== null && + this.cx.reachable(terminal.fallthrough) && !this.cx.isScheduled(terminal.fallthrough) ? terminal.fallthrough : null; @@ -289,7 +289,7 @@ class Driver { } case "while": { const fallthroughId = - terminal.fallthrough !== null && + this.cx.reachable(terminal.fallthrough) && !this.cx.isScheduled(terminal.fallthrough) ? terminal.fallthrough : null; @@ -350,11 +350,9 @@ class Driver { ? terminal.loop : null; - const fallthroughId = - terminal.fallthrough !== null && - !this.cx.isScheduled(terminal.fallthrough) - ? terminal.fallthrough - : null; + const fallthroughId = !this.cx.isScheduled(terminal.fallthrough) + ? terminal.fallthrough + : null; const scheduleId = this.cx.scheduleLoop( terminal.fallthrough, @@ -437,11 +435,9 @@ class Driver { ? terminal.loop : null; - const fallthroughId = - terminal.fallthrough !== null && - !this.cx.isScheduled(terminal.fallthrough) - ? terminal.fallthrough - : null; + const fallthroughId = !this.cx.isScheduled(terminal.fallthrough) + ? terminal.fallthrough + : null; const scheduleId = this.cx.scheduleLoop( terminal.fallthrough, @@ -512,11 +508,9 @@ class Driver { ? terminal.loop : null; - const fallthroughId = - terminal.fallthrough !== null && - !this.cx.isScheduled(terminal.fallthrough) - ? terminal.fallthrough - : null; + const fallthroughId = !this.cx.isScheduled(terminal.fallthrough) + ? terminal.fallthrough + : null; const scheduleId = this.cx.scheduleLoop( terminal.fallthrough, @@ -626,7 +620,7 @@ class Driver { } case "label": { const fallthroughId = - terminal.fallthrough !== null && + this.cx.reachable(terminal.fallthrough) && !this.cx.isScheduled(terminal.fallthrough) ? terminal.fallthrough : null; @@ -747,7 +741,7 @@ class Driver { } case "try": { const fallthroughId = - terminal.fallthrough !== null && + this.cx.reachable(terminal.fallthrough) && !this.cx.isScheduled(terminal.fallthrough) ? terminal.fallthrough : null; @@ -1241,6 +1235,11 @@ class Context { this.#catchHandlers.add(block); } + reachable(id: BlockId): boolean { + const block = this.ir.blocks.get(id)!; + return block.terminal.kind !== "unreachable"; + } + /* * Record that the given block will be emitted (eg by the codegen of a parent node) * so that child nodes can avoid re-emitting it. diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md index 9ed207d171..c43bd6f804 100644 --- a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-in-nested-scope.expect.md @@ -40,38 +40,46 @@ import { unstable_useMemoCache as useMemoCache } from "react"; import { mutate, setProperty, throwErrorWithMessageIf } from "shared-runtime"; function useFoo(t0) { - const $ = useMemoCache(3); + const $ = useMemoCache(6); const { value, cond } = t0; + let y; let t1; if ($[0] !== value || $[1] !== cond) { t1 = Symbol.for("react.early_return_sentinel"); bb0: { - const y = [value]; - const x = { cond }; - try { - mutate(x); - throwErrorWithMessageIf(x.cond, "error"); - } catch { - setProperty(x, "henderson"); - t1 = x; - break bb0; + y = [value]; + let x; + if ($[4] !== cond) { + x = { cond }; + try { + mutate(x); + throwErrorWithMessageIf(x.cond, "error"); + } catch { + setProperty(x, "henderson"); + t1 = x; + break bb0; + } + + setProperty(x, "nevada"); + $[4] = cond; + $[5] = x; + } else { + x = $[5]; } - - setProperty(x, "nevada"); y.push(x); - - t1 = y; - break bb0; } $[0] = value; $[1] = cond; - $[2] = t1; + $[2] = y; + $[3] = t1; } else { - t1 = $[2]; + y = $[2]; + t1 = $[3]; } if (t1 !== Symbol.for("react.early_return_sentinel")) { return t1; } + return y; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md index 0cb7e5993a..5ef2a901d4 100644 --- a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-try-value-modified-in-catch.expect.md @@ -45,9 +45,6 @@ function Component(props) { t0 = e; break bb0; } - - t0 = null; - break bb0; } $[0] = props.y; $[1] = props.e; @@ -58,6 +55,7 @@ function Component(props) { if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; } + return null; } export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md index a490495a93..6d658808a9 100644 --- a/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md +++ b/compiler/packages/babel-plugin-react-forget/src/__tests__/fixtures/compiler/try-catch-with-catch-param.expect.md @@ -31,12 +31,13 @@ import { unstable_useMemoCache as useMemoCache } from "react"; const { throwInput } = require("shared-runtime"); function Component(props) { - const $ = useMemoCache(1); + const $ = useMemoCache(2); + let x; let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { t0 = Symbol.for("react.early_return_sentinel"); bb0: { - const x = []; + x = []; try { throwInput(x); } catch (t1) { @@ -45,17 +46,17 @@ function Component(props) { t0 = e; break bb0; } - - t0 = x; - break bb0; } - $[0] = t0; + $[0] = x; + $[1] = t0; } else { - t0 = $[0]; + x = $[0]; + t0 = $[1]; } if (t0 !== Symbol.for("react.early_return_sentinel")) { return t0; } + return x; } export const FIXTURE_ENTRYPOINT = {