oxc-project / oxc-project/backlog

AST: Encode `TryStatement` Clauses as an Enum

Open
#234 1 comment 0 reactions 1 assignee View on GitHub

@camc314 is already working on this.

Since Jul 28, 2026.

Dominant language
No language data
Stars
7
Forks
0
PR merge metrics
No merged PRs in 30d

Description

Parent: oxc-project/backlog#210

Summary

Replace the independent optional handler and finalizer fields on TryStatement with an enum representing the three forms allowed by the ECMAScript grammar: catch only, finally only, or catch followed by finally.

Motivation

Every try statement must have a catch clause, a finally clause, or both:

try {} catch {}
try {} finally {}
try {} catch {} finally {}

The current Rust AST stores the two clauses independently:

pub handler: Option<Box<'a, CatchClause<'a>>>,
pub finalizer: Option<Box<'a, BlockStatement<'a>>>,

This permits a fourth state which is rejected by the grammar:

handler == None && finalizer == None

Codegen and the formatter interpret the fields independently, so a manually constructed invalid node can print a bare try {}. Semantic and control-flow analysis also has to branch on each optional field rather than on the grammatical form of the statement.

The parser currently creates this invalid state during error recovery. It reports the missing clause, but still constructs a TryStatement with both fields absent:

let handler = self.at(Kind::Catch).then(|| self.parse_catch_clause());
let finalizer = self.eat(Kind::Finally).then(|| self.parse_block());

if handler.is_none() && finalizer.is_none() {
    self.error(diagnostics::expect_catch_finally(...));
}

Statement::new_try_statement(..., handler, finalizer, self)

Parser recovery therefore needs an intentional representation rather than relying on a state that is invalid for every successfully parsed program.

Current AST Shape

pub struct TryStatement<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub block: Box<'a, BlockStatement<'a>>,
    pub handler: Option<Box<'a, CatchClause<'a>>>,
    pub finalizer: Option<Box<'a, BlockStatement<'a>>>,
}

The independent options represent four combinations, while the grammar permits only three.

Proposed AST Shape

Store the grammatical alternatives explicitly:

pub struct TryStatement<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub block: Box<'a, BlockStatement<'a>>,
    pub clauses: TryStatementClauses<'a>,
}

pub enum TryStatementClauses<'a> {
    Catch {
        handler: Box<'a, CatchClause<'a>>,
    },
    Finally {
        finalizer: Box<'a, BlockStatement<'a>>,
    },
    CatchFinally {
        handler: Box<'a, CatchClause<'a>>,
        finalizer: Box<'a, BlockStatement<'a>>,
    },
}

Syntax Mapping

Source form Proposed representation
try {} catch (error) {} Catch { handler }
try {} catch {} Catch { handler } with no catch parameter
try {} finally {} Finally { finalizer }
try {} catch {} finally {} CatchFinally { handler, finalizer }
try {} Invalid input; diagnose and construct an explicit recovery repair, not an empty clause set

Guaranteed Invariants

  • Every valid TryStatement contains at least one clause.
  • Catch always precedes finally when both clauses are present.
  • A catch clause and its body remain grouped in CatchClause.
  • Codegen cannot emit a bare try {} from a valid TryStatement.
  • Adding or removing a clause changes the grammatical form atomically.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.