From d8af7b4ee5cc5c0d8f7243b698f98200e609c3c2 Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Mon, 11 Jul 2022 21:39:01 +0000 Subject: [PATCH] Implement arrow function parsing based on `CoverParenthesizedExpressionAndArrowParameterList` (#2171) Previously we parsed arrow functions without the relevant cover grammar `CoverParenthesizedExpressionAndArrowParameterList`. This leads to either arrow functions or parenthesized expressions not being parsed correctly. Implementing this is a bit tricky, as the cover grammar is being parsed in `PrimaryExpression` while arrow functions are parsed in `AssignmentExpression`. This means that we have to return the covered parameter list that was parsed via `CoverParenthesizedExpressionAndArrowParameterList` in `PrimaryExpression` to `AssignmentExpression`. Fortunately this works pretty good and now the full arrow function test suite, with the exception of a few tests that require other features, passes. This Pull Request changes the following: - Implement `CoverParenthesizedExpressionAndArrowParameterList` parsing. - Implement `CoverInitializedName` parsing in object literals. - Fix a bug where an environment would be wrongly removed from the environment stack when an expression in default function parameters throws. - Add more valid cases where on object literal can be converted to an object declaration pattern. - Implement `Expression` parsing manually to avoid some cases where the parser would prematurely throw an error. - Implement parsing of arrow functions via `CoverParenthesizedExpressionAndArrowParameterList`. - Remove unneeded `AllowIn` flag on array and object declaration pattern parsers. - Fix an of-by-one bug in the trace output. --- boa_engine/src/bytecompiler.rs | 9 + .../src/syntax/ast/node/declaration/mod.rs | 4 +- boa_engine/src/syntax/ast/node/mod.rs | 14 +- boa_engine/src/syntax/ast/node/object/mod.rs | 15 + .../syntax/ast/node/operator/assign/mod.rs | 98 +++++- boa_engine/src/syntax/ast/node/parameters.rs | 70 +++- boa_engine/src/syntax/ast/punctuator.rs | 10 +- boa_engine/src/syntax/parser/cursor/mod.rs | 14 + .../expression/assignment/arrow_function.rs | 4 +- .../parser/expression/assignment/mod.rs | 128 +++---- .../src/syntax/parser/expression/mod.rs | 65 +++- .../syntax/parser/expression/primary/mod.rs | 328 +++++++++++++++++- .../primary/object_initializer/mod.rs | 71 +++- boa_engine/src/syntax/parser/function/mod.rs | 19 +- .../parser/statement/declaration/lexical.rs | 10 +- boa_engine/src/syntax/parser/statement/mod.rs | 78 ++--- .../syntax/parser/statement/try_stm/catch.rs | 4 +- .../syntax/parser/statement/variable/mod.rs | 10 +- boa_engine/src/vm/code_block.rs | 19 +- boa_engine/src/vm/mod.rs | 2 +- 20 files changed, 782 insertions(+), 190 deletions(-) diff --git a/boa_engine/src/bytecompiler.rs b/boa_engine/src/bytecompiler.rs index 1731147b2e..c5c34f33f7 100644 --- a/boa_engine/src/bytecompiler.rs +++ b/boa_engine/src/bytecompiler.rs @@ -994,6 +994,11 @@ impl<'b> ByteCompiler<'b> { self.emit(Opcode::CopyDataProperties, &[0]); self.emit_opcode(Opcode::Pop); } + PropertyDefinition::CoverInitializedName(_, _) => { + return self.context.throw_syntax_error( + "invalid assignment pattern in object literal", + ); + } } } @@ -2025,6 +2030,8 @@ impl<'b> ByteCompiler<'b> { let env_label = if parameters.has_expressions() { compiler.code_block.num_bindings = compiler.context.get_binding_number(); compiler.context.push_compile_time_environment(true); + compiler.code_block.function_environment_push_location = + compiler.next_opcode_location(); Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)) } else { None @@ -2665,6 +2672,8 @@ impl<'b> ByteCompiler<'b> { let env_label = if expr.parameters().has_expressions() { compiler.code_block.num_bindings = compiler.context.get_binding_number(); compiler.context.push_compile_time_environment(true); + compiler.code_block.function_environment_push_location = + compiler.next_opcode_location(); Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)) } else { None diff --git a/boa_engine/src/syntax/ast/node/declaration/mod.rs b/boa_engine/src/syntax/ast/node/declaration/mod.rs index 22f8959527..9583945e64 100644 --- a/boa_engine/src/syntax/ast/node/declaration/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/mod.rs @@ -514,7 +514,7 @@ impl DeclarationPattern { #[derive(Clone, Debug, PartialEq)] pub struct DeclarationPatternObject { bindings: Vec, - init: Option, + pub(crate) init: Option, } impl ToInternedString for DeclarationPatternObject { @@ -613,7 +613,7 @@ impl DeclarationPatternObject { #[derive(Clone, Debug, PartialEq)] pub struct DeclarationPatternArray { bindings: Vec, - init: Option, + pub(crate) init: Option, } impl ToInternedString for DeclarationPatternArray { diff --git a/boa_engine/src/syntax/ast/node/mod.rs b/boa_engine/src/syntax/ast/node/mod.rs index 6e9c303251..b2af2900ea 100644 --- a/boa_engine/src/syntax/ast/node/mod.rs +++ b/boa_engine/src/syntax/ast/node/mod.rs @@ -249,6 +249,13 @@ pub enum Node { /// A call of the super constructor. [More information](./super_call/struct.SuperCall.html). SuperCall(SuperCall), + + /// A FormalParameterList. + /// + /// This is only used in the parser itself. + /// It is not a valid AST node. + #[doc(hidden)] + FormalParameterList(FormalParameterList), } impl From for Node { @@ -358,6 +365,7 @@ impl Node { Self::ClassDecl(ref decl) => decl.to_indented_string(interner, indentation), Self::ClassExpr(ref expr) => expr.to_indented_string(interner, indentation), Self::SuperCall(ref super_call) => super_call.to_interned_string(interner), + Self::FormalParameterList(_) => unreachable!(), } } @@ -740,6 +748,7 @@ impl Node { } } }, + PropertyDefinition::CoverInitializedName(_, _) => {} } } } @@ -1189,7 +1198,6 @@ impl Node { Node::Object(object) => { for property in object.properties() { match property { - PropertyDefinition::IdentifierReference(_) => {} PropertyDefinition::Property(name, init) => { if let Some(node) = name.computed() { if node.contains(symbol) { @@ -1212,6 +1220,8 @@ impl Node { } } } + PropertyDefinition::IdentifierReference(_) + | PropertyDefinition::CoverInitializedName(_, _) => {} } } } @@ -1237,6 +1247,7 @@ impl Node { } } } + Node::Yield(_) if symbol == ContainsSymbol::YieldExpression => return true, _ => {} } false @@ -1248,6 +1259,7 @@ impl Node { pub(crate) enum ContainsSymbol { SuperProperty, SuperCall, + YieldExpression, } impl ToInternedString for Node { diff --git a/boa_engine/src/syntax/ast/node/object/mod.rs b/boa_engine/src/syntax/ast/node/object/mod.rs index 89f91e8da9..8553381126 100644 --- a/boa_engine/src/syntax/ast/node/object/mod.rs +++ b/boa_engine/src/syntax/ast/node/object/mod.rs @@ -112,6 +112,13 @@ impl Object { }, ) } + PropertyDefinition::CoverInitializedName(ident, expr) => { + format!( + "{indentation}{} = {},\n", + interner.resolve_expect(*ident), + expr.to_no_indent_string(interner, indent_n + 1) + ) + } }); } buf.push_str(&format!("{}}}", " ".repeat(indent_n))); @@ -201,6 +208,14 @@ pub enum PropertyDefinition { /// [spec]: https://tc39.es/ecma262/#prod-PropertyDefinition /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#Spread_properties SpreadObject(Node), + + /// Cover grammar for when an object literal is used as an object biding pattern. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#prod-CoverInitializedName + CoverInitializedName(Sym, Node), } impl PropertyDefinition { diff --git a/boa_engine/src/syntax/ast/node/operator/assign/mod.rs b/boa_engine/src/syntax/ast/node/operator/assign/mod.rs index 7f49de4942..93cefd76d2 100644 --- a/boa_engine/src/syntax/ast/node/operator/assign/mod.rs +++ b/boa_engine/src/syntax/ast/node/operator/assign/mod.rs @@ -181,6 +181,68 @@ pub(crate) fn object_decl_to_declaration_pattern( default_init: None, }); } + (PropertyName::Literal(name), Node::Identifier(ident)) => { + bindings.push(BindingPatternTypeObject::SingleName { + ident: ident.sym(), + property_name: PropertyName::Literal(*name), + default_init: None, + }); + } + (PropertyName::Literal(name), Node::Object(object)) => { + let pattern = object_decl_to_declaration_pattern(object, strict)?; + bindings.push(BindingPatternTypeObject::BindingPattern { + ident: PropertyName::Literal(*name), + pattern, + default_init: None, + }); + } + (PropertyName::Literal(name), Node::ArrayDecl(array)) => { + let pattern = array_decl_to_declaration_pattern(array, strict)?; + bindings.push(BindingPatternTypeObject::BindingPattern { + ident: PropertyName::Literal(*name), + pattern, + default_init: None, + }); + } + (PropertyName::Literal(name), Node::Assign(assign)) => match assign.lhs() { + AssignTarget::Identifier(ident) if *name == ident.sym() => { + if strict && *name == Sym::EVAL { + return None; + } + if strict && RESERVED_IDENTIFIERS_STRICT.contains(name) { + return None; + } + + excluded_keys.push(*name); + bindings.push(BindingPatternTypeObject::SingleName { + ident: *name, + property_name: PropertyName::Literal(*name), + default_init: Some(assign.rhs().clone()), + }); + } + AssignTarget::Identifier(ident) => { + bindings.push(BindingPatternTypeObject::SingleName { + ident: ident.sym(), + property_name: PropertyName::Literal(*name), + default_init: Some(assign.rhs().clone()), + }); + } + AssignTarget::DeclarationPattern(pattern) => { + bindings.push(BindingPatternTypeObject::BindingPattern { + ident: PropertyName::Literal(*name), + pattern: pattern.clone(), + default_init: Some(assign.rhs().clone()), + }); + } + _ => return None, + }, + (PropertyName::Computed(name), Node::Identifier(ident)) => { + bindings.push(BindingPatternTypeObject::SingleName { + ident: ident.sym(), + property_name: PropertyName::Computed(name.clone()), + default_init: None, + }); + } _ => return None, }, PropertyDefinition::SpreadObject(spread) => { @@ -204,6 +266,17 @@ pub(crate) fn object_decl_to_declaration_pattern( } } PropertyDefinition::MethodDefinition(_, _) => return None, + PropertyDefinition::CoverInitializedName(ident, expr) => { + if strict && (*ident == Sym::EVAL || *ident == Sym::ARGUMENTS) { + return None; + } + + bindings.push(BindingPatternTypeObject::SingleName { + ident: *ident, + property_name: PropertyName::Literal(*ident), + default_init: Some(expr.clone()), + }); + } } } if object.properties().is_empty() { @@ -286,11 +359,26 @@ pub(crate) fn array_decl_to_declaration_pattern( get_field: get_field.clone(), }); } - AssignTarget::DeclarationPattern(pattern) => { - bindings.push(BindingPatternTypeArray::BindingPattern { - pattern: pattern.clone(), - }); - } + AssignTarget::DeclarationPattern(pattern) => match pattern { + DeclarationPattern::Object(pattern) => { + let mut pattern = pattern.clone(); + if pattern.init.is_none() { + pattern.init = Some(assign.rhs().clone()); + } + bindings.push(BindingPatternTypeArray::BindingPattern { + pattern: DeclarationPattern::Object(pattern), + }); + } + DeclarationPattern::Array(pattern) => { + let mut pattern = pattern.clone(); + if pattern.init.is_none() { + pattern.init = Some(assign.rhs().clone()); + } + bindings.push(BindingPatternTypeArray::BindingPattern { + pattern: DeclarationPattern::Array(pattern), + }); + } + }, AssignTarget::GetPrivateField(_) => return None, }, Node::ArrayDecl(array) => { diff --git a/boa_engine/src/syntax/ast/node/parameters.rs b/boa_engine/src/syntax/ast/node/parameters.rs index 7ca6002a88..90de723f20 100644 --- a/boa_engine/src/syntax/ast/node/parameters.rs +++ b/boa_engine/src/syntax/ast/node/parameters.rs @@ -1,8 +1,13 @@ -use crate::syntax::{ast::Position, parser::ParseError}; - -use super::{Declaration, DeclarationPattern, Node}; +use crate::syntax::{ + ast::{ + node::{ContainsSymbol, Declaration, DeclarationPattern, Node}, + Position, + }, + parser::ParseError, +}; use bitflags::bitflags; use boa_interner::{Interner, Sym, ToInternedString}; +use rustc_hash::FxHashSet; #[cfg(feature = "deser")] use serde::{Deserialize, Serialize}; @@ -96,6 +101,65 @@ impl FormalParameterList { } Ok(()) } + + /// Check if the any of the parameters contains a yield expression. + pub(crate) fn contains_yield_expression(&self) -> bool { + for parameter in self.parameters.iter() { + if parameter + .declaration() + .contains(ContainsSymbol::YieldExpression) + { + return true; + } + } + false + } +} + +impl From> for FormalParameterList { + fn from(parameters: Vec) -> Self { + let mut flags = FormalParameterListFlags::default(); + let mut length = 0; + let mut names = FxHashSet::default(); + + for parameter in ¶meters { + let parameter_names = parameter.names(); + + for name in parameter_names { + if name == Sym::ARGUMENTS { + flags |= FormalParameterListFlags::HAS_ARGUMENTS; + } + if names.contains(&name) { + flags |= FormalParameterListFlags::HAS_DUPLICATES; + } else { + names.insert(name); + } + } + + if parameter.is_rest_param() { + flags |= FormalParameterListFlags::HAS_REST_PARAMETER; + } + if parameter.init().is_some() { + flags |= FormalParameterListFlags::HAS_EXPRESSIONS; + } + if parameter.is_rest_param() || parameter.init().is_some() || !parameter.is_identifier() + { + flags.remove(FormalParameterListFlags::IS_SIMPLE); + } + if !(flags.contains(FormalParameterListFlags::HAS_EXPRESSIONS) + || parameter.is_rest_param() + || parameter.init().is_some()) + { + length += 1; + } + } + + Self { + parameters: parameters.into_boxed_slice(), + flags, + length, + } + } } impl From for FormalParameterList { diff --git a/boa_engine/src/syntax/ast/punctuator.rs b/boa_engine/src/syntax/ast/punctuator.rs index 8cb334b7f7..4723d22aff 100644 --- a/boa_engine/src/syntax/ast/punctuator.rs +++ b/boa_engine/src/syntax/ast/punctuator.rs @@ -141,7 +141,7 @@ impl Punctuator { /// Attempts to convert a punctuator (`+`, `=`...) to a Binary Operator /// /// If there is no match, `None` will be returned. - pub fn as_binop(self) -> Option { + pub const fn as_binop(self) -> Option { match self { Self::AssignAdd => Some(BinOp::Assign(AssignOp::Add)), Self::AssignAnd => Some(BinOp::Assign(AssignOp::And)), @@ -186,7 +186,7 @@ impl Punctuator { } /// Retrieves the punctuator as a static string. - pub fn as_str(self) -> &'static str { + pub const fn as_str(self) -> &'static str { match self { Self::Add => "+", Self::And => "&", @@ -261,3 +261,9 @@ impl Display for Punctuator { write!(f, "{}", self.as_str()) } } + +impl From for Box { + fn from(p: Punctuator) -> Self { + p.as_str().into() + } +} diff --git a/boa_engine/src/syntax/parser/cursor/mod.rs b/boa_engine/src/syntax/parser/cursor/mod.rs index c3edc5db44..68c1b02358 100644 --- a/boa_engine/src/syntax/parser/cursor/mod.rs +++ b/boa_engine/src/syntax/parser/cursor/mod.rs @@ -261,6 +261,20 @@ where } } + /// Check if the peeked token is a line terminator. + #[inline] + pub(super) fn peek_expect_is_line_terminator( + &mut self, + skip_n: usize, + interner: &mut Interner, + ) -> Result { + if let Some(t) = self.buffered_lexer.peek(skip_n, false, interner)? { + Ok(t.kind() == &TokenKind::LineTerminator) + } else { + Err(ParseError::AbruptEnd) + } + } + /// Advance the cursor to the next token and retrieve it, only if it's of `kind` type. /// /// When the next token is a `kind` token, get the token, otherwise return `None`. diff --git a/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs b/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs index 36e62d0454..112ed1d22f 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs @@ -158,13 +158,13 @@ where /// #[derive(Debug, Clone, Copy)] -struct ConciseBody { +pub(in crate::syntax::parser) struct ConciseBody { allow_in: AllowIn, } impl ConciseBody { /// Creates a new `ConciseBody` parser. - fn new(allow_in: I) -> Self + pub(in crate::syntax::parser) fn new(allow_in: I) -> Self where I: Into, { diff --git a/boa_engine/src/syntax/parser/expression/assignment/mod.rs b/boa_engine/src/syntax/parser/expression/assignment/mod.rs index 5511ec705a..46bde867c7 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/mod.rs +++ b/boa_engine/src/syntax/parser/expression/assignment/mod.rs @@ -14,13 +14,14 @@ mod r#yield; use crate::syntax::{ ast::{ - node::{operator::assign::AssignTarget, Assign, BinOp, Node}, + node::{operator::assign::AssignTarget, ArrowFunctionDecl, Assign, BinOp, Node}, Keyword, Punctuator, }, lexer::{Error as LexError, InputElement, TokenKind}, parser::{ expression::assignment::{ - arrow_function::ArrowFunction, conditional::ConditionalExpression, + arrow_function::{ArrowFunction, ConciseBody}, + conditional::ConditionalExpression, r#yield::YieldExpression, }, AllowAwait, AllowIn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, @@ -103,8 +104,15 @@ where } // ArrowFunction[?In, ?Yield, ?Await] -> ArrowParameters[?Yield, ?Await] -> BindingIdentifier[?Yield, ?Await] TokenKind::Identifier(_) | TokenKind::Keyword((Keyword::Yield | Keyword::Await, _)) => { + // Because we already peeked the identifier token, there may be a line terminator before the identifier token. + // In that case we have to skip an additional token on the next peek. + let skip_n = if cursor.peek_expect_is_line_terminator(0, interner)? { + 2 + } else { + 1 + }; if let Ok(tok) = - cursor.peek_expect_no_lineterminator(1, "assignment expression", interner) + cursor.peek_expect_no_lineterminator(skip_n, "assignment expression", interner) { if tok.kind() == &TokenKind::Punctuator(Punctuator::Arrow) { return ArrowFunction::new( @@ -118,77 +126,6 @@ where } } } - // ArrowFunction[?In, ?Yield, ?Await] -> ArrowParameters[?Yield, ?Await] -> CoverParenthesizedExpressionAndArrowParameterList[?Yield, ?Await] - TokenKind::Punctuator(Punctuator::OpenParen) => { - if let Some(next_token) = cursor.peek(1, interner)? { - match *next_token.kind() { - TokenKind::Punctuator(Punctuator::CloseParen) => { - // Need to check if the token after the close paren is an arrow, if so then this is an ArrowFunction - // otherwise it is an expression of the form (b). - if let Some(t) = cursor.peek(2, interner)? { - if t.kind() == &TokenKind::Punctuator(Punctuator::Arrow) { - return ArrowFunction::new( - self.name, - self.allow_in, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner) - .map(Node::ArrowFunctionDecl); - } - } - } - TokenKind::Punctuator(Punctuator::Spread) => { - return ArrowFunction::new( - None, - self.allow_in, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner) - .map(Node::ArrowFunctionDecl); - } - TokenKind::Identifier(_) => { - if let Some(t) = cursor.peek(2, interner)? { - match *t.kind() { - TokenKind::Punctuator(Punctuator::Comma) => { - // This must be an argument list and therefore (a, b) => {} - return ArrowFunction::new( - self.name, - self.allow_in, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner) - .map(Node::ArrowFunctionDecl); - } - TokenKind::Punctuator(Punctuator::CloseParen) => { - // Need to check if the token after the close paren is an - // arrow, if so then this is an ArrowFunction otherwise it - // is an expression of the form (b). - if let Some(t) = cursor.peek(3, interner)? { - if t.kind() == &TokenKind::Punctuator(Punctuator::Arrow) - { - return ArrowFunction::new( - self.name, - self.allow_in, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner) - .map(Node::ArrowFunctionDecl); - } - } - } - _ => {} - } - } - } - _ => {} - } - } - } - _ => {} } @@ -207,6 +144,49 @@ where ) .parse(cursor, interner)?; + // If the left hand side is a parameter list, we must parse an arrow function. + if let Node::FormalParameterList(parameters) = lhs { + cursor.peek_expect_no_lineterminator(0, "arrow function", interner)?; + + cursor.expect( + TokenKind::Punctuator(Punctuator::Arrow), + "arrow function", + interner, + )?; + let arrow = cursor.arrow(); + cursor.set_arrow(true); + let body = ConciseBody::new(self.allow_in).parse(cursor, interner)?; + cursor.set_arrow(arrow); + + // Early Error: ArrowFormalParameters are UniqueFormalParameters. + if parameters.has_duplicates() { + return Err(ParseError::lex(LexError::Syntax( + "Duplicate parameter name not allowed in this context".into(), + position, + ))); + } + + // Early Error: It is a Syntax Error if ConciseBodyContainsUseStrict of ConciseBody is true + // and IsSimpleParameterList of ArrowParameters 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(), + position, + ))); + } + + // It is a Syntax Error if any element of the BoundNames of ArrowParameters + // also occurs in the LexicallyDeclaredNames of ConciseBody. + // https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors + parameters.name_in_lexically_declared_names( + &body.lexically_declared_names_top_level(), + position, + )?; + + return Ok(ArrowFunctionDecl::new(self.name, parameters, body).into()); + } + // Review if we are trying to assign to an invalid left hand side expression. // TODO: can we avoid cloning? if let Some(tok) = cursor.peek(0, interner)?.cloned() { diff --git a/boa_engine/src/syntax/parser/expression/mod.rs b/boa_engine/src/syntax/parser/expression/mod.rs index 7df5de551d..406da35ec1 100644 --- a/boa_engine/src/syntax/parser/expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/mod.rs @@ -155,13 +155,64 @@ impl Expression { } } -expression!( - Expression, - AssignmentExpression, - [Punctuator::Comma], - [name, allow_in, allow_yield, allow_await], - None:: -); +impl TokenParser for Expression +where + R: Read, +{ + type Output = Node; + + fn parse(mut self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + let _timer = Profiler::global().start_event("Expression", "Parsing"); + + let mut lhs = + AssignmentExpression::new(self.name, self.allow_in, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + self.name = None; + while let Some(tok) = cursor.peek(0, interner)? { + match *tok.kind() { + TokenKind::Punctuator(Punctuator::Comma) => { + if cursor + .peek(1, interner)? + .ok_or(ParseError::AbruptEnd)? + .kind() + == &TokenKind::Punctuator(Punctuator::CloseParen) + { + return Ok(lhs); + } + + if cursor + .peek(1, interner)? + .ok_or(ParseError::AbruptEnd)? + .kind() + == &TokenKind::Punctuator(Punctuator::Spread) + { + return Ok(lhs); + } + + let _next = cursor.next(interner).expect("token disappeared"); + + lhs = BinOp::new( + Punctuator::Comma + .as_binop() + .expect("Could not get binary operation."), + lhs, + AssignmentExpression::new( + self.name, + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor, interner)?, + ) + .into(); + } + _ => break, + } + } + + Ok(lhs) + } +} /// Parses a logical expression expression. /// diff --git a/boa_engine/src/syntax/parser/expression/primary/mod.rs b/boa_engine/src/syntax/parser/expression/primary/mod.rs index eea2ecd247..3d1366dc34 100644 --- a/boa_engine/src/syntax/parser/expression/primary/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/mod.rs @@ -28,14 +28,24 @@ use self::{ }; use crate::syntax::{ ast::{ - node::{Call, Identifier, New, Node}, - Const, Keyword, Punctuator, + node::{ + declaration::{BindingPatternTypeArray, BindingPatternTypeObject}, + operator::assign::{ + array_decl_to_declaration_pattern, object_decl_to_declaration_pattern, AssignTarget, + }, + Call, Declaration, DeclarationPattern, FormalParameter, FormalParameterList, + Identifier, New, Node, + }, + op::BinOp, + Const, Keyword, Punctuator, Span, }, - lexer::{token::Numeric, InputElement, TokenKind}, + lexer::{token::Numeric, InputElement, Token, TokenKind}, parser::{ expression::{ - identifiers::IdentifierReference, primary::template::TemplateLiteral, Expression, + identifiers::IdentifierReference, primary::template::TemplateLiteral, + BindingIdentifier, Expression, }, + statement::{ArrayBindingPattern, ObjectBindingPattern}, AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser, }, }; @@ -132,9 +142,12 @@ where TokenKind::Punctuator(Punctuator::OpenParen) => { cursor.next(interner).expect("token disappeared"); cursor.set_goal(InputElement::RegExp); - let expr = Expression::new(self.name, true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; - cursor.expect(Punctuator::CloseParen, "primary expression", interner)?; + let expr = CoverParenthesizedExpressionAndArrowParameterList::new( + self.name, + self.allow_yield, + self.allow_await, + ) + .parse(cursor, interner)?; Ok(expr) } TokenKind::Punctuator(Punctuator::OpenBracket) => { @@ -243,3 +256,304 @@ where } } } + +/// Parses a `CoverParenthesizedExpressionAndArrowParameterList` expression. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-CoverParenthesizedExpressionAndArrowParameterList +#[derive(Debug, Clone, Copy)] +pub(super) struct CoverParenthesizedExpressionAndArrowParameterList { + name: Option, + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl CoverParenthesizedExpressionAndArrowParameterList { + /// Creates a new `CoverParenthesizedExpressionAndArrowParameterList` parser. + pub(super) fn new(name: N, allow_yield: Y, allow_await: A) -> Self + where + N: Into>, + Y: Into, + A: Into, + { + Self { + name: name.into(), + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for CoverParenthesizedExpressionAndArrowParameterList +where + R: Read, +{ + type Output = Node; + + fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { + #[derive(Debug)] + enum InnerExpression { + Expression(Node), + SpreadObject(Vec), + SpreadArray(Vec), + SpreadBinding(Sym), + } + + let _timer = Profiler::global().start_event( + "CoverParenthesizedExpressionAndArrowParameterList", + "Parsing", + ); + + let start_span = cursor + .peek(0, interner)? + .ok_or(ParseError::AbruptEnd)? + .span(); + + let mut expressions = Vec::new(); + let mut tailing_comma = None; + + let close_span = loop { + let next = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + match next.kind() { + TokenKind::Punctuator(Punctuator::CloseParen) => { + let span = next.span(); + cursor.next(interner).expect("token disappeared"); + break span; + } + TokenKind::Punctuator(Punctuator::Comma) => { + let span = next.span(); + cursor.next(interner).expect("token disappeared"); + if let Some(token) = cursor.next_if(Punctuator::CloseParen, interner)? { + tailing_comma = Some(span); + break token.span(); + } + } + TokenKind::Punctuator(Punctuator::Spread) => { + cursor.next(interner).expect("token disappeared"); + let next = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?; + match next.kind() { + TokenKind::Punctuator(Punctuator::OpenBlock) => { + let bindings = + ObjectBindingPattern::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + expressions.push(InnerExpression::SpreadObject(bindings)); + } + TokenKind::Punctuator(Punctuator::OpenBracket) => { + let bindings = + ArrayBindingPattern::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + expressions.push(InnerExpression::SpreadArray(bindings)); + } + _ => { + let binding = + BindingIdentifier::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + expressions.push(InnerExpression::SpreadBinding(binding)); + } + } + } + _ => { + let expression = + Expression::new(self.name, true, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + expressions.push(InnerExpression::Expression(expression)); + } + } + }; + + let is_arrow = if let Some(TokenKind::Punctuator(Punctuator::Arrow)) = + cursor.peek(0, interner)?.map(Token::kind) + { + !cursor.peek_expect_is_line_terminator(0, interner)? + } else { + false + }; + + // If the next token is not an arrow, we know that we must parse a parenthesized expression. + if !is_arrow { + if let Some(span) = tailing_comma { + return Err(ParseError::unexpected( + Punctuator::Comma, + span, + "trailing comma in parenthesized expression", + )); + } + if expressions.is_empty() { + return Err(ParseError::unexpected( + Punctuator::CloseParen, + close_span, + "empty parenthesized expression", + )); + } + if expressions.len() != 1 { + return Err(ParseError::unexpected( + Punctuator::CloseParen, + close_span, + "multiple expressions in parenthesized expression", + )); + } + if let InnerExpression::Expression(expression) = &expressions[0] { + return Ok(expression.clone()); + } + return Err(ParseError::unexpected( + Punctuator::CloseParen, + close_span, + "parenthesized expression with spread expressions", + )); + } + + // We know that we must parse an arrow function. + // We parse the expressions in to a parameter list. + + let mut parameters = Vec::new(); + + for expression in expressions { + match expression { + InnerExpression::Expression(node) => { + node_to_formal_parameters( + &node, + &mut parameters, + cursor.strict_mode(), + start_span, + )?; + } + InnerExpression::SpreadObject(bindings) => { + let declaration = Declaration::new_with_object_pattern(bindings, None); + let parameter = FormalParameter::new(declaration, true); + parameters.push(parameter); + } + InnerExpression::SpreadArray(bindings) => { + let declaration = Declaration::new_with_array_pattern(bindings, None); + let parameter = FormalParameter::new(declaration, true); + parameters.push(parameter); + } + InnerExpression::SpreadBinding(ident) => { + let declaration = Declaration::new_with_identifier(ident, None); + let parameter = FormalParameter::new(declaration, true); + parameters.push(parameter); + } + } + } + + let parameters = FormalParameterList::from(parameters); + + if let Some(span) = tailing_comma { + if parameters.has_rest_parameter() { + return Err(ParseError::general( + "rest parameter must be last formal parameter", + span.start(), + )); + } + } + + if parameters.contains_yield_expression() { + return Err(ParseError::general( + "yield expression is not allowed in formal parameter list of arrow function", + start_span.start(), + )); + } + + Ok(Node::FormalParameterList(parameters)) + } +} + +/// Convert a node to a formal parameter and append it to the given parameter list. +fn node_to_formal_parameters( + node: &Node, + parameters: &mut Vec, + strict: bool, + span: Span, +) -> Result<(), ParseError> { + match node { + Node::Identifier(identifier) if strict && identifier.sym() == Sym::EVAL => { + return Err(ParseError::general( + "parameter name 'eval' not allowed in strict mode", + span.start(), + )); + } + Node::Identifier(identifier) if strict && identifier.sym() == Sym::ARGUMENTS => { + return Err(ParseError::general( + "parameter name 'arguments' not allowed in strict mode", + span.start(), + )); + } + Node::Identifier(identifier) => { + parameters.push(FormalParameter::new( + Declaration::new_with_identifier(identifier.sym(), None), + false, + )); + } + Node::BinOp(bin_op) if bin_op.op() == BinOp::Comma => { + node_to_formal_parameters(bin_op.lhs(), parameters, strict, span)?; + node_to_formal_parameters(bin_op.rhs(), parameters, strict, span)?; + } + Node::Assign(assign) => match assign.lhs() { + AssignTarget::Identifier(ident) => { + parameters.push(FormalParameter::new( + Declaration::new_with_identifier(ident.sym(), Some(assign.rhs().clone())), + false, + )); + } + AssignTarget::DeclarationPattern(pattern) => match pattern { + DeclarationPattern::Object(pattern) => { + parameters.push(FormalParameter::new( + Declaration::new_with_object_pattern( + pattern.bindings().clone(), + Some(assign.rhs().clone()), + ), + false, + )); + } + DeclarationPattern::Array(pattern) => { + parameters.push(FormalParameter::new( + Declaration::new_with_array_pattern( + pattern.bindings().clone(), + Some(assign.rhs().clone()), + ), + false, + )); + } + }, + _ => { + return Err(ParseError::general( + "invalid initialization expression in formal parameter list", + span.start(), + )); + } + }, + Node::Object(object) => { + let decl = object_decl_to_declaration_pattern(object, strict); + + if let Some(pattern) = decl { + parameters.push(FormalParameter::new(Declaration::Pattern(pattern), false)); + } else { + return Err(ParseError::general( + "invalid object binding pattern in formal parameter list", + span.start(), + )); + } + } + Node::ArrayDecl(array) => { + let decl = array_decl_to_declaration_pattern(array, strict); + + if let Some(pattern) = decl { + parameters.push(FormalParameter::new(Declaration::Pattern(pattern), false)); + } else { + return Err(ParseError::general( + "invalid array binding pattern in formal parameter list", + span.start(), + )); + } + } + _ => { + return Err(ParseError::unexpected( + ")".to_string(), + span, + "parenthesized expression with non-binding expression", + )); + } + } + Ok(()) +} diff --git a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs index 2008e4a174..e5811dba85 100644 --- a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -141,16 +141,21 @@ where ) -> Result { let _timer = Profiler::global().start_event("PropertyDefinition", "Parsing"); - // IdentifierReference[?Yield, ?Await] - if let Some(next_token) = cursor.peek(1, interner)? { - if matches!( - next_token.kind(), - TokenKind::Punctuator(Punctuator::CloseBlock | Punctuator::Comma) - ) { + match cursor + .peek(1, interner)? + .ok_or(ParseError::AbruptEnd)? + .kind() + { + TokenKind::Punctuator(Punctuator::CloseBlock | Punctuator::Comma) => { let ident = IdentifierReference::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; return Ok(object::PropertyDefinition::property(ident.sym(), ident)); } + TokenKind::Punctuator(Punctuator::Assign) => { + return CoverInitializedName::new(self.allow_yield, self.allow_await) + .parse(cursor, interner); + } + _ => {} } // ... AssignmentExpression[+In, ?Yield, ?Await] @@ -898,3 +903,57 @@ where )) } } + +/// `CoverInitializedName` parsing. +/// +/// More information: +/// - [ECMAScript specification][spec] +/// +/// [spec]: https://tc39.es/ecma262/#prod-CoverInitializedName +#[derive(Debug, Clone, Copy)] +pub(in crate::syntax::parser) struct CoverInitializedName { + allow_yield: AllowYield, + allow_await: AllowAwait, +} + +impl CoverInitializedName { + /// Creates a new `CoverInitializedName` parser. + pub(in crate::syntax::parser) fn new(allow_yield: Y, allow_await: A) -> Self + where + Y: Into, + A: Into, + { + Self { + allow_yield: allow_yield.into(), + allow_await: allow_await.into(), + } + } +} + +impl TokenParser for CoverInitializedName +where + R: Read, +{ + type Output = object::PropertyDefinition; + + fn parse( + self, + cursor: &mut Cursor, + interner: &mut Interner, + ) -> Result { + let _timer = Profiler::global().start_event("CoverInitializedName", "Parsing"); + + let ident = + IdentifierReference::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; + + cursor.expect(Punctuator::Assign, "CoverInitializedName", interner)?; + + let expr = AssignmentExpression::new(ident.sym(), true, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; + + Ok(object::PropertyDefinition::CoverInitializedName( + ident.sym(), + expr, + )) + } +} diff --git a/boa_engine/src/syntax/parser/function/mod.rs b/boa_engine/src/syntax/parser/function/mod.rs index 5d368d4a19..45cb1b33ec 100644 --- a/boa_engine/src/syntax/parser/function/mod.rs +++ b/boa_engine/src/syntax/parser/function/mod.rs @@ -99,7 +99,10 @@ where _ => { let param = FormalParameter::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; - if param.init().is_none() { + if !(flags.contains(FormalParameterListFlags::HAS_EXPRESSIONS) + || param.is_rest_param() + || param.init().is_some()) + { length += 1; } param @@ -300,7 +303,7 @@ where if let Some(t) = cursor.peek(0, interner)? { let declaration = match *t.kind() { TokenKind::Punctuator(Punctuator::OpenBlock) => { - let param = ObjectBindingPattern::new(true, self.allow_yield, self.allow_await) + let param = ObjectBindingPattern::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; let init = cursor @@ -320,7 +323,7 @@ where TokenKind::Punctuator(Punctuator::OpenBracket) => { Declaration::new_with_array_pattern( - ArrayBindingPattern::new(true, self.allow_yield, self.allow_await) + ArrayBindingPattern::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?, None, ) @@ -399,9 +402,8 @@ where if let Some(t) = cursor.peek(0, interner)? { let declaration = match *t.kind() { TokenKind::Punctuator(Punctuator::OpenBlock) => { - let bindings = - ObjectBindingPattern::new(true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + let bindings = ObjectBindingPattern::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; let init = if *cursor .peek(0, interner)? .ok_or(ParseError::AbruptEnd)? @@ -419,9 +421,8 @@ where Declaration::new_with_object_pattern(bindings, init) } TokenKind::Punctuator(Punctuator::OpenBracket) => { - let bindings = - ArrayBindingPattern::new(true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + let bindings = ArrayBindingPattern::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; let init = if *cursor .peek(0, interner)? .ok_or(ParseError::AbruptEnd)? diff --git a/boa_engine/src/syntax/parser/statement/declaration/lexical.rs b/boa_engine/src/syntax/parser/statement/declaration/lexical.rs index 1f67ee2ebd..4060965657 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/lexical.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/lexical.rs @@ -274,9 +274,8 @@ where match peek_token.kind() { TokenKind::Punctuator(Punctuator::OpenBlock) => { - let bindings = - ObjectBindingPattern::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + let bindings = ObjectBindingPattern::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; let init = if let Some(t) = cursor.peek(0, interner)? { if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { @@ -309,9 +308,8 @@ where Ok(Declaration::Pattern(declaration)) } TokenKind::Punctuator(Punctuator::OpenBracket) => { - let bindings = - ArrayBindingPattern::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + let bindings = ArrayBindingPattern::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; let init = if let Some(t) = cursor.peek(0, interner)? { if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { diff --git a/boa_engine/src/syntax/parser/statement/mod.rs b/boa_engine/src/syntax/parser/statement/mod.rs index 49fe736ec1..c29cfd3fd8 100644 --- a/boa_engine/src/syntax/parser/statement/mod.rs +++ b/boa_engine/src/syntax/parser/statement/mod.rs @@ -37,8 +37,7 @@ use self::{ variable::VariableStatement, }; use super::{ - expression::PropertyName, AllowAwait, AllowIn, AllowReturn, AllowYield, Cursor, ParseError, - TokenParser, + expression::PropertyName, AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser, }; use crate::syntax::{ ast::{ @@ -379,21 +378,18 @@ where /// [spec]: https://tc39.es/ecma262/#prod-ObjectBindingPattern #[derive(Debug, Clone, Copy)] pub(super) struct ObjectBindingPattern { - allow_in: AllowIn, allow_yield: AllowYield, allow_await: AllowAwait, } impl ObjectBindingPattern { /// Creates a new `ObjectBindingPattern` parser. - pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self where - I: Into, Y: Into, A: Into, { Self { - allow_in: allow_in.into(), allow_yield: allow_yield.into(), allow_await: allow_await.into(), } @@ -480,19 +476,15 @@ where if let Some(peek_token) = cursor.peek(0, interner)? { match peek_token.kind() { TokenKind::Punctuator(Punctuator::OpenBlock) => { - let bindings = Self::new( - self.allow_in, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner)?; + let bindings = Self::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; if let Some(peek_token) = cursor.peek(0, interner)? { match peek_token.kind() { TokenKind::Punctuator(Punctuator::Assign) => { let init = Initializer::new( None, - self.allow_in, + true, self.allow_yield, self.allow_await, ) @@ -527,7 +519,6 @@ where } TokenKind::Punctuator(Punctuator::OpenBracket) => { let bindings = ArrayBindingPattern::new( - self.allow_in, self.allow_yield, self.allow_await, ) @@ -538,7 +529,7 @@ where TokenKind::Punctuator(Punctuator::Assign) => { let init = Initializer::new( None, - self.allow_in, + true, self.allow_yield, self.allow_await, ) @@ -583,7 +574,7 @@ where TokenKind::Punctuator(Punctuator::Assign) => { let init = Initializer::new( None, - self.allow_in, + true, self.allow_yield, self.allow_await, ) @@ -618,7 +609,7 @@ where Some(TokenKind::Punctuator(Punctuator::Assign)) => { let init = Initializer::new( Some(name), - self.allow_in, + true, self.allow_yield, self.allow_await, ) @@ -681,21 +672,18 @@ where /// [spec]: https://tc39.es/ecma262/#prod-ArrayBindingPattern #[derive(Debug, Clone, Copy)] pub(super) struct ArrayBindingPattern { - allow_in: AllowIn, allow_yield: AllowYield, allow_await: AllowAwait, } impl ArrayBindingPattern { /// Creates a new `ArrayBindingPattern` parser. - pub(super) fn new(allow_in: I, allow_yield: Y, allow_await: A) -> Self + pub(super) fn new(allow_yield: Y, allow_await: A) -> Self where - I: Into, Y: Into, A: Into, { Self { - allow_in: allow_in.into(), allow_yield: allow_yield.into(), allow_await: allow_await.into(), } @@ -764,12 +752,9 @@ where .kind() { TokenKind::Punctuator(Punctuator::OpenBlock) => { - let bindings = ObjectBindingPattern::new( - self.allow_in, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner)?; + let bindings = + ObjectBindingPattern::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; patterns.push(BindingPatternTypeArray::BindingPatternRest { pattern: DeclarationPattern::Object(DeclarationPatternObject::new( bindings, None, @@ -777,9 +762,8 @@ where }); } TokenKind::Punctuator(Punctuator::OpenBracket) => { - let bindings = - Self::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + let bindings = Self::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; patterns.push(BindingPatternTypeArray::BindingPatternRest { pattern: DeclarationPattern::Array(DeclarationPatternArray::new( bindings, None, @@ -806,12 +790,8 @@ where TokenKind::Punctuator(Punctuator::OpenBlock) => { last_elision_or_first = false; - let bindings = ObjectBindingPattern::new( - self.allow_in, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner)?; + let bindings = ObjectBindingPattern::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; match cursor .peek(0, interner)? @@ -819,13 +799,9 @@ where .kind() { TokenKind::Punctuator(Punctuator::Assign) => { - let default_init = Initializer::new( - None, - self.allow_in, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner)?; + let default_init = + Initializer::new(None, true, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; patterns.push(BindingPatternTypeArray::BindingPattern { pattern: DeclarationPattern::Object(DeclarationPatternObject::new( bindings, @@ -845,8 +821,8 @@ where TokenKind::Punctuator(Punctuator::OpenBracket) => { last_elision_or_first = false; - let bindings = Self::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + let bindings = + Self::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; match cursor .peek(0, interner)? @@ -854,13 +830,9 @@ where .kind() { TokenKind::Punctuator(Punctuator::Assign) => { - let default_init = Initializer::new( - None, - self.allow_in, - self.allow_yield, - self.allow_await, - ) - .parse(cursor, interner)?; + let default_init = + Initializer::new(None, true, self.allow_yield, self.allow_await) + .parse(cursor, interner)?; patterns.push(BindingPatternTypeArray::BindingPattern { pattern: DeclarationPattern::Array(DeclarationPatternArray::new( bindings, @@ -890,7 +862,7 @@ where TokenKind::Punctuator(Punctuator::Assign) => { let default_init = Initializer::new( Some(ident), - self.allow_in, + true, self.allow_yield, self.allow_await, ) diff --git a/boa_engine/src/syntax/parser/statement/try_stm/catch.rs b/boa_engine/src/syntax/parser/statement/try_stm/catch.rs index 8e576a8f1a..121fd42990 100644 --- a/boa_engine/src/syntax/parser/statement/try_stm/catch.rs +++ b/boa_engine/src/syntax/parser/statement/try_stm/catch.rs @@ -192,13 +192,13 @@ where match token.kind() { TokenKind::Punctuator(Punctuator::OpenBlock) => { - let pat = ObjectBindingPattern::new(true, self.allow_yield, self.allow_await) + let pat = ObjectBindingPattern::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; Ok(node::Declaration::new_with_object_pattern(pat, None)) } TokenKind::Punctuator(Punctuator::OpenBracket) => { - let pat = ArrayBindingPattern::new(true, self.allow_yield, self.allow_await) + let pat = ArrayBindingPattern::new(self.allow_yield, self.allow_await) .parse(cursor, interner)?; Ok(node::Declaration::new_with_array_pattern(pat, None)) } diff --git a/boa_engine/src/syntax/parser/statement/variable/mod.rs b/boa_engine/src/syntax/parser/statement/variable/mod.rs index a0179e9a41..3f8b72f4ff 100644 --- a/boa_engine/src/syntax/parser/statement/variable/mod.rs +++ b/boa_engine/src/syntax/parser/statement/variable/mod.rs @@ -183,9 +183,8 @@ where match peek_token.kind() { TokenKind::Punctuator(Punctuator::OpenBlock) => { - let bindings = - ObjectBindingPattern::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + let bindings = ObjectBindingPattern::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; let init = if let Some(t) = cursor.peek(0, interner)? { if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { @@ -208,9 +207,8 @@ where Ok(Declaration::new_with_object_pattern(bindings, init)) } TokenKind::Punctuator(Punctuator::OpenBracket) => { - let bindings = - ArrayBindingPattern::new(self.allow_in, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; + let bindings = ArrayBindingPattern::new(self.allow_yield, self.allow_await) + .parse(cursor, interner)?; let init = if let Some(t) = cursor.peek(0, interner)? { if *t.kind() == TokenKind::Punctuator(Punctuator::Assign) { diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index ca2f499940..4317bd6baf 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -101,6 +101,12 @@ pub struct CodeBlock { /// The `[[IsClassConstructor]]` internal slot. pub(crate) is_class_constructor: bool, + + /// Marks the location in the code where the function environment in pushed. + /// This is only relevant for functions with expressions in the parameters. + /// We execute the parameter expressions in the function code and push the function environment afterward. + /// When the execution of the parameter expressions throws an error, we do not need to pop the function environment. + pub(crate) function_environment_push_location: u32, } impl CodeBlock { @@ -121,6 +127,7 @@ impl CodeBlock { arguments_binding: None, compile_environments: Vec::new(), is_class_constructor: false, + function_environment_push_location: 0, } } @@ -741,10 +748,12 @@ impl JsObject { }); let result = context.run(); - context.vm.pop_frame().expect("must have frame"); + let frame = context.vm.pop_frame().expect("must have frame"); context.realm.environments.pop(); - if has_expressions { + if has_expressions + && frame.pc > frame.code.function_environment_push_location as usize + { context.realm.environments.pop(); } @@ -861,10 +870,12 @@ impl JsObject { }); let _result = context.run(); - context.vm.pop_frame().expect("must have frame"); + let frame = context.vm.pop_frame().expect("must have frame"); context.realm.environments.pop(); - if has_expressions { + if has_expressions + && frame.pc > frame.code.function_environment_push_location as usize + { context.realm.environments.pop(); } diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 6116cde728..e11a15c734 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -2388,7 +2388,7 @@ impl Context { let _timer = Profiler::global().start_event("run", "vm"); if self.vm.trace { - let msg = if self.vm.frames.get(self.vm.frames.len() - 2).is_some() { + let msg = if self.vm.frames.last().is_some() { " Call Frame " } else { " VM Start "