diff --git a/core/parser/src/parser/expression/assignment/mod.rs b/core/parser/src/parser/expression/assignment/mod.rs index 8fbfcb0874c..56f24cc005c 100644 --- a/core/parser/src/parser/expression/assignment/mod.rs +++ b/core/parser/src/parser/expression/assignment/mod.rs @@ -22,7 +22,7 @@ use crate::{ FormalParameterListOrExpression, assignment::{ arrow_function::{ArrowFunction, ConciseBody}, - async_arrow_function::AsyncArrowFunction, + async_arrow_function::{AsyncArrowFunction, AsyncConciseBody}, conditional::ConditionalExpression, r#yield::YieldExpression, }, @@ -118,28 +118,29 @@ where } // AsyncArrowFunction[?In, ?Yield, ?Await] TokenKind::Keyword((Keyword::Async, false)) => { - let skip_n = if cursor.peek_is_line_terminator(0, interner).or_abrupt()? { + let skip_n = if cursor + .peek_is_line_terminator(0, interner)? + .unwrap_or(false) + { 2 } else { 1 }; - let peek_1 = cursor.peek(1, interner).or_abrupt()?.kind().clone(); if !cursor - .peek_is_line_terminator(skip_n, interner) - .or_abrupt()? - && (matches!(peek_1, TokenKind::Punctuator(Punctuator::OpenParen)) - || (matches!( - peek_1, - TokenKind::IdentifierName(_) - | TokenKind::Keyword(( - Keyword::Yield | Keyword::Await | Keyword::Of, - _ - )) - ) && matches!( - cursor.peek(2, interner).or_abrupt()?.kind(), - TokenKind::Punctuator(Punctuator::Arrow) - ))) + .peek_is_line_terminator(skip_n, interner)? + .unwrap_or(true) + && let Some(peek_1) = cursor.peek(1, interner)? + && (matches!( + peek_1.kind(), + TokenKind::IdentifierName(_) + | TokenKind::Keyword(( + Keyword::Yield | Keyword::Await | Keyword::Of, + _ + )) + ) && cursor.peek(2, interner)?.is_some_and(|p2| { + matches!(p2.kind(), TokenKind::Punctuator(Punctuator::Arrow)) + })) { return Ok(AsyncArrowFunction::new(self.allow_in, self.allow_yield) .parse(cursor, interner)? @@ -231,6 +232,78 @@ where ) .into()); } + FormalParameterListOrExpression::AsyncArrowHead { + fpl: parameters, + params_start_position, + } => { + cursor.peek_expect_no_lineterminator(0, "async arrow function", interner)?; + + cursor.expect( + TokenKind::Punctuator(Punctuator::Arrow), + "async arrow function", + interner, + )?; + let arrow = cursor.arrow(); + cursor.set_arrow(true); + let body = AsyncConciseBody::new(self.allow_in).parse(cursor, interner)?; + cursor.set_arrow(arrow); + + // Early Error: ArrowFormalParameters are UniqueFormalParameters. + if parameters.has_duplicates() { + return Err(Error::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + params_start_position, + ))); + } + + // Early Error: It is a Syntax Error if CoverCallExpressionAndAsyncArrowHead Contains YieldExpression is true. + if contains(¶meters, ContainsSymbol::YieldExpression) { + return Err(Error::lex(LexError::Syntax( + "Yield expression not allowed in this context".into(), + params_start_position, + ))); + } + + // Early Error: It is a Syntax Error if CoverCallExpressionAndAsyncArrowHead Contains AwaitExpression is true. + if contains(¶meters, ContainsSymbol::AwaitExpression) { + return Err(Error::lex(LexError::Syntax( + "Await expression not allowed in this context".into(), + params_start_position, + ))); + } + + // Early Error: It is a Syntax Error if AsyncConciseBodyContainsUseStrict of AsyncConciseBody is true and + // IsSimpleParameterList of CoverCallExpressionAndAsyncArrowHead is false. + if body.strict() && !parameters.is_simple() { + return Err(Error::lex(LexError::Syntax( + "Illegal 'use strict' directive in function with non-simple parameter list" + .into(), + params_start_position, + ))); + } + + // Early Error: It is a Syntax Error if any element of the BoundNames of CoverCallExpressionAndAsyncArrowHead + // also occurs in the LexicallyDeclaredNames of AsyncConciseBody. + name_in_lexically_declared_names( + &bound_names(¶meters), + &lexically_declared_names(&body), + params_start_position, + interner, + )?; + + let linear_pos_end = body.linear_pos_end(); + let linear_span = start_linear_span.union(linear_pos_end); + + let body_span_end = body.span().end(); + return Ok(boa_ast::function::AsyncArrowFunction::new( + None, + parameters, + body, + linear_span, + Span::new(position, body_span_end), + ) + .into()); + } FormalParameterListOrExpression::Expression(expression) => expression, }; diff --git a/core/parser/src/parser/expression/fpl_or_exp.rs b/core/parser/src/parser/expression/fpl_or_exp.rs index 9867c1f7bda..5fa98ed09d8 100644 --- a/core/parser/src/parser/expression/fpl_or_exp.rs +++ b/core/parser/src/parser/expression/fpl_or_exp.rs @@ -6,6 +6,10 @@ pub(crate) enum FormalParameterListOrExpression { fpl: FormalParameterList, span_start: Position, }, + AsyncArrowHead { + fpl: FormalParameterList, + params_start_position: Position, + }, Expression(ast::Expression), } @@ -19,6 +23,13 @@ impl FormalParameterListOrExpression { position: span_start, }) } + FormalParameterListOrExpression::AsyncArrowHead { + params_start_position, + .. + } => Err(Error::General { + message: "invalid arrow-function arguments (parentheses around the arrow-function may help)".into(), + position: params_start_position, + }), } } } diff --git a/core/parser/src/parser/expression/left_hand_side/arguments.rs b/core/parser/src/parser/expression/left_hand_side/arguments.rs index 69e7d588d50..25fbd1c7149 100644 --- a/core/parser/src/parser/expression/left_hand_side/arguments.rs +++ b/core/parser/src/parser/expression/left_hand_side/arguments.rs @@ -51,7 +51,7 @@ impl TokenParser for Arguments where R: ReadChar, { - type Output = (Box<[Expression]>, Span); + type Output = (Box<[Expression]>, Span, bool); fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let start = cursor @@ -60,6 +60,7 @@ where .start(); let mut args = Vec::new(); + let mut has_trailing_comma = false; let end = loop { cursor.set_goal(InputElement::RegExp); let next_token = cursor.peek(0, interner).or_abrupt()?; @@ -83,6 +84,7 @@ where } if let Some(next) = cursor.next_if(Punctuator::CloseParen, interner)? { + has_trailing_comma = true; break next.span().end(); } } @@ -118,6 +120,10 @@ where } }; cursor.set_goal(InputElement::Div); - Ok((args.into_boxed_slice(), Span::new(start, end))) + Ok(( + args.into_boxed_slice(), + Span::new(start, end), + has_trailing_comma, + )) } } diff --git a/core/parser/src/parser/expression/left_hand_side/call.rs b/core/parser/src/parser/expression/left_hand_side/call.rs index 1b5a5c9ae20..3dae970e7ad 100644 --- a/core/parser/src/parser/expression/left_hand_side/call.rs +++ b/core/parser/src/parser/expression/left_hand_side/call.rs @@ -10,20 +10,27 @@ use super::arguments::Arguments; use crate::{ Error, - lexer::TokenKind, + lexer::{Token, TokenKind}, parser::{ AllowAwait, AllowYield, Cursor, OrAbrupt, ParseResult, TokenParser, - expression::{Expression, left_hand_side::template::TaggedTemplateLiteral}, + expression::{ + Expression, expression_to_formal_parameters, + fpl_or_exp::FormalParameterListOrExpression, + left_hand_side::template::TaggedTemplateLiteral, + }, }, source::ReadChar, }; use ast::function::PrivateName; use boa_ast::{ self as ast, Punctuator, Span, Spanned, + declaration::Variable, expression::{ Call, Identifier, access::{PrivatePropertyAccess, SimplePropertyAccess}, }, + function::{FormalParameter, FormalParameterList}, + operations::bound_names, }; use boa_interner::{Interner, Sym}; @@ -63,17 +70,12 @@ impl TokenParser for CallExpression where R: ReadChar, { - type Output = ast::Expression; + type Output = FormalParameterListOrExpression; fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let token = cursor.peek(0, interner).or_abrupt()?; - let lhs = if token.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) { - let (args, args_span) = - Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; - - Call::new(self.first_member_expr, args, args_span).into() - } else { + if token.kind() != &TokenKind::Punctuator(Punctuator::OpenParen) { let next_token = cursor.next(interner)?.expect("token vanished"); return Err(Error::expected( ["(".to_owned()], @@ -81,9 +83,48 @@ where next_token.span(), "call expression", )); + } + + let has_lt = cursor.peek_is_line_terminator(0, interner)?.unwrap_or(true); + let (args, args_span, has_trailing_comma) = + Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; + + let is_async = match &self.first_member_expr { + ast::Expression::Identifier(ident) => { + ident.sym() == Sym::ASYNC + && (ident + .span() + .end() + .column_number() + .saturating_sub(ident.span().start().column_number()) + == 5) + } + _ => false, }; - CallExpressionTail::new(self.allow_yield, self.allow_await, lhs).parse(cursor, interner) + let is_arrow = is_async + && !has_lt + && cursor.peek(0, interner)?.map(Token::kind) + == Some(&TokenKind::Punctuator(Punctuator::Arrow)) + && !cursor.peek_is_line_terminator(0, interner)?.unwrap_or(true); + + if is_arrow { + let fpl = arguments_to_formal_parameters( + &args, + has_trailing_comma, + cursor.strict(), + args_span, + )?; + return Ok(FormalParameterListOrExpression::AsyncArrowHead { + fpl, + params_start_position: args_span.start(), + }); + } + + let lhs = Call::new(self.first_member_expr, args, args_span).into(); + let expr = CallExpressionTail::new(self.allow_yield, self.allow_await, lhs) + .parse(cursor, interner)?; + Ok(FormalParameterListOrExpression::Expression(expr)) } } @@ -123,7 +164,7 @@ where let lhs_span_start = lhs.span().start(); match token.kind() { TokenKind::Punctuator(Punctuator::OpenParen) => { - let (args, args_span) = Arguments::new(self.allow_yield, self.allow_await) + let (args, args_span, _) = Arguments::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; lhs = Call::new(lhs, args, args_span).into(); } @@ -197,3 +238,93 @@ where Ok(lhs) } } + +/// Convert call arguments to a formal parameter list for an async arrow function. +fn arguments_to_formal_parameters( + args: &[ast::Expression], + has_trailing_comma: bool, + strict: bool, + args_span: Span, +) -> ParseResult { + let mut parameters = Vec::new(); + let num_args = args.len(); + + for (i, arg) in args.iter().enumerate() { + match arg { + ast::Expression::Spread(spread) => { + if i != num_args - 1 { + return Err(Error::general( + "rest parameter must be last formal parameter", + spread.span().start(), + )); + } + if has_trailing_comma { + return Err(Error::general( + "rest parameter must be last formal parameter", + args_span.end(), + )); + } + match spread.target() { + ast::Expression::Identifier(ident) => { + if strict && (*ident == Sym::EVAL || *ident == Sym::ARGUMENTS) { + return Err(Error::general( + format!( + "parameter name '{}' not allowed in strict mode", + if *ident == Sym::EVAL { + "eval" + } else { + "arguments" + } + ), + spread.span().start(), + )); + } + let declaration = Variable::from_identifier(*ident, None); + parameters.push(FormalParameter::new(declaration, true)); + } + ast::Expression::ObjectLiteral(object) => { + let pattern = object.to_pattern(strict).ok_or_else(|| { + Error::general( + "invalid object binding pattern in formal parameter list", + spread.span().start(), + ) + })?; + let declaration = Variable::from_pattern(pattern.into(), None); + parameters.push(FormalParameter::new(declaration, true)); + } + ast::Expression::ArrayLiteral(array) => { + let pattern = array.to_pattern(strict).ok_or_else(|| { + Error::general( + "invalid array binding pattern in formal parameter list", + spread.span().start(), + ) + })?; + let declaration = Variable::from_pattern(pattern.into(), None); + parameters.push(FormalParameter::new(declaration, true)); + } + _ => { + return Err(Error::unexpected( + ")".to_string(), + spread.span(), + "parenthesized expression with non-binding expression", + )); + } + } + } + expr => { + expression_to_formal_parameters(expr, &mut parameters, strict, args_span)?; + } + } + } + + let parameters = FormalParameterList::from(parameters); + + if bound_names(¶meters).contains(&Sym::AWAIT) { + return Err(Error::general( + "keyword `await` not allowed in this context", + args_span.start(), + )); + } + + Ok(parameters) +} diff --git a/core/parser/src/parser/expression/left_hand_side/member.rs b/core/parser/src/parser/expression/left_hand_side/member.rs index a06f8830ecc..83882804c76 100644 --- a/core/parser/src/parser/expression/left_hand_side/member.rs +++ b/core/parser/src/parser/expression/left_hand_side/member.rs @@ -142,8 +142,10 @@ where Some(next) if next.kind() == &TokenKind::Punctuator(Punctuator::OpenParen) => { - Arguments::new(self.allow_yield, self.allow_await) - .parse(cursor, interner)? + let (args, args_span, _) = + Arguments::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + (args, args_span) } _ => (Box::default(), lhs_inner.span()), }; diff --git a/core/parser/src/parser/expression/left_hand_side/mod.rs b/core/parser/src/parser/expression/left_hand_side/mod.rs index 6fb4acb1217..7a4c29f7047 100644 --- a/core/parser/src/parser/expression/left_hand_side/mod.rs +++ b/core/parser/src/parser/expression/left_hand_side/mod.rs @@ -154,7 +154,7 @@ where let mut lhs: FormalParameterListOrExpression = if let Some(start) = is_keyword_call(Keyword::Super, cursor, interner)? { cursor.advance(interner); - let (args, args_span) = + let (args, args_span, _) = Arguments::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; SuperCall::new(args, Span::new(start, args_span.end())).into() } else if let Some(start) = is_keyword_call(Keyword::Import, cursor, interner)? { @@ -278,7 +278,6 @@ where member.try_into_expression()?, ) .parse(cursor, interner)? - .into() } else { member } diff --git a/core/parser/src/parser/expression/left_hand_side/optional/mod.rs b/core/parser/src/parser/expression/left_hand_side/optional/mod.rs index adf1b857df6..716fb8e22b4 100644 --- a/core/parser/src/parser/expression/left_hand_side/optional/mod.rs +++ b/core/parser/src/parser/expression/left_hand_side/optional/mod.rs @@ -137,7 +137,7 @@ where let token = cursor.peek(0, interner).or_abrupt()?; let (item, item_span) = match token.kind() { TokenKind::Punctuator(Punctuator::OpenParen) => { - let (args, args_span) = Arguments::new(self.allow_yield, self.allow_await) + let (args, args_span, _) = Arguments::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; (OptionalOperationKind::Call { args }, args_span) } diff --git a/core/parser/src/parser/expression/mod.rs b/core/parser/src/parser/expression/mod.rs index 654e8af6ac8..0c01aed2451 100644 --- a/core/parser/src/parser/expression/mod.rs +++ b/core/parser/src/parser/expression/mod.rs @@ -15,6 +15,8 @@ mod primary; mod unary; mod update; +pub(crate) use primary::expression_to_formal_parameters; + pub(in crate::parser) mod await_expr; #[cfg(test)] diff --git a/core/parser/src/parser/expression/primary/mod.rs b/core/parser/src/parser/expression/primary/mod.rs index 6b532da224c..94b578a4166 100644 --- a/core/parser/src/parser/expression/primary/mod.rs +++ b/core/parser/src/parser/expression/primary/mod.rs @@ -553,7 +553,7 @@ where } /// Convert an expression to a formal parameter and append it to the given parameter list. -fn expression_to_formal_parameters( +pub(crate) fn expression_to_formal_parameters( node: &ast::Expression, parameters: &mut Vec, strict: bool, diff --git a/core/parser/src/parser/expression/tests.rs b/core/parser/src/parser/expression/tests.rs index 933667ca12b..2a74dd06023 100644 --- a/core/parser/src/parser/expression/tests.rs +++ b/core/parser/src/parser/expression/tests.rs @@ -1248,3 +1248,93 @@ fn check_non_reserved_identifiers() { check_non_reserved_identifier!("set"); check_non_reserved_identifier!("target"); } + +#[test] +fn parse_async_call() { + let interner = &mut Interner::default(); + check_script_parser( + "async(1);", + vec![ + Statement::Expression(Expression::from(Call::new( + Identifier::new(Sym::ASYNC, Span::new((1, 1), (1, 6))).into(), + Box::new([Literal::new(1, Span::new((1, 7), (1, 8))).into()]), + Span::new((1, 6), (1, 9)), + ))) + .into(), + ], + interner, + ); +} + +#[test] +fn parse_async_call_binary_op() { + let interner = &mut Interner::default(); + check_script_parser( + "async(1) + 2;", + vec![ + Statement::Expression(Expression::from(Binary::new( + ArithmeticOp::Add.into(), + Call::new( + Identifier::new(Sym::ASYNC, Span::new((1, 1), (1, 6))).into(), + Box::new([Literal::new(1, Span::new((1, 7), (1, 8))).into()]), + Span::new((1, 6), (1, 9)), + ) + .into(), + Literal::new(2, Span::new((1, 12), (1, 13))).into(), + ))) + .into(), + ], + interner, + ); +} + +#[test] +fn parse_async_identifier_at_eof() { + let interner = &mut Interner::default(); + check_script_parser( + "const async = 5;\nasync", + vec![ + Declaration::Lexical(LexicalDeclaration::Const( + vec![Variable::from_identifier( + Identifier::new(Sym::ASYNC, Span::new((1, 7), (1, 12))), + Some(Literal::new(5, Span::new((1, 15), (1, 16))).into()), + )] + .try_into() + .unwrap(), + )) + .into(), + Statement::Expression(Identifier::new(Sym::ASYNC, Span::new((2, 1), (2, 6))).into()) + .into(), + ], + interner, + ); +} + +#[test] +fn parse_async_arrow_expressions() { + let interner = &mut Interner::default(); + check_script_parser( + "async () => {}", + vec![ + Statement::Expression( + AsyncArrowFunction::new( + None, + FormalParameterList::default(), + FunctionBody::new(StatementList::default(), Span::new((1, 13), (1, 15))), + LinearSpan::new(LinearPosition::default(), LinearPosition::default()), + Span::new((1, 1), (1, 15)), + ) + .into(), + ) + .into(), + ], + interner, + ); +} + +#[test] +fn parse_invalid_async_arrow() { + check_invalid_script("async (1) => {}"); + check_invalid_script("async (...rest,) => {}"); + check_invalid_script("async (await) => {}"); +} diff --git a/core/parser/src/parser/statement/declaration/export.rs b/core/parser/src/parser/statement/declaration/export.rs index 78b8d8f26e4..1fcc84b2b01 100644 --- a/core/parser/src/parser/statement/declaration/export.rs +++ b/core/parser/src/parser/statement/declaration/export.rs @@ -10,7 +10,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export use crate::{ - lexer::{TokenKind, token::ContainsEscapeSequence}, + lexer::{Token, TokenKind, token::ContainsEscapeSequence}, parser::{ Error, OrAbrupt, ParseResult, TokenParser, cursor::Cursor, @@ -184,17 +184,43 @@ where } } TokenKind::Keyword((Keyword::Async, false)) => { - let next_token = cursor.peek(2, interner).or_abrupt()?; - if next_token.kind() == &TokenKind::Punctuator(Punctuator::Mul) { - AstExportDeclaration::DefaultAsyncGeneratorDeclaration( - AsyncGeneratorDeclaration::new(false, true, true) - .parse(cursor, interner)?, - ) + let skip_n = if cursor + .peek_is_line_terminator(0, interner)? + .unwrap_or(false) + { + 2 } else { - AstExportDeclaration::DefaultAsyncFunctionDeclaration( - AsyncFunctionDeclaration::new(false, true, true) - .parse(cursor, interner)?, - ) + 1 + }; + let is_line_terminator = cursor + .peek_is_line_terminator(skip_n, interner)? + .unwrap_or(true); + + if !is_line_terminator + && cursor.peek(1, interner)?.map(Token::kind) + == Some(&TokenKind::Keyword((Keyword::Function, false))) + { + let is_generator = cursor.peek(2, interner)?.map(Token::kind) + == Some(&TokenKind::Punctuator(Punctuator::Mul)); + + if is_generator { + AstExportDeclaration::DefaultAsyncGeneratorDeclaration( + AsyncGeneratorDeclaration::new(false, true, true) + .parse(cursor, interner)?, + ) + } else { + AstExportDeclaration::DefaultAsyncFunctionDeclaration( + AsyncFunctionDeclaration::new(false, true, true) + .parse(cursor, interner)?, + ) + } + } else { + let expr = AssignmentExpression::new(true, false, true) + .parse(cursor, interner)?; + + cursor.expect_semicolon("default expression export", interner)?; + + AstExportDeclaration::DefaultAssignmentExpression(expr) } } TokenKind::Keyword((Keyword::Class, false)) => { diff --git a/core/parser/src/parser/statement/declaration/tests.rs b/core/parser/src/parser/statement/declaration/tests.rs index 16bb745c43f..25ca28147bc 100644 --- a/core/parser/src/parser/statement/declaration/tests.rs +++ b/core/parser/src/parser/statement/declaration/tests.rs @@ -899,3 +899,31 @@ fn await_using_valid_identifiers() { result.err() ); } + +#[test] +fn export_default_async() { + let interner = &mut Interner::default(); + let source = Source::from_bytes("export default async;"); + let mut parser = Parser::new(source); + let scope = boa_ast::scope::Scope::new_global(); + let result = parser.parse_module(&scope, interner); + assert!( + result.is_ok(), + "Failed to parse export default async: {:?}", + result.err() + ); +} + +#[test] +fn export_default_async_arrow() { + let interner = &mut Interner::default(); + let source = Source::from_bytes("export default async () => {};"); + let mut parser = Parser::new(source); + let scope = boa_ast::scope::Scope::new_global(); + let result = parser.parse_module(&scope, interner); + assert!( + result.is_ok(), + "Failed to parse export default async arrow: {:?}", + result.err() + ); +} diff --git a/core/parser/src/parser/statement/iteration/for_statement.rs b/core/parser/src/parser/statement/iteration/for_statement.rs index e64d48ab6b9..bb18e7707ef 100644 --- a/core/parser/src/parser/statement/iteration/for_statement.rs +++ b/core/parser/src/parser/statement/iteration/for_statement.rs @@ -132,10 +132,9 @@ where .into(), ), TokenKind::Keyword((Keyword::Async, false)) if !r#await => { - if matches!( - cursor.peek(1, interner).or_abrupt()?.kind(), - TokenKind::Keyword((Keyword::Of, false)) - ) { + if cursor.peek(1, interner)?.is_some_and(|tok| { + matches!(tok.kind(), TokenKind::Keyword((Keyword::Of, false))) + }) { init_is_async_of = true; }