Browse Source

Allow some keywords as identifiers (#2269)

This PR fixes #2275 

There are keywords that are allowed as identifiers.
https://tc39.es/ecma262/#sec-keywords-and-reserved-words
> Those that are always allowed as identifiers, but also appear as keywords within certain syntactic productions, at places where Identifier is not allowed: as, async, from, get, meta, of, set, and target.

This PR adds test cases for them, and fixes some cases such as
`class A { set(a, b) { } }`
`function of() { }`
`let obj = {async: true}`
`async()`
pull/2277/head
Choongwoo Han 2 years ago
parent
commit
2072f51faf
  1. 2
      boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs
  2. 4
      boa_engine/src/syntax/parser/expression/identifiers.rs
  3. 8
      boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs
  4. 57
      boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs
  5. 50
      boa_engine/src/syntax/parser/expression/primary/mod.rs
  6. 11
      boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs
  7. 58
      boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs
  8. 28
      boa_engine/src/syntax/parser/expression/tests.rs
  9. 155
      boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs
  10. 95
      boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/tests.rs
  11. 59
      boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs
  12. 18
      boa_engine/src/syntax/parser/statement/iteration/for_statement.rs
  13. 12
      boa_engine/src/syntax/parser/statement/mod.rs
  14. 8
      boa_interner/src/sym.rs

2
boa_engine/src/syntax/ast/node/declaration/class_decl/tests.rs

@ -44,6 +44,8 @@ fn class_declaration_elements() {
} }
set e(value) {} set e(value) {}
get e() {} get e() {}
set(a, b) {}
get(a, b) {}
}; };
"#, "#,
); );

4
boa_engine/src/syntax/parser/expression/identifiers.rs

@ -111,6 +111,8 @@ where
TokenKind::Keyword((Keyword::Await, _)) if !self.allow_await.0 => { TokenKind::Keyword((Keyword::Await, _)) if !self.allow_await.0 => {
Ok(Identifier::new(Sym::AWAIT)) Ok(Identifier::new(Sym::AWAIT))
} }
TokenKind::Keyword((Keyword::Async, _)) => Ok(Identifier::new(Sym::ASYNC)),
TokenKind::Keyword((Keyword::Of, _)) => Ok(Identifier::new(Sym::OF)),
_ => Err(ParseError::unexpected( _ => Err(ParseError::unexpected(
token.to_string(interner), token.to_string(interner),
token.span(), token.span(),
@ -217,6 +219,8 @@ where
)) ))
} }
TokenKind::Keyword((Keyword::Await, _)) if !self.allow_await.0 => Ok(Sym::AWAIT), TokenKind::Keyword((Keyword::Await, _)) if !self.allow_await.0 => Ok(Sym::AWAIT),
TokenKind::Keyword((Keyword::Async, _)) => Ok(Sym::ASYNC),
TokenKind::Keyword((Keyword::Of, _)) => Ok(Sym::OF),
_ => Err(ParseError::expected( _ => Err(ParseError::expected(
["identifier".to_owned()], ["identifier".to_owned()],
next_token.to_string(interner), next_token.to_string(interner),

8
boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs

@ -67,9 +67,11 @@ where
.ok_or(ParseError::AbruptEnd)? .ok_or(ParseError::AbruptEnd)?
.kind() .kind()
{ {
TokenKind::Identifier(_) | TokenKind::Keyword((Keyword::Yield | Keyword::Await, _)) => { TokenKind::Identifier(_)
Some(BindingIdentifier::new(false, false).parse(cursor, interner)?) | TokenKind::Keyword((
} Keyword::Yield | Keyword::Await | Keyword::Async | Keyword::Of,
_,
)) => Some(BindingIdentifier::new(false, false).parse(cursor, interner)?),
_ => self.name, _ => self.name,
}; };

57
boa_engine/src/syntax/parser/expression/primary/function_expression/tests.rs

@ -88,3 +88,60 @@ fn check_nested_function_expression() {
interner, interner,
); );
} }
#[test]
fn check_function_non_reserved_keyword() {
let genast = |keyword, interner: &mut Interner| {
vec![DeclarationList::Const(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("add"),
Some(
FunctionExpr::new::<_, _, StatementList>(
Some(interner.get_or_intern_static(keyword)),
FormalParameterList::default(),
vec![Return::new::<_, _, Option<Sym>>(Const::from(1), None).into()].into(),
)
.into(),
),
)]
.into(),
)
.into()]
};
let mut interner = Interner::default();
let ast = genast("as", &mut interner);
check_parser("const add = function as() { return 1; };", ast, interner);
let mut interner = Interner::default();
let ast = genast("async", &mut interner);
check_parser("const add = function async() { return 1; };", ast, interner);
let mut interner = Interner::default();
let ast = genast("from", &mut interner);
check_parser("const add = function from() { return 1; };", ast, interner);
let mut interner = Interner::default();
let ast = genast("get", &mut interner);
check_parser("const add = function get() { return 1; };", ast, interner);
let mut interner = Interner::default();
let ast = genast("meta", &mut interner);
check_parser("const add = function meta() { return 1; };", ast, interner);
let mut interner = Interner::default();
let ast = genast("of", &mut interner);
check_parser("const add = function of() { return 1; };", ast, interner);
let mut interner = Interner::default();
let ast = genast("set", &mut interner);
check_parser("const add = function set() { return 1; };", ast, interner);
let mut interner = Interner::default();
let ast = genast("target", &mut interner);
check_parser(
"const add = function target() { return 1; };",
ast,
interner,
);
}

50
boa_engine/src/syntax/parser/expression/primary/mod.rs

@ -98,11 +98,12 @@ where
// TODO: tok currently consumes the token instead of peeking, so the token // TODO: tok currently consumes the token instead of peeking, so the token
// isn't passed and consumed by parsers according to spec (EX: GeneratorExpression) // isn't passed and consumed by parsers according to spec (EX: GeneratorExpression)
let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let tok = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let tok_position = tok.span().start();
match tok.kind() { match tok.kind() {
TokenKind::Keyword((Keyword::This | Keyword::Async, true)) => Err(ParseError::general( TokenKind::Keyword((Keyword::This, true)) => Err(ParseError::general(
"Keyword must not contain escaped characters", "Keyword must not contain escaped characters",
tok.span().start(), tok_position,
)), )),
TokenKind::Keyword((Keyword::This, false)) => { TokenKind::Keyword((Keyword::This, false)) => {
cursor.next(interner).expect("token disappeared"); cursor.next(interner).expect("token disappeared");
@ -126,17 +127,31 @@ where
ClassExpression::new(self.name, self.allow_yield, self.allow_await) ClassExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner) .parse(cursor, interner)
} }
TokenKind::Keyword((Keyword::Async, false)) => { TokenKind::Keyword((Keyword::Async, contain_escaped_char)) => {
cursor.next(interner).expect("token disappeared"); let contain_escaped_char = *contain_escaped_char;
let mul_peek = cursor.peek(1, interner)?.ok_or(ParseError::AbruptEnd)?; match cursor.peek(1, interner)?.map(Token::kind) {
if mul_peek.kind() == &TokenKind::Punctuator(Punctuator::Mul) { Some(TokenKind::Keyword((Keyword::Function, _))) if contain_escaped_char => {
AsyncGeneratorExpression::new(self.name) Err(ParseError::general(
.parse(cursor, interner) "Keyword must not contain escaped characters",
.map(Node::from) tok_position,
} else { ))
AsyncFunctionExpression::new(self.name, self.allow_yield) }
Some(TokenKind::Keyword((Keyword::Function, _))) => {
cursor.next(interner).expect("token disappeared");
match cursor.peek(1, interner)?.map(Token::kind) {
Some(TokenKind::Punctuator(Punctuator::Mul)) => {
AsyncGeneratorExpression::new(self.name)
.parse(cursor, interner)
.map(Node::from)
}
_ => AsyncFunctionExpression::new(self.name, self.allow_yield)
.parse(cursor, interner)
.map(Node::from),
}
}
_ => IdentifierReference::new(self.allow_yield, self.allow_await)
.parse(cursor, interner) .parse(cursor, interner)
.map(Node::from) .map(Node::from),
} }
} }
TokenKind::Punctuator(Punctuator::OpenParen) => { TokenKind::Punctuator(Punctuator::OpenParen) => {
@ -174,11 +189,12 @@ where
Ok(Const::Null.into()) Ok(Const::Null.into())
} }
TokenKind::Identifier(_) TokenKind::Identifier(_)
| TokenKind::Keyword((Keyword::Let | Keyword::Yield | Keyword::Await, _)) => { | TokenKind::Keyword((
IdentifierReference::new(self.allow_yield, self.allow_await) Keyword::Let | Keyword::Yield | Keyword::Await | Keyword::Of,
.parse(cursor, interner) _,
.map(Node::from) )) => IdentifierReference::new(self.allow_yield, self.allow_await)
} .parse(cursor, interner)
.map(Node::from),
TokenKind::StringLiteral(lit) => { TokenKind::StringLiteral(lit) => {
let node = Const::from(*lit).into(); let node = Const::from(*lit).into();
cursor.next(interner).expect("token disappeared"); cursor.next(interner).expect("token disappeared");

11
boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs

@ -166,15 +166,22 @@ where
} }
//Async [AsyncMethod, AsyncGeneratorMethod] object methods //Async [AsyncMethod, AsyncGeneratorMethod] object methods
let is_keyword = !matches!(
cursor
.peek(1, interner)?
.ok_or(ParseError::AbruptEnd)?
.kind(),
TokenKind::Punctuator(Punctuator::OpenParen | Punctuator::Colon)
);
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
match token.kind() { match token.kind() {
TokenKind::Keyword((Keyword::Async, true)) => { TokenKind::Keyword((Keyword::Async, true)) if is_keyword => {
return Err(ParseError::general( return Err(ParseError::general(
"Keyword must not contain escaped characters", "Keyword must not contain escaped characters",
token.span().start(), token.span().start(),
)); ));
} }
TokenKind::Keyword((Keyword::Async, false)) => { TokenKind::Keyword((Keyword::Async, false)) if is_keyword => {
cursor.next(interner)?.expect("token disappeared"); cursor.next(interner)?.expect("token disappeared");
cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?; cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?;

58
boa_engine/src/syntax/parser/expression/primary/object_initializer/tests.rs

@ -3,7 +3,7 @@ use crate::syntax::{
node::{ node::{
object::{MethodDefinition, PropertyDefinition}, object::{MethodDefinition, PropertyDefinition},
AsyncFunctionExpr, AsyncGeneratorExpr, Declaration, DeclarationList, FormalParameter, AsyncFunctionExpr, AsyncGeneratorExpr, Declaration, DeclarationList, FormalParameter,
FormalParameterList, FormalParameterListFlags, FunctionExpr, Identifier, Object, FormalParameterList, FormalParameterListFlags, FunctionExpr, Identifier, Node, Object,
}, },
Const, Const,
}, },
@ -447,3 +447,59 @@ fn check_async_gen_method_lineterminator() {
", ",
); );
} }
#[test]
fn check_async_ordinary_method() {
let mut interner = Interner::default();
let object_properties = vec![PropertyDefinition::method_definition(
MethodDefinition::Ordinary(FunctionExpr::new(
None,
FormalParameterList::default(),
vec![],
)),
Node::Const(Const::from(interner.get_or_intern_static("async"))),
)];
check_parser(
"const x = {
async() {}
};
",
vec![DeclarationList::Const(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("x"),
Some(Object::from(object_properties).into()),
)]
.into(),
)
.into()],
interner,
);
}
#[test]
fn check_async_property() {
let mut interner = Interner::default();
let object_properties = vec![PropertyDefinition::property(
Node::Const(Const::from(interner.get_or_intern_static("async"))),
Const::from(true),
)];
check_parser(
"const x = {
async: true
};
",
vec![DeclarationList::Const(
vec![Declaration::new_with_identifier(
interner.get_or_intern_static("x"),
Some(Object::from(object_properties).into()),
)]
.into(),
)
.into()],
interner,
);
}

28
boa_engine/src/syntax/parser/expression/tests.rs

@ -590,3 +590,31 @@ fn check_logical_expressions() {
check_invalid("a ?? b || c"); check_invalid("a ?? b || c");
check_invalid("a || b ?? c"); check_invalid("a || b ?? c");
} }
#[track_caller]
fn check_non_reserved_identifier(keyword: &'static str) {
let mut interner = Interner::default();
check_parser(
format!("({})", keyword).as_str(),
vec![Identifier::new(interner.get_or_intern_static(keyword)).into()],
interner,
);
}
#[test]
fn check_non_reserved_identifiers() {
// https://tc39.es/ecma262/#sec-keywords-and-reserved-words
// Those that are always allowed as identifiers, but also appear as
// keywords within certain syntactic productions, at places where
// Identifier is not allowed: as, async, from, get, meta, of, set,
// and target.
check_non_reserved_identifier("as");
check_non_reserved_identifier("async");
check_non_reserved_identifier("from");
check_non_reserved_identifier("get");
check_non_reserved_identifier("meta");
check_non_reserved_identifier("of");
check_non_reserved_identifier("set");
check_non_reserved_identifier("target");
}

155
boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs

@ -1,3 +1,6 @@
#[cfg(test)]
mod tests;
use crate::syntax::{ use crate::syntax::{
ast::{ ast::{
node::{ node::{
@ -15,10 +18,7 @@ use crate::syntax::{
AssignmentExpression, AsyncGeneratorMethod, AsyncMethod, BindingIdentifier, AssignmentExpression, AsyncGeneratorMethod, AsyncMethod, BindingIdentifier,
GeneratorMethod, LeftHandSideExpression, PropertyName, GeneratorMethod, LeftHandSideExpression, PropertyName,
}, },
function::{ function::{FormalParameters, FunctionBody, UniqueFormalParameters, FUNCTION_BREAK_TOKENS},
FormalParameter, FormalParameters, FunctionBody, UniqueFormalParameters,
FUNCTION_BREAK_TOKENS,
},
statement::StatementList, statement::StatementList,
AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser, AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, TokenParser,
}, },
@ -571,11 +571,9 @@ where
| TokenKind::NullLiteral | TokenKind::NullLiteral
| TokenKind::PrivateIdentifier(_) | TokenKind::PrivateIdentifier(_)
| TokenKind::Punctuator( | TokenKind::Punctuator(
Punctuator::OpenBracket Punctuator::OpenBracket | Punctuator::Mul | Punctuator::OpenBlock,
| Punctuator::Mul
| Punctuator::OpenBlock
| Punctuator::Semicolon,
) => { ) => {
// this "static" is a keyword.
cursor.next(interner).expect("token disappeared"); cursor.next(interner).expect("token disappeared");
true true
} }
@ -585,6 +583,19 @@ where
_ => false, _ => false,
}; };
let is_keyword = !matches!(
cursor
.peek(1, interner)?
.ok_or(ParseError::AbruptEnd)?
.kind(),
TokenKind::Punctuator(
Punctuator::Assign
| Punctuator::CloseBlock
| Punctuator::OpenParen
| Punctuator::Semicolon
)
);
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
let position = token.span().start(); let position = token.span().start();
let element = match token.kind() { let element = match token.kind() {
@ -613,10 +624,6 @@ where
return Ok((Some(FunctionExpr::new(self.name, parameters, body)), None)); return Ok((Some(FunctionExpr::new(self.name, parameters, body)), None));
} }
TokenKind::Punctuator(Punctuator::Semicolon) if r#static => {
cursor.next(interner).expect("token disappeared");
ClassElementNode::FieldDefinition(Sym::STATIC.into(), None)
}
TokenKind::Punctuator(Punctuator::OpenBlock) if r#static => { TokenKind::Punctuator(Punctuator::OpenBlock) if r#static => {
cursor.next(interner).expect("token disappeared"); cursor.next(interner).expect("token disappeared");
let statement_list = if cursor let statement_list = if cursor
@ -722,13 +729,13 @@ where
} }
} }
} }
TokenKind::Keyword((Keyword::Async, true)) => { TokenKind::Keyword((Keyword::Async, true)) if is_keyword => {
return Err(ParseError::general( return Err(ParseError::general(
"Keyword must not contain escaped characters", "Keyword must not contain escaped characters",
token.span().start(), token.span().start(),
)); ));
} }
TokenKind::Keyword((Keyword::Async, false)) => { TokenKind::Keyword((Keyword::Async, false)) if is_keyword => {
cursor.next(interner).expect("token disappeared"); cursor.next(interner).expect("token disappeared");
cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?; cursor.peek_expect_no_lineterminator(0, "Async object methods", interner)?;
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
@ -806,64 +813,10 @@ where
} }
} }
} }
TokenKind::Identifier(Sym::GET) => { TokenKind::Identifier(Sym::GET) if is_keyword => {
cursor.next(interner).expect("token disappeared"); cursor.next(interner).expect("token disappeared");
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
match token.kind() { match token.kind() {
TokenKind::Punctuator(Punctuator::Assign) => {
cursor.next(interner).expect("token disappeared");
let strict = cursor.strict_mode();
cursor.set_strict_mode(true);
let rhs = AssignmentExpression::new(
Sym::GET,
true,
self.allow_yield,
self.allow_await,
)
.parse(cursor, interner)?;
cursor.expect_semicolon("expected semicolon", interner)?;
cursor.set_strict_mode(strict);
if r#static {
ClassElementNode::StaticFieldDefinition(Literal(Sym::GET), Some(rhs))
} else {
ClassElementNode::FieldDefinition(Literal(Sym::GET), Some(rhs))
}
}
TokenKind::Punctuator(Punctuator::OpenParen) => {
let strict = cursor.strict_mode();
cursor.set_strict_mode(true);
let params =
UniqueFormalParameters::new(false, false).parse(cursor, interner)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::OpenBlock),
"method definition",
interner,
)?;
let body = FunctionBody::new(false, false).parse(cursor, interner)?;
let token = cursor.expect(
TokenKind::Punctuator(Punctuator::CloseBlock),
"method definition",
interner,
)?;
// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// and IsSimpleParameterList of UniqueFormalParameters is false.
if body.strict() && !params.is_simple() {
return Err(ParseError::lex(LexError::Syntax(
"Illegal 'use strict' directive in function with non-simple parameter list"
.into(),
token.span().start(),
)));
}
cursor.set_strict_mode(strict);
let method =
MethodDefinition::Ordinary(FunctionExpr::new(None, params, body));
if r#static {
ClassElementNode::StaticMethodDefinition(Literal(Sym::GET), method)
} else {
ClassElementNode::MethodDefinition(Literal(Sym::GET), method)
}
}
TokenKind::PrivateIdentifier(Sym::CONSTRUCTOR) => { TokenKind::PrivateIdentifier(Sym::CONSTRUCTOR) => {
return Err(ParseError::general( return Err(ParseError::general(
"class constructor may not be a private method", "class constructor may not be a private method",
@ -975,72 +928,10 @@ where
} }
} }
} }
TokenKind::Identifier(Sym::SET) => { TokenKind::Identifier(Sym::SET) if is_keyword => {
cursor.next(interner).expect("token disappeared"); cursor.next(interner).expect("token disappeared");
let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; let token = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
match token.kind() { match token.kind() {
TokenKind::Punctuator(Punctuator::Assign) => {
cursor.next(interner).expect("token disappeared");
let strict = cursor.strict_mode();
cursor.set_strict_mode(true);
let rhs = AssignmentExpression::new(
Sym::SET,
true,
self.allow_yield,
self.allow_await,
)
.parse(cursor, interner)?;
cursor.expect_semicolon("expected semicolon", interner)?;
cursor.set_strict_mode(strict);
if r#static {
ClassElementNode::StaticFieldDefinition(Literal(Sym::SET), Some(rhs))
} else {
ClassElementNode::FieldDefinition(Literal(Sym::SET), Some(rhs))
}
}
TokenKind::Punctuator(Punctuator::OpenParen) => {
cursor.next(interner).expect("token disappeared");
let strict = cursor.strict_mode();
cursor.set_strict_mode(true);
let parameters: FormalParameterList = FormalParameter::new(false, false)
.parse(cursor, interner)?
.into();
cursor.expect(
TokenKind::Punctuator(Punctuator::CloseParen),
"class setter method definition",
interner,
)?;
cursor.expect(
TokenKind::Punctuator(Punctuator::OpenBlock),
"method definition",
interner,
)?;
let body = FunctionBody::new(false, false).parse(cursor, interner)?;
let token = cursor.expect(
TokenKind::Punctuator(Punctuator::CloseBlock),
"method definition",
interner,
)?;
// Early Error: It is a Syntax Error if FunctionBodyContainsUseStrict of FunctionBody is true
// and IsSimpleParameterList of UniqueFormalParameters is false.
if body.strict() && !parameters.is_simple() {
return Err(ParseError::lex(LexError::Syntax(
"Illegal 'use strict' directive in function with non-simple parameter list"
.into(),
token.span().start(),
)));
}
cursor.set_strict_mode(strict);
let method =
MethodDefinition::Ordinary(FunctionExpr::new(None, parameters, body));
if r#static {
ClassElementNode::StaticMethodDefinition(Literal(Sym::SET), method)
} else {
ClassElementNode::MethodDefinition(Literal(Sym::SET), method)
}
}
TokenKind::PrivateIdentifier(Sym::CONSTRUCTOR) => { TokenKind::PrivateIdentifier(Sym::CONSTRUCTOR) => {
return Err(ParseError::general( return Err(ParseError::general(
"class constructor may not be a private method", "class constructor may not be a private method",

95
boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/tests.rs

@ -0,0 +1,95 @@
use crate::syntax::{
ast::{
node::{
declaration::class_decl::ClassElement as ClassElementNode,
object::{MethodDefinition, PropertyName},
Class, FormalParameterList, FunctionExpr, Node,
},
Const,
},
parser::tests::check_parser,
};
use boa_interner::Interner;
#[test]
fn check_async_ordinary_method() {
let mut interner = Interner::default();
let elements = vec![ClassElementNode::MethodDefinition(
PropertyName::Computed(Node::Const(Const::from(
interner.get_or_intern_static("async"),
))),
MethodDefinition::Ordinary(FunctionExpr::new(
None,
FormalParameterList::default(),
vec![],
)),
)];
check_parser(
"class A {
async() { }
}
",
[Node::ClassDecl(Class::new(
interner.get_or_intern_static("A"),
None,
None,
elements,
))],
interner,
);
}
#[test]
fn check_async_field_initialization() {
let mut interner = Interner::default();
let elements = vec![ClassElementNode::FieldDefinition(
PropertyName::Computed(Node::Const(Const::from(
interner.get_or_intern_static("async"),
))),
Some(Node::Const(Const::from(1))),
)];
check_parser(
"class A {
async
= 1
}
",
[Node::ClassDecl(Class::new(
interner.get_or_intern_static("A"),
None,
None,
elements,
))],
interner,
);
}
#[test]
fn check_async_field() {
let mut interner = Interner::default();
let elements = vec![ClassElementNode::FieldDefinition(
PropertyName::Computed(Node::Const(Const::from(
interner.get_or_intern_static("async"),
))),
None,
)];
check_parser(
"class A {
async
}
",
[Node::ClassDecl(Class::new(
interner.get_or_intern_static("A"),
None,
None,
elements,
))],
interner,
);
}

59
boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/tests.rs

@ -23,27 +23,52 @@ fn function_declaration() {
/// Function declaration parsing with keywords. /// Function declaration parsing with keywords.
#[test] #[test]
fn function_declaration_keywords() { fn function_declaration_keywords() {
let mut interner = Interner::default(); let genast = |keyword, interner: &mut Interner| {
check_parser(
"function yield() {}",
vec![FunctionDecl::new( vec![FunctionDecl::new(
interner.get_or_intern_static("yield"), interner.get_or_intern_static(keyword),
FormalParameterList::default(), FormalParameterList::default(),
vec![], vec![],
) )
.into()], .into()]
interner, };
);
let mut interner = Interner::default(); let mut interner = Interner::default();
check_parser( let ast = genast("yield", &mut interner);
"function await() {}", check_parser("function yield() {}", ast, interner);
vec![FunctionDecl::new(
interner.get_or_intern_static("await"), let mut interner = Interner::default();
FormalParameterList::default(), let ast = genast("await", &mut interner);
vec![], check_parser("function await() {}", ast, interner);
)
.into()], let mut interner = Interner::default();
interner, let ast = genast("as", &mut interner);
); check_parser("function as() {}", ast, interner);
let mut interner = Interner::default();
let ast = genast("async", &mut interner);
check_parser("function async() {}", ast, interner);
let mut interner = Interner::default();
let ast = genast("from", &mut interner);
check_parser("function from() {}", ast, interner);
let mut interner = Interner::default();
let ast = genast("get", &mut interner);
check_parser("function get() {}", ast, interner);
let mut interner = Interner::default();
let ast = genast("meta", &mut interner);
check_parser("function meta() {}", ast, interner);
let mut interner = Interner::default();
let ast = genast("of", &mut interner);
check_parser("function of() {}", ast, interner);
let mut interner = Interner::default();
let ast = genast("set", &mut interner);
check_parser("function set() {}", ast, interner);
let mut interner = Interner::default();
let ast = genast("target", &mut interner);
check_parser("function target() {}", ast, interner);
} }

18
boa_engine/src/syntax/parser/statement/iteration/for_statement.rs

@ -101,6 +101,24 @@ where
Declaration::new(self.allow_yield, self.allow_await, false) Declaration::new(self.allow_yield, self.allow_await, false)
.parse(cursor, interner)?, .parse(cursor, interner)?,
), ),
TokenKind::Keyword((Keyword::Async, false)) => {
match cursor
.peek(1, interner)?
.ok_or(ParseError::AbruptEnd)?
.kind()
{
TokenKind::Keyword((Keyword::Of, _)) => {
return Err(ParseError::lex(LexError::Syntax(
"invalid left-hand side expression 'async' of a for-of loop".into(),
init_position,
)));
}
_ => Some(
Expression::new(None, false, self.allow_yield, self.allow_await)
.parse(cursor, interner)?,
),
}
}
TokenKind::Punctuator(Punctuator::Semicolon) => None, TokenKind::Punctuator(Punctuator::Semicolon) => None,
_ => Some( _ => Some(
Expression::new(None, false, self.allow_yield, self.allow_await) Expression::new(None, false, self.allow_yield, self.allow_await)

12
boa_engine/src/syntax/parser/statement/mod.rs

@ -359,11 +359,21 @@ where
match *tok.kind() { match *tok.kind() {
TokenKind::Keyword(( TokenKind::Keyword((
Keyword::Function | Keyword::Async | Keyword::Class | Keyword::Const | Keyword::Let, Keyword::Function | Keyword::Class | Keyword::Const | Keyword::Let,
_, _,
)) => { )) => {
Declaration::new(self.allow_yield, self.allow_await, true).parse(cursor, interner) Declaration::new(self.allow_yield, self.allow_await, true).parse(cursor, interner)
} }
TokenKind::Keyword((Keyword::Async, _)) => {
match cursor.peek(1, interner)?.map(Token::kind) {
Some(TokenKind::Keyword((Keyword::Function, _))) => {
Declaration::new(self.allow_yield, self.allow_await, true)
.parse(cursor, interner)
}
_ => Statement::new(self.allow_yield, self.allow_await, self.allow_return)
.parse(cursor, interner),
}
}
_ => Statement::new(self.allow_yield, self.allow_await, self.allow_return) _ => Statement::new(self.allow_yield, self.allow_await, self.allow_return)
.parse(cursor, interner), .parse(cursor, interner),
} }

8
boa_interner/src/sym.rs

@ -91,6 +91,12 @@ impl Sym {
/// Symbol for the `"false"` string. /// Symbol for the `"false"` string.
pub const FALSE: Self = unsafe { Self::new_unchecked(25) }; pub const FALSE: Self = unsafe { Self::new_unchecked(25) };
/// Symbol for the `"async"` string.
pub const ASYNC: Self = unsafe { Self::new_unchecked(26) };
/// Symbol for the `"of"` string.
pub const OF: Self = unsafe { Self::new_unchecked(27) };
/// Creates a new [`Sym`] from the provided `value`, or returns `None` if `index` is zero. /// Creates a new [`Sym`] from the provided `value`, or returns `None` if `index` is zero.
#[inline] #[inline]
pub(super) fn new(value: usize) -> Option<Self> { pub(super) fn new(value: usize) -> Option<Self> {
@ -153,6 +159,8 @@ pub(super) static COMMON_STRINGS: phf::OrderedSet<&'static str> = {
"anonymous", "anonymous",
"true", "true",
"false", "false",
"async",
"of",
}; };
// A `COMMON_STRINGS` of size `usize::MAX` would cause an overflow on our `Interner` // A `COMMON_STRINGS` of size `usize::MAX` would cause an overflow on our `Interner`
sa::const_assert!(COMMON_STRINGS.len() < usize::MAX); sa::const_assert!(COMMON_STRINGS.len() < usize::MAX);

Loading…
Cancel
Save