mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
f13df97da3
## Root cause
A `try`/`catch` with a catch parameter lowers the catch binding to a
`DeclareLocal` (and a `StoreLocal` in the handler) with
`InstructionKind::Catch`. In `emit_store` these codegen to an **empty
statement** — a faithful port of the fork's `emitStore` (`case
InstructionKind.Catch: return t.emptyStatement()`) — because the catch
parameter itself is emitted separately, from the `Try` terminal's
`handler_binding`, so the declaration has no textual form.
oxc's other instruction paths drop such empty statements
(`ox_codegen_instruction_nullable` filters `EmptyStatement` -> `None`),
but the `StoreLocal`/`DeclareLocal`/`DeclareContext`/`Destructure` path
returned the result of `ox_codegen_store_or_declare` **directly**, so
the catch declaration's empty statement was pushed into the block. It
surfaced as a stray `;`:
```js
bb0: {
x = [];
; // <- stray, from the catch `DeclareLocal`
try { ... } catch (t1) { ... }
}
```
and, when the `try` was optimized away entirely, as a bare `;` in the
function body (`let x; ; return 42;`). The reference goldens
(`*.expect.md`) never contain such a `;`.
## Fix
`crates/oxc_react_compiler/src/react_compiler_reactive_scopes/codegen_reactive_function.rs`:
1. **Drop the empty statement** from the store/declare path in
`ox_codegen_instruction_nullable`, consistent with the other instruction
paths.
2. Removing it turned a labeled block that wrapped only a `try` into a
single-statement block, which tripped the labeled-block single-statement
**unwrap** and dropped the braces (`bb0: try {}` instead of `bb0: { try
{} catch {} }`). Restrict the unwrap so a labeled `try` keeps its braces
— matching the reference output, which always brace-wraps a labeled
`try` (the catch declaration originally padded the block to two
statements). Other single statements (`if`/`for`/`switch`/`while`,
nested labels such as `bb1: bb2: switch`) still unwrap as before.
## Fixtures changed
20 snapshots updated, each removing exactly one stray `;` (braces and
everything else preserved). ~29 `try`/`catch` fixtures now match the
fork golden at the compiled-component level. The change also cleaned two
non-`try-catch`-named fixtures that embed a `try`
(`repro-unreachable-code-early-return-in-useMemo`,
`error.bug-invariant-local-or-context-references`). The whole-suite diff
vs `main` is 20 files, all `-1` line; no snapshot regressed.
## Remaining work (out of scope, pre-existing)
The still-diverging `try`/`catch` fixtures differ only by codegen
formatting / error handling unrelated to the stray `;`:
- Prettier-style formatting: inline vs multi-line object/array literals
and `FIXTURE_ENTRYPOINT` params, multi-line vs inline JSX
(`try-catch-multiple-value-blocks`, `try-catch-within-object-method*`),
directive-comment placement (`propagate-scope-deps-hir-fork__*`,
`repro-nested-try-catch-in-usememo`).
- `finally` clauses and invalid-JSX-in-`catch` fixtures (`error.todo*`,
`invalid-jsx-*`, `fault-tolerance__error.try-finally-*`) where oxc's
error/`## Error` behavior diverges from the fork.
29 lines
545 B
Plaintext
29 lines
545 B
Plaintext
---
|
|
source: crates/oxc_react_compiler/tests/snapshot.rs
|
|
input_file: crates/oxc_react_compiler/fixtures/try-catch-within-function-expression.js
|
|
---
|
|
import { c as _c } from "react/compiler-runtime";
|
|
function Component(props) {
|
|
const $ = _c(1);
|
|
const callback = _temp;
|
|
let t0;
|
|
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
t0 = callback();
|
|
$[0] = t0;
|
|
} else {
|
|
t0 = $[0];
|
|
}
|
|
return t0;
|
|
}
|
|
function _temp() {
|
|
try {
|
|
return [];
|
|
} catch (t0) {
|
|
return;
|
|
}
|
|
}
|
|
export const FIXTURE_ENTRYPOINT = {
|
|
fn: Component,
|
|
params: [{}]
|
|
};
|