oxc-project / oxc-project/backlog
AST: Split Plain and Delegated Yield Expressions
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 validYieldExpression. - Consumers matching
Delegatecan access its argument without an optional-value guard.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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