Browse Source

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.
pull/2174/head
raskad 2 years ago
parent
commit
d8af7b4ee5
  1. 9
      boa_engine/src/bytecompiler.rs
  2. 4
      boa_engine/src/syntax/ast/node/declaration/mod.rs
  3. 14
      boa_engine/src/syntax/ast/node/mod.rs
  4. 15
      boa_engine/src/syntax/ast/node/object/mod.rs
  5. 98
      boa_engine/src/syntax/ast/node/operator/assign/mod.rs
  6. 70
      boa_engine/src/syntax/ast/node/parameters.rs
  7. 10
      boa_engine/src/syntax/ast/punctuator.rs
  8. 14
      boa_engine/src/syntax/parser/cursor/mod.rs
  9. 4
      boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs
  10. 128
      boa_engine/src/syntax/parser/expression/assignment/mod.rs
  11. 65
      boa_engine/src/syntax/parser/expression/mod.rs
  12. 328
      boa_engine/src/syntax/parser/expression/primary/mod.rs
  13. 71
      boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs
  14. 19
      boa_engine/src/syntax/parser/function/mod.rs
  15. 10
      boa_engine/src/syntax/parser/statement/declaration/lexical.rs
  16. 78
      boa_engine/src/syntax/parser/statement/mod.rs
  17. 4
      boa_engine/src/syntax/parser/statement/try_stm/catch.rs
  18. 10
      boa_engine/src/syntax/parser/statement/variable/mod.rs
  19. 19
      boa_engine/src/vm/code_block.rs
  20. 2
      boa_engine/src/vm/mod.rs

9
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

4
boa_engine/src/syntax/ast/node/declaration/mod.rs

@ -514,7 +514,7 @@ impl DeclarationPattern {
#[derive(Clone, Debug, PartialEq)]
pub struct DeclarationPatternObject {
bindings: Vec<BindingPatternTypeObject>,
init: Option<Node>,
pub(crate) init: Option<Node>,
}
impl ToInternedString for DeclarationPatternObject {
@ -613,7 +613,7 @@ impl DeclarationPatternObject {
#[derive(Clone, Debug, PartialEq)]
pub struct DeclarationPatternArray {
bindings: Vec<BindingPatternTypeArray>,
init: Option<Node>,
pub(crate) init: Option<Node>,
}
impl ToInternedString for DeclarationPatternArray {

14
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<Const> 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 {

15
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 {

98
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) => {

70
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<Vec<FormalParameter>> for FormalParameterList {
fn from(parameters: Vec<FormalParameter>) -> Self {
let mut flags = FormalParameterListFlags::default();
let mut length = 0;
let mut names = FxHashSet::default();
for parameter in &parameters {
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<FormalParameter> for FormalParameterList {

10
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<BinOp> {
pub const fn as_binop(self) -> Option<BinOp> {
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<Punctuator> for Box<str> {
fn from(p: Punctuator) -> Self {
p.as_str().into()
}
}

14
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<bool, ParseError> {
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`.

4
boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs

@ -158,13 +158,13 @@ where
/// <https://tc39.es/ecma262/#prod-ConciseBody>
#[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<I>(allow_in: I) -> Self
pub(in crate::syntax::parser) fn new<I>(allow_in: I) -> Self
where
I: Into<AllowIn>,
{

128
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() {

65
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::<InputElement>
);
impl<R> TokenParser<R> for Expression
where
R: Read,
{
type Output = Node;
fn parse(mut self, cursor: &mut Cursor<R>, 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.
///

328
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<Sym>,
allow_yield: AllowYield,
allow_await: AllowAwait,
}
impl CoverParenthesizedExpressionAndArrowParameterList {
/// Creates a new `CoverParenthesizedExpressionAndArrowParameterList` parser.
pub(super) fn new<N, Y, A>(name: N, allow_yield: Y, allow_await: A) -> Self
where
N: Into<Option<Sym>>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
name: name.into(),
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl<R> TokenParser<R> for CoverParenthesizedExpressionAndArrowParameterList
where
R: Read,
{
type Output = Node;
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult {
#[derive(Debug)]
enum InnerExpression {
Expression(Node),
SpreadObject(Vec<BindingPatternTypeObject>),
SpreadArray(Vec<BindingPatternTypeArray>),
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<FormalParameter>,
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(())
}

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

@ -141,16 +141,21 @@ where
) -> Result<Self::Output, ParseError> {
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<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
Self {
allow_yield: allow_yield.into(),
allow_await: allow_await.into(),
}
}
}
impl<R> TokenParser<R> for CoverInitializedName
where
R: Read,
{
type Output = object::PropertyDefinition;
fn parse(
self,
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<Self::Output, ParseError> {
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,
))
}
}

19
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)?

10
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) {

78
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
pub(super) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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<I, Y, A>(allow_in: I, allow_yield: Y, allow_await: A) -> Self
pub(super) fn new<Y, A>(allow_yield: Y, allow_await: A) -> Self
where
I: Into<AllowIn>,
Y: Into<AllowYield>,
A: Into<AllowAwait>,
{
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,
)

4
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))
}

10
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) {

19
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();
}

2
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 "

Loading…
Cancel
Save