From b77b9f0f7b5c2a1f6616a4a2f005af985d3b43d4 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Tue, 18 Apr 2023 18:59:13 -0600 Subject: [PATCH 1/3] Implement var initializers in for-in loops --- boa_ast/src/statement/iteration/mod.rs | 18 +++---- boa_cli/Cargo.toml | 5 +- boa_engine/src/bytecompiler/statement/loop.rs | 19 ++++++-- boa_parser/src/error.rs | 14 ++++-- boa_parser/src/lexer/mod.rs | 2 +- boa_parser/src/parser/statement/if_stm/mod.rs | 9 ++-- .../statement/iteration/for_statement.rs | 48 ++++++++++++++----- .../src/parser/statement/labelled_stm/mod.rs | 19 +++++--- .../src/parser/statement/variable/mod.rs | 9 +++- boa_tester/Cargo.toml | 5 +- boa_wasm/Cargo.toml | 5 +- 11 files changed, 101 insertions(+), 52 deletions(-) diff --git a/boa_ast/src/statement/iteration/mod.rs b/boa_ast/src/statement/iteration/mod.rs index 4e52340fd52..a1177f49569 100644 --- a/boa_ast/src/statement/iteration/mod.rs +++ b/boa_ast/src/statement/iteration/mod.rs @@ -9,7 +9,7 @@ mod for_of_loop; mod while_loop; use crate::{ - declaration::Binding, + declaration::{Binding, Variable}, expression::{access::PropertyAccess, Identifier}, pattern::Pattern, }; @@ -43,7 +43,7 @@ pub enum IterableLoopInitializer { /// A property access. Access(PropertyAccess), /// A new var declaration. - Var(Binding), + Var(Variable), /// A new let declaration. Let(Binding), /// A new const declaration. @@ -58,12 +58,12 @@ impl ToInternedString for IterableLoopInitializer { Self::Identifier(ident) => return ident.to_interned_string(interner), Self::Pattern(pattern) => return pattern.to_interned_string(interner), Self::Access(access) => return access.to_interned_string(interner), - Self::Var(binding) => (binding, "var"), - Self::Let(binding) => (binding, "let"), - Self::Const(binding) => (binding, "const"), + Self::Var(binding) => (binding.to_interned_string(interner), "var"), + Self::Let(binding) => (binding.to_interned_string(interner), "let"), + Self::Const(binding) => (binding.to_interned_string(interner), "const"), }; - format!("{pre} {}", binding.to_interned_string(interner)) + format!("{pre} {binding}") } } @@ -75,7 +75,8 @@ impl VisitWith for IterableLoopInitializer { match self { Self::Identifier(id) => visitor.visit_identifier(id), Self::Access(pa) => visitor.visit_property_access(pa), - Self::Var(b) | Self::Let(b) | Self::Const(b) => visitor.visit_binding(b), + Self::Var(b) => visitor.visit_variable(b), + Self::Let(b) | Self::Const(b) => visitor.visit_binding(b), Self::Pattern(p) => visitor.visit_pattern(p), } } @@ -87,7 +88,8 @@ impl VisitWith for IterableLoopInitializer { match self { Self::Identifier(id) => visitor.visit_identifier_mut(id), Self::Access(pa) => visitor.visit_property_access_mut(pa), - Self::Var(b) | Self::Let(b) | Self::Const(b) => visitor.visit_binding_mut(b), + Self::Var(b) => visitor.visit_variable_mut(b), + Self::Let(b) | Self::Const(b) => visitor.visit_binding_mut(b), Self::Pattern(p) => visitor.visit_pattern_mut(p), } } diff --git a/boa_cli/Cargo.toml b/boa_cli/Cargo.toml index 08597fd6272..b52d5bf093a 100644 --- a/boa_cli/Cargo.toml +++ b/boa_cli/Cargo.toml @@ -12,7 +12,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] -boa_engine = { workspace = true, features = ["deser", "console", "flowgraph", "trace", "annex-b"] } +boa_engine = { workspace = true, features = ["deser", "flowgraph", "trace", "console"] } boa_ast = { workspace = true, features = ["serde"] } boa_parser.workspace = true boa_gc.workspace = true @@ -26,8 +26,7 @@ phf = { version = "0.11.1", features = ["macros"] } pollster = "0.3.0" [features] -default = ["intl"] -intl = ["boa_engine/intl"] +default = ["boa_engine/annex-b", "boa_engine/intl"] [target.x86_64-unknown-linux-gnu.dependencies] jemallocator = "0.5.0" diff --git a/boa_engine/src/bytecompiler/statement/loop.rs b/boa_engine/src/bytecompiler/statement/loop.rs index 421e9823949..fdc89d804eb 100644 --- a/boa_engine/src/bytecompiler/statement/loop.rs +++ b/boa_engine/src/bytecompiler/statement/loop.rs @@ -89,6 +89,16 @@ impl ByteCompiler<'_, '_> { label: Option, configurable_globals: bool, ) { + // Handle https://tc39.es/ecma262/#prod-annexB-ForInOfStatement + if let IterableLoopInitializer::Var(var) = for_in_loop.initializer() { + if let Binding::Identifier(ident) = var.binding() { + if let Some(init) = var.init() { + self.compile_expr(init, true); + self.create_mutable_binding(*ident, true, true); + self.emit_binding(BindingOpcode::InitVar, *ident); + } + } + } let initializer_bound_names = match for_in_loop.initializer() { IterableLoopInitializer::Let(declaration) | IterableLoopInitializer::Const(declaration) => bound_names(declaration), @@ -137,9 +147,7 @@ impl ByteCompiler<'_, '_> { match for_in_loop.initializer() { IterableLoopInitializer::Identifier(ident) => { self.create_mutable_binding(*ident, true, true); - let binding = self.set_mutable_binding(*ident); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DefInitVar, &[index]); + self.emit_binding(BindingOpcode::InitVar, *ident); } IterableLoopInitializer::Access(access) => { self.access_set( @@ -148,7 +156,7 @@ impl ByteCompiler<'_, '_> { ByteCompiler::access_set_top_of_stack_expr_fn, ); } - IterableLoopInitializer::Var(declaration) => match declaration { + IterableLoopInitializer::Var(declaration) => match declaration.binding() { Binding::Identifier(ident) => { self.create_mutable_binding(*ident, true, configurable_globals); self.emit_binding(BindingOpcode::InitVar, *ident); @@ -284,7 +292,8 @@ impl ByteCompiler<'_, '_> { ByteCompiler::access_set_top_of_stack_expr_fn, ); } - IterableLoopInitializer::Var(declaration) => match declaration { + // Can ignore initializers since those aren't allowed on for-of loops. + IterableLoopInitializer::Var(declaration) => match declaration.binding() { Binding::Identifier(ident) => { self.create_mutable_binding(*ident, true, false); self.emit_binding(BindingOpcode::InitVar, *ident); diff --git a/boa_parser/src/error.rs b/boa_parser/src/error.rs index 1179d8fcc43..3b17dcb4728 100644 --- a/boa_parser/src/error.rs +++ b/boa_parser/src/error.rs @@ -125,18 +125,22 @@ impl Error { } } - /// Creates a "general" parsing error with the specific error message for a wrong function declaration in non-strict mode. - pub(crate) fn wrong_function_declaration_non_strict(position: Position) -> Self { + /// Creates a "general" parsing error with the specific error message for a wrong function declaration in an if statement. + pub(crate) fn function_declaration_in_if(position: Position, strict: bool) -> Self { Self::General { - message: "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.".into(), - position + message: format!( + "{}functions can only be declared at top level or inside a block.", + if strict { "in strict mode code, " } else { "" } + ) + .into(), + position, } } /// Creates a "general" parsing error with the specific error message for a wrong function declaration with label. pub(crate) fn wrong_labelled_function_declaration(position: Position) -> Self { Self::General { - message: "Labelled functions can only be declared at top level or inside a block" + message: "labelled functions can only be declared at top level or inside a block" .into(), position, } diff --git a/boa_parser/src/lexer/mod.rs b/boa_parser/src/lexer/mod.rs index d72adcb29c7..ef67fafd19c 100644 --- a/boa_parser/src/lexer/mod.rs +++ b/boa_parser/src/lexer/mod.rs @@ -178,7 +178,7 @@ impl Lexer { where R: Read, { - if !cfg!(feature = "annex-b") || self.module() { + if cfg!(not(feature = "annex-b")) || self.module() { return Ok(()); } diff --git a/boa_parser/src/parser/statement/if_stm/mod.rs b/boa_parser/src/parser/statement/if_stm/mod.rs index ea2b22783cc..9dc181bf8fd 100644 --- a/boa_parser/src/parser/statement/if_stm/mod.rs +++ b/boa_parser/src/parser/statement/if_stm/mod.rs @@ -77,9 +77,8 @@ where TokenKind::Keyword((Keyword::Function, _)) => { // FunctionDeclarations in IfStatement Statement Clauses // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses - if strict { - // This production only applies when parsing non-strict code. - return Err(Error::wrong_function_declaration_non_strict(position)); + if cfg!(not(feature = "annex-b")) || strict { + return Err(Error::function_declaration_in_if(position, strict)); } // Source text matched by this production is processed as if each matching // occurrence of FunctionDeclaration[?Yield, ?Await, ~Default] was the sole @@ -117,8 +116,8 @@ where TokenKind::Keyword((Keyword::Function, _)) => { // FunctionDeclarations in IfStatement Statement Clauses // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses - if strict { - return Err(Error::wrong_function_declaration_non_strict(position)); + if cfg!(not(feature = "annex-b")) || strict { + return Err(Error::function_declaration_in_if(position, strict)); } // Source text matched by this production is processed as if each matching diff --git a/boa_parser/src/parser/statement/iteration/for_statement.rs b/boa_parser/src/parser/statement/iteration/for_statement.rs index 1bcc00d73d6..15802b682cf 100644 --- a/boa_parser/src/parser/statement/iteration/for_statement.rs +++ b/boa_parser/src/parser/statement/iteration/for_statement.rs @@ -17,7 +17,10 @@ use crate::{ }, Error, }; -use ast::operations::{bound_names, var_declared_names}; +use ast::{ + declaration::Binding, + operations::{bound_names, var_declared_names}, +}; use boa_ast::{ self as ast, statement::{ @@ -157,9 +160,13 @@ where )); } (Some(init), TokenKind::Keyword((kw @ (Keyword::In | Keyword::Of), false))) => { - let kw = *kw; - let init = - initializer_to_iterable_loop_initializer(init, position, cursor.strict())?; + let in_loop = kw == &Keyword::In; + let init = initializer_to_iterable_loop_initializer( + init, + position, + cursor.strict(), + in_loop, + )?; cursor.advance(interner); let expr = Expression::new(None, true, self.allow_yield, self.allow_await) @@ -208,7 +215,7 @@ where } } } - return Ok(if kw == Keyword::In { + return Ok(if in_loop { ForInLoop::new(init, expr, body).into() } else { ForOfLoop::new(init, expr, body, r#await).into() @@ -288,7 +295,9 @@ fn initializer_to_iterable_loop_initializer( initializer: ForLoopInitializer, position: Position, strict: bool, + in_loop: bool, ) -> ParseResult { + let loop_type = if in_loop { "for-in" } else { "for-of" }; match initializer { ForLoopInitializer::Expression(mut expr) => { while let ast::Expression::Parenthesized(p) = expr { @@ -338,7 +347,7 @@ fn initializer_to_iterable_loop_initializer( [declaration] => { if declaration.init().is_some() { return Err(Error::lex(LexError::Syntax( - "a declaration in the head of a for-of loop can't have an initializer" + format!("a lexical declaration in the head of a {loop_type} loop can't have an initializer") .into(), position, ))); @@ -353,25 +362,38 @@ fn initializer_to_iterable_loop_initializer( }) } _ => Err(Error::lex(LexError::Syntax( - "only one variable can be declared in the head of a for-of loop".into(), + format!("only one variable can be declared in the head of a {loop_type} loop") + .into(), position, ))), }, ForLoopInitializer::Var(decl) => match decl.0.as_ref() { [declaration] => { - // TODO: implement initializers in ForIn heads // https://tc39.es/ecma262/#sec-initializers-in-forin-statement-heads - if declaration.init().is_some() { + let is_pattern = matches!(declaration.binding(), Binding::Pattern(_)); + if declaration.init().is_some() + && (cfg!(not(feature = "annex-b")) || strict || !in_loop || is_pattern) + { return Err(Error::lex(LexError::Syntax( - "a declaration in the head of a for-of loop can't have an initializer" - .into(), + format!( + "{}a {} declaration in the head of a {loop_type} loop \ + cannot have an initializer", + if strict { "in strict mode, " } else { "" }, + if is_pattern { + "binding pattern" + } else { + "binding declaration" + } + ) + .into(), position, ))); } - Ok(IterableLoopInitializer::Var(declaration.binding().clone())) + Ok(IterableLoopInitializer::Var(declaration.clone())) } _ => Err(Error::lex(LexError::Syntax( - "only one variable can be declared in the head of a for-of loop".into(), + format!("only one variable can be declared in the head of a {loop_type} loop") + .into(), position, ))), }, diff --git a/boa_parser/src/parser/statement/labelled_stm/mod.rs b/boa_parser/src/parser/statement/labelled_stm/mod.rs index e3ef1f0d06a..9c024191fc3 100644 --- a/boa_parser/src/parser/statement/labelled_stm/mod.rs +++ b/boa_parser/src/parser/statement/labelled_stm/mod.rs @@ -65,18 +65,25 @@ where // Early Error: It is a Syntax Error if any strict mode source code matches this rule. // https://tc39.es/ecma262/#sec-labelled-statements-static-semantics-early-errors // https://tc39.es/ecma262/#sec-labelled-function-declarations - TokenKind::Keyword((Keyword::Function, _)) if strict => { + TokenKind::Keyword((Keyword::Function, _)) + if cfg!(not(feature = "annex-b")) || strict => + { return Err(Error::general( - "In strict mode code, functions can only be declared at top level or inside a block.", - next_token.span().start() + format!( + "{}functions can only be declared at top level or inside a block.", + if strict { "in strict mode code, " } else { "" }, + ), + next_token.span().start(), )) } TokenKind::Keyword((Keyword::Function, _)) => { FunctionDeclaration::new(self.allow_yield, self.allow_await, false) - .parse(cursor, interner)? - .into() + .parse(cursor, interner)? + .into() } - _ => Statement::new(self.allow_yield, self.allow_await, self.allow_return).parse(cursor, interner)?.into() + _ => Statement::new(self.allow_yield, self.allow_await, self.allow_return) + .parse(cursor, interner)? + .into(), }; Ok(ast::statement::Labelled::new(labelled_item, label)) diff --git a/boa_parser/src/parser/statement/variable/mod.rs b/boa_parser/src/parser/statement/variable/mod.rs index ce6a0f52f56..54f6241d59d 100644 --- a/boa_parser/src/parser/statement/variable/mod.rs +++ b/boa_parser/src/parser/statement/variable/mod.rs @@ -214,8 +214,13 @@ where .is_some() { Some( - Initializer::new(Some(ident), true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?, + Initializer::new( + Some(ident), + self.allow_in, + self.allow_yield, + self.allow_await, + ) + .parse(cursor, interner)?, ) } else { None diff --git a/boa_tester/Cargo.toml b/boa_tester/Cargo.toml index f5ea453661b..e876fe8f60d 100644 --- a/boa_tester/Cargo.toml +++ b/boa_tester/Cargo.toml @@ -12,7 +12,7 @@ repository.workspace = true rust-version.workspace = true [dependencies] -boa_engine = { workspace = true, features = ["annex-b"] } +boa_engine.workspace = true boa_gc.workspace = true clap = { version = "4.2.4", features = ["derive"] } serde = { version = "1.0.160", features = ["derive"] } @@ -31,5 +31,4 @@ comfy-table = "6.1.4" serde_repr = "0.1.12" [features] -default = ["intl"] -intl = ["boa_engine/intl"] +default = ["boa_engine/intl", "boa_engine/annex-b"] diff --git a/boa_wasm/Cargo.toml b/boa_wasm/Cargo.toml index df5afbb704e..315642c63f7 100644 --- a/boa_wasm/Cargo.toml +++ b/boa_wasm/Cargo.toml @@ -12,11 +12,14 @@ repository.workspace = true rust-version.workspace = true [dependencies] -boa_engine = { workspace = true, features = ["console", "annex-b"] } +boa_engine = { workspace = true, features = ["console"] } wasm-bindgen = "0.2.84" getrandom = { version = "0.2.9", features = ["js"] } chrono = { version = "0.4.24", features = ["clock", "std", "wasmbind"] } +[features] +default = ["boa_engine/annex-b"] + [lib] crate-type = ["cdylib", "lib"] name = "boa_wasm" From f9fae5de907fe04757221ffd74fca336164ef5cc Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Tue, 18 Apr 2023 19:52:07 -0600 Subject: [PATCH 2/3] Add assert to restrict initializers on for-of loops --- boa_engine/src/bytecompiler/statement/loop.rs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/boa_engine/src/bytecompiler/statement/loop.rs b/boa_engine/src/bytecompiler/statement/loop.rs index fdc89d804eb..785edceca70 100644 --- a/boa_engine/src/bytecompiler/statement/loop.rs +++ b/boa_engine/src/bytecompiler/statement/loop.rs @@ -292,19 +292,22 @@ impl ByteCompiler<'_, '_> { ByteCompiler::access_set_top_of_stack_expr_fn, ); } - // Can ignore initializers since those aren't allowed on for-of loops. - IterableLoopInitializer::Var(declaration) => match declaration.binding() { - Binding::Identifier(ident) => { - self.create_mutable_binding(*ident, true, false); - self.emit_binding(BindingOpcode::InitVar, *ident); - } - Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - self.create_mutable_binding(ident, true, false); + IterableLoopInitializer::Var(declaration) => { + // ignore initializers since those aren't allowed on for-of loops. + assert!(declaration.init().is_none()); + match declaration.binding() { + Binding::Identifier(ident) => { + self.create_mutable_binding(*ident, true, false); + self.emit_binding(BindingOpcode::InitVar, *ident); + } + Binding::Pattern(pattern) => { + for ident in bound_names(pattern) { + self.create_mutable_binding(ident, true, false); + } + self.compile_declaration_pattern(pattern, BindingOpcode::InitVar); } - self.compile_declaration_pattern(pattern, BindingOpcode::InitVar); } - }, + } IterableLoopInitializer::Let(declaration) => match declaration { Binding::Identifier(ident) => { self.create_mutable_binding(*ident, false, false); From 0f9b268acc4ec1f67d8d25a03606620a5fc9724b Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Wed, 19 Apr 2023 15:40:43 -0600 Subject: [PATCH 3/3] Reuse `Error` function for error creation --- boa_parser/src/error.rs | 8 ++++---- boa_parser/src/parser/statement/if_stm/mod.rs | 6 ++++-- boa_parser/src/parser/statement/labelled_stm/mod.rs | 7 ++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/boa_parser/src/error.rs b/boa_parser/src/error.rs index 3b17dcb4728..ffa4c25f41b 100644 --- a/boa_parser/src/error.rs +++ b/boa_parser/src/error.rs @@ -125,11 +125,11 @@ impl Error { } } - /// Creates a "general" parsing error with the specific error message for a wrong function declaration in an if statement. - pub(crate) fn function_declaration_in_if(position: Position, strict: bool) -> Self { + /// Creates a "general" parsing error with the specific error message for a misplaced function declaration. + pub(crate) fn misplaced_function_declaration(position: Position, strict: bool) -> Self { Self::General { message: format!( - "{}functions can only be declared at top level or inside a block.", + "{}functions can only be declared at the top level or inside a block.", if strict { "in strict mode code, " } else { "" } ) .into(), @@ -140,7 +140,7 @@ impl Error { /// Creates a "general" parsing error with the specific error message for a wrong function declaration with label. pub(crate) fn wrong_labelled_function_declaration(position: Position) -> Self { Self::General { - message: "labelled functions can only be declared at top level or inside a block" + message: "labelled functions can only be declared at the top level or inside a block" .into(), position, } diff --git a/boa_parser/src/parser/statement/if_stm/mod.rs b/boa_parser/src/parser/statement/if_stm/mod.rs index 9dc181bf8fd..0b0bbe25ce2 100644 --- a/boa_parser/src/parser/statement/if_stm/mod.rs +++ b/boa_parser/src/parser/statement/if_stm/mod.rs @@ -78,7 +78,7 @@ where // FunctionDeclarations in IfStatement Statement Clauses // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses if cfg!(not(feature = "annex-b")) || strict { - return Err(Error::function_declaration_in_if(position, strict)); + return Err(Error::misplaced_function_declaration(position, strict)); } // Source text matched by this production is processed as if each matching // occurrence of FunctionDeclaration[?Yield, ?Await, ~Default] was the sole @@ -117,7 +117,9 @@ where // FunctionDeclarations in IfStatement Statement Clauses // https://tc39.es/ecma262/#sec-functiondeclarations-in-ifstatement-statement-clauses if cfg!(not(feature = "annex-b")) || strict { - return Err(Error::function_declaration_in_if(position, strict)); + return Err(Error::misplaced_function_declaration( + position, strict, + )); } // Source text matched by this production is processed as if each matching diff --git a/boa_parser/src/parser/statement/labelled_stm/mod.rs b/boa_parser/src/parser/statement/labelled_stm/mod.rs index 9c024191fc3..16026ef60d2 100644 --- a/boa_parser/src/parser/statement/labelled_stm/mod.rs +++ b/boa_parser/src/parser/statement/labelled_stm/mod.rs @@ -68,12 +68,9 @@ where TokenKind::Keyword((Keyword::Function, _)) if cfg!(not(feature = "annex-b")) || strict => { - return Err(Error::general( - format!( - "{}functions can only be declared at top level or inside a block.", - if strict { "in strict mode code, " } else { "" }, - ), + return Err(Error::misplaced_function_declaration( next_token.span().start(), + strict, )) } TokenKind::Keyword((Keyword::Function, _)) => {