Skip to content

Commit

Permalink
feat(parser): parse a ? b ? (c = 0) : d => 1 : (e = 2) : f => 3 (#9229
Browse files Browse the repository at this point in the history
)

closes #9215
  • Loading branch information
Boshen committed Feb 19, 2025
1 parent d4d01c3 commit bde4126
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 100 deletions.
111 changes: 91 additions & 20 deletions crates/oxc_parser/src/js/arrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,28 @@ struct ArrowFunctionHead<'a> {
return_type: Option<Box<'a, TSTypeAnnotation<'a>>>,
r#async: bool,
span: Span,
has_return_colon: bool,
}

impl<'a> ParserImpl<'a> {
pub(super) fn try_parse_parenthesized_arrow_function_expression(
&mut self,
allow_return_type_in_arrow_function: bool,
) -> Result<Option<Expression<'a>>> {
match self.is_parenthesized_arrow_function_expression() {
Tristate::False => Ok(None),
Tristate::True => self.parse_parenthesized_arrow_function(),
Tristate::Maybe => self.parse_possible_parenthesized_arrow_function_expression(),
Tristate::True => self.parse_parenthesized_arrow_function_expression(
/* allow_return_type_in_arrow_function */ true,
),
Tristate::Maybe => self.parse_possible_parenthesized_arrow_function_expression(
allow_return_type_in_arrow_function,
),
}
}

pub(super) fn try_parse_async_simple_arrow_function_expression(
&mut self,
allow_return_type_in_arrow_function: bool,
) -> Result<Option<Expression<'a>>> {
if self.at(Kind::Async)
&& self.is_un_parenthesized_async_arrow_function_worker() == Tristate::True
Expand All @@ -36,7 +43,12 @@ impl<'a> ParserImpl<'a> {
self.bump_any(); // bump `async`
let expr = self.parse_binary_expression_or_higher(Precedence::Comma)?;
return self
.parse_simple_arrow_function_expression(span, expr, /* async */ true)
.parse_simple_arrow_function_expression(
span,
expr,
/* async */ true,
allow_return_type_in_arrow_function,
)
.map(Some);
}
Ok(None)
Expand Down Expand Up @@ -204,6 +216,7 @@ impl<'a> ParserImpl<'a> {
span: Span,
ident: Expression<'a>,
r#async: bool,
allow_return_type_in_arrow_function: bool,
) -> Result<Expression<'a>> {
let has_await = self.ctx.has_await();
self.ctx = self.ctx.union_await_if(r#async);
Expand Down Expand Up @@ -236,13 +249,17 @@ impl<'a> ParserImpl<'a> {

self.expect(Kind::Arrow)?;

self.parse_arrow_function_body(ArrowFunctionHead {
type_parameters: None,
params,
return_type: None,
r#async,
span,
})
self.parse_arrow_function_expression_body(
ArrowFunctionHead {
type_parameters: None,
params,
return_type: None,
r#async,
span,
has_return_colon: false,
},
allow_return_type_in_arrow_function,
)
}

fn parse_parenthesized_arrow_function_head(&mut self) -> Result<ArrowFunctionHead<'a>> {
Expand All @@ -262,6 +279,7 @@ impl<'a> ParserImpl<'a> {
self.error(diagnostics::ts_arrow_function_this_parameter(this_param.span));
}

let has_return_colon = self.is_ts && self.at(Kind::Colon);
let return_type = self.parse_ts_return_type_annotation(Kind::Arrow, false)?;

self.ctx = self.ctx.and_await(has_await);
Expand All @@ -272,27 +290,36 @@ impl<'a> ParserImpl<'a> {

self.expect(Kind::Arrow)?;

Ok(ArrowFunctionHead { type_parameters, params, return_type, r#async, span })
Ok(ArrowFunctionHead {
type_parameters,
params,
return_type,
r#async,
span,
has_return_colon,
})
}

/// [ConciseBody](https://tc39.es/ecma262/#prod-ConciseBody)
/// [lookahead ≠ {] `ExpressionBody`[?In, ~Await]
/// { `FunctionBody`[~Yield, ~Await] }
/// `ExpressionBody`[In, Await] :
/// `AssignmentExpression`[?In, ~Yield, ?Await]
fn parse_arrow_function_body(
fn parse_arrow_function_expression_body(
&mut self,
arrow_function_head: ArrowFunctionHead<'a>,
allow_return_type_in_arrow_function: bool,
) -> Result<Expression<'a>> {
let ArrowFunctionHead { type_parameters, params, return_type, r#async, span } =
let ArrowFunctionHead { type_parameters, params, return_type, r#async, span, .. } =
arrow_function_head;
let has_await = self.ctx.has_await();
let has_yield = self.ctx.has_yield();
self.ctx = self.ctx.and_await(r#async).and_yield(false);

let expression = !self.at(Kind::LCurly);
let body = if expression {
let expr = self.parse_assignment_expression_or_higher()?;
let expr = self
.parse_assignment_expression_or_higher_impl(allow_return_type_in_arrow_function)?;
let span = expr.span();
let expr_stmt = self.ast.statement_expression(span, expr);
self.ast.alloc_function_body(span, self.ast.vec(), self.ast.vec1(expr_stmt))
Expand All @@ -316,22 +343,66 @@ impl<'a> ParserImpl<'a> {
/// Section [Arrow Function](https://tc39.es/ecma262/#sec-arrow-function-definitions)
/// `ArrowFunction`[In, Yield, Await] :
/// `ArrowParameters`[?Yield, ?Await] [no `LineTerminator` here] => `ConciseBody`[?In]
fn parse_parenthesized_arrow_function(&mut self) -> Result<Option<Expression<'a>>> {
fn parse_parenthesized_arrow_function_expression(
&mut self,
allow_return_type_in_arrow_function: bool,
) -> Result<Option<Expression<'a>>> {
let head = self.parse_parenthesized_arrow_function_head()?;
self.parse_arrow_function_body(head).map(Some)
self.parse_arrow_function_expression_body(head, allow_return_type_in_arrow_function)
.map(Some)
}

fn parse_possible_parenthesized_arrow_function_expression(
&mut self,
allow_return_type_in_arrow_function: bool,
) -> Result<Option<Expression<'a>>> {
let pos = self.cur_token().start;
if self.state.not_parenthesized_arrow.contains(&pos) {
return Ok(None);
}
if let Some(head) = self.try_parse(ParserImpl::parse_parenthesized_arrow_function_head) {
return self.parse_arrow_function_body(head).map(Some);

let checkpoint = self.checkpoint();

let Ok(head) = self.parse_parenthesized_arrow_function_head() else {
self.state.not_parenthesized_arrow.insert(pos);
self.rewind(checkpoint);
return Ok(None);
};

let has_return_colon = head.has_return_colon;

let body =
self.parse_arrow_function_expression_body(head, allow_return_type_in_arrow_function)?;

// Given:
// x ? y => ({ y }) : z => ({ z })
// We try to parse the body of the first arrow function by looking at:
// ({ y }) : z => ({ z })
// This is a valid arrow function with "z" as the return type.
//
// But, if we're in the true side of a conditional expression, this colon
// terminates the expression, so we cannot allow a return type if we aren't
// certain whether or not the preceding text was parsed as a parameter list.
//
// For example,
// a() ? (b: number, c?: string): void => d() : e
// is determined by isParenthesizedArrowFunctionExpression to unambiguously
// be an arrow expression, so we allow a return type.
if !allow_return_type_in_arrow_function && has_return_colon {
// However, if the arrow function we were able to parse is followed by another colon
// as in:
// a ? (x): string => x : null
// Then allow the arrow function, and treat the second colon as terminating
// the conditional expression. It's okay to do this because this code would
// be a syntax error in JavaScript (as the second colon shouldn't be there).

if !self.at(Kind::Colon) {
self.state.not_parenthesized_arrow.insert(pos);
self.rewind(checkpoint);
return Ok(None);
}
}
self.state.not_parenthesized_arrow.insert(pos);
Ok(None)

Ok(Some(body))
}
}
52 changes: 39 additions & 13 deletions crates/oxc_parser/src/js/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1076,32 +1076,47 @@ impl<'a> ParserImpl<'a> {
&mut self,
lhs_span: Span,
lhs: Expression<'a>,
allow_return_type_in_arrow_function: bool,
) -> Result<Expression<'a>> {
if !self.eat(Kind::Question) {
return Ok(lhs);
}
let consequent = self.context(
Context::In,
Context::empty(),
Self::parse_assignment_expression_or_higher,
)?;
let consequent = self.context(Context::In, Context::empty(), |p| {
p.parse_assignment_expression_or_higher_impl(
/* allow_return_type_in_arrow_function */ false,
)
})?;
self.expect(Kind::Colon)?;
let alternate = self.parse_assignment_expression_or_higher()?;
let alternate =
self.parse_assignment_expression_or_higher_impl(allow_return_type_in_arrow_function)?;
Ok(self.ast.expression_conditional(self.end_span(lhs_span), lhs, consequent, alternate))
}

/// `AssignmentExpression`[In, Yield, Await] :
pub(crate) fn parse_assignment_expression_or_higher(&mut self) -> Result<Expression<'a>> {
self.parse_assignment_expression_or_higher_impl(
/* allow_return_type_in_arrow_function */ true,
)
}

pub(crate) fn parse_assignment_expression_or_higher_impl(
&mut self,
allow_return_type_in_arrow_function: bool,
) -> Result<Expression<'a>> {
// [+Yield] YieldExpression
if self.is_yield_expression() {
return self.parse_yield_expression();
}
// `(x) => {}`
if let Some(arrow_expr) = self.try_parse_parenthesized_arrow_function_expression()? {
// `() => {}`, `(x) => {}`
if let Some(arrow_expr) = self.try_parse_parenthesized_arrow_function_expression(
allow_return_type_in_arrow_function,
)? {
return Ok(arrow_expr);
}
// `async x => {}`
if let Some(arrow_expr) = self.try_parse_async_simple_arrow_function_expression()? {
if let Some(arrow_expr) = self
.try_parse_async_simple_arrow_function_expression(allow_return_type_in_arrow_function)?
{
return Ok(arrow_expr);
}

Expand All @@ -1111,20 +1126,30 @@ impl<'a> ParserImpl<'a> {

// `x => {}`
if lhs.is_identifier_reference() && kind == Kind::Arrow {
return self.parse_simple_arrow_function_expression(span, lhs, /* async */ false);
return self.parse_simple_arrow_function_expression(
span,
lhs,
/* async */ false,
allow_return_type_in_arrow_function,
);
}

if kind.is_assignment_operator() {
return self.parse_assignment_expression_recursive(span, lhs);
return self.parse_assignment_expression_recursive(
span,
lhs,
allow_return_type_in_arrow_function,
);
}

self.parse_conditional_expression_rest(span, lhs)
self.parse_conditional_expression_rest(span, lhs, allow_return_type_in_arrow_function)
}

fn parse_assignment_expression_recursive(
&mut self,
span: Span,
lhs: Expression<'a>,
allow_return_type_in_arrow_function: bool,
) -> Result<Expression<'a>> {
let operator = map_assignment_operator(self.cur_kind());
// 13.15.5 Destructuring Assignment
Expand All @@ -1135,7 +1160,8 @@ impl<'a> ParserImpl<'a> {
// ArrayAssignmentPattern
let left = AssignmentTarget::cover(lhs, self)?;
self.bump_any();
let right = self.parse_assignment_expression_or_higher()?;
let right =
self.parse_assignment_expression_or_higher_impl(allow_return_type_in_arrow_function)?;
Ok(self.ast.expression_assignment(self.end_span(span), operator, left, right))
}

Expand Down
1 change: 1 addition & 0 deletions tasks/coverage/misc/pass/oxc-9215.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a ? b ? (c = 0) : d => 1 : (e = 2) : f => 3;
4 changes: 2 additions & 2 deletions tasks/coverage/snapshots/codegen_misc.snap
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
codegen_misc Summary:
AST Parsed : 31/31 (100.00%)
Positive Passed: 31/31 (100.00%)
AST Parsed : 32/32 (100.00%)
Positive Passed: 32/32 (100.00%)
Loading

0 comments on commit bde4126

Please sign in to comment.