oxc-project / oxc-project/backlog

AST: Split Plain and Delegated Yield Expressions

Open
#233 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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 delegate boolean and optional argument on YieldExpression with an enum representing plain and delegated yield expressions.

A plain yield may omit its argument. A delegated yield* always requires an expression.

Motivation

ECMAScript permits three yield-expression forms:

yield;
yield value;
yield* iterable;

The current AST stores delegation and the argument independently:

pub struct YieldExpression<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub delegate: bool,
    pub argument: Option<Expression<'a>>,
}

This represents four combinations, including the invalid state:

delegate == true && argument == None

That state corresponds to yield* without the grammar-required assignment expression. Both
codegen and the formatter currently print it as invalid source:

if self.delegate {
    p.print_ascii_byte(b'*');
}
if let Some(argument) = self.argument.as_ref() {
    // Print the argument.
}

Downstream consumers also have to defend against the impossible combination. The async-generator
transform uses expr.argument.as_mut().map(...) after checking delegate, and lint rules that
operate on yield* repeat an optional-argument guard.

The parser already knows the grammar invariant. Once it consumes *, it unconditionally calls parse_assignment_expression_or_higher, even when the following token normally terminates a plain yield:

delegate = self.eat(Kind::Star);
// ...
if !not_assignment_expr || delegate {
    argument = Some(self.parse_assignment_expression_or_higher());
}

The invariant is therefore available at construction time but is discarded by the current field layout.

Current AST Shape

pub struct YieldExpression<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub delegate: bool,
    pub argument: Option<Expression<'a>>,
}

delegate and argument must be interpreted together by every parser, builder, transformer, printer, formatter, linter, and serializer.

Proposed AST Shape

Represent the two grammatical forms explicitly:

pub struct YieldExpression<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub kind: YieldExpressionKind<'a>,
}

pub enum YieldExpressionKind<'a> {
    Plain(Option<Expression<'a>>) = 0,
    Delegate(Expression<'a>) = 1,
}

Plain(None) represents yield, Plain(Some(argument)) represents yield argument, and
Delegate(argument) represents yield* argument.

The enum is a grouping detail, not a new ESTree or semantic node. Visiting either argument should
preserve the existing traversal position and YieldExpression ancestor identity.

The parser can construct the variants directly:

let kind = if self.eat(Kind::Star) {
    YieldExpressionKind::Delegate(self.parse_assignment_expression_or_higher())
} else {
    YieldExpressionKind::Plain(self.parse_optional_yield_argument())
};

For malformed input such as yield*;, the delegated branch must still invoke the required assignment-expression parser and retain its existing diagnostic and recovery behavior. If that
parser returns a recovered or dummy Expression, it can inhabit Delegate; if parsing is fatal,
the enclosing parser should recover without constructing an argument-less delegated yield. Do not
add Delegate(None) or an Invalid variant solely for recovery.

Syntax Mapping

Source form Proposed representation
yield Plain(None)
yield value Plain(Some(value))
yield* iterable Delegate(iterable)
yield\nvalue Plain(None) followed by a separate expression statement
yield* Invalid input; diagnose and recover with an expression or without constructing a delegated yield

Guaranteed Invariants

  • A delegated yield always contains an expression.
  • Only a plain yield may omit its argument.
  • Delegation and argument presence cannot be changed independently.
  • Codegen and the formatter cannot emit a bare yield* from a valid YieldExpression.
  • Consumers matching Delegate can access its argument without an optional-value guard.

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.

Research direction

Locate YieldExpression in the AST and follow its parser, codegen, formatter, async-generator transform, lint, visitor, and serializer uses. Start with parser construction, then trace each consumer's handling of plain and delegated forms. Done means valid yield syntax and traversal are preserved, delegated yields always contain an expression, and existing malformed-input recovery remains intact.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, rust
Domain
compilers
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.