Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 90 additions & 17 deletions core/parser/src/parser/expression/assignment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down Expand Up @@ -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)?
Expand Down Expand Up @@ -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(&parameters, 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(&parameters, 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(&parameters),
&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,
};

Expand Down
11 changes: 11 additions & 0 deletions core/parser/src/parser/expression/fpl_or_exp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ pub(crate) enum FormalParameterListOrExpression {
fpl: FormalParameterList,
span_start: Position,
},
AsyncArrowHead {
fpl: FormalParameterList,
params_start_position: Position,
},
Expression(ast::Expression),
}

Expand All @@ -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,
}),
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions core/parser/src/parser/expression/left_hand_side/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl<R> TokenParser<R> for Arguments
where
R: ReadChar,
{
type Output = (Box<[Expression]>, Span);
type Output = (Box<[Expression]>, Span, bool);

fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
let start = cursor
Expand All @@ -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()?;
Expand All @@ -83,6 +84,7 @@ where
}

if let Some(next) = cursor.next_if(Punctuator::CloseParen, interner)? {
has_trailing_comma = true;
break next.span().end();
}
}
Expand Down Expand Up @@ -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,
))
}
}
153 changes: 142 additions & 11 deletions core/parser/src/parser/expression/left_hand_side/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -63,27 +70,61 @@ impl<R> TokenParser<R> for CallExpression
where
R: ReadChar,
{
type Output = ast::Expression;
type Output = FormalParameterListOrExpression;

fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult<Self::Output> {
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()],
next_token.to_string(interner),
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))
}
}

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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<FormalParameterList> {
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(&parameters).contains(&Sym::AWAIT) {
return Err(Error::general(
"keyword `await` not allowed in this context",
args_span.start(),
));
}

Ok(parameters)
}
Loading
Loading