diff --git a/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs b/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs index 34c29498968..efa6c9b1f1b 100644 --- a/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs +++ b/boa/src/syntax/ast/node/declaration/const_decl_list/mod.rs @@ -2,7 +2,7 @@ use crate::{ environment::lexical_environment::VariableScope, exec::Executable, gc::{Finalize, Trace}, - syntax::ast::node::{join_nodes, Identifier, Node, declaration::DeclarationList}, + syntax::ast::node::{declaration::Declaration, join_nodes, Node}, Context, Result, Value, }; use std::fmt; @@ -59,12 +59,6 @@ impl Executable for ConstDeclList { } } -impl DeclarationList for ConstDeclList { - fn names(&self) -> Box + '_> { - return Box::new(self.as_ref().iter().map(|i| i.name())) - } -} - impl From for ConstDeclList where T: Into>, @@ -105,44 +99,4 @@ impl From for Node { } } -/// Individual constant declaration. -#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct ConstDecl { - name: Identifier, - init: Option, -} - -impl fmt::Display for ConstDecl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.name, f)?; - if let Some(ref init) = self.init { - write!(f, " = {}", init)?; - } - Ok(()) - } -} - -impl ConstDecl { - /// Creates a new variable declaration. - pub(in crate::syntax) fn new(name: N, init: Option) -> Self - where - N: Into, - I: Into, - { - Self { - name: name.into(), - init: init.map(|n| n.into()), - } - } - - /// Gets the name of the variable. - pub fn name(&self) -> &str { - self.name.as_ref() - } - - /// Gets the initialization node for the variable, if any. - pub fn init(&self) -> &Option { - &self.init - } -} +pub type ConstDecl = Declaration; diff --git a/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs b/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs index b3bce456953..1f2265ff4ed 100644 --- a/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs +++ b/boa/src/syntax/ast/node/declaration/let_decl_list/mod.rs @@ -2,7 +2,7 @@ use crate::{ environment::lexical_environment::VariableScope, exec::Executable, gc::{Finalize, Trace}, - syntax::ast::node::{declaration::DeclarationList, join_nodes, Identifier, Node}, + syntax::ast::node::{declaration::Declaration, join_nodes, Node}, Context, Result, Value, }; use std::fmt; @@ -56,12 +56,6 @@ impl Executable for LetDeclList { } } -impl DeclarationList for LetDeclList { - fn names(&self) -> Box + '_> { - return Box::new(self.as_ref().iter().map(|i| i.name())); - } -} - impl From for LetDeclList where T: Into>, @@ -102,44 +96,4 @@ impl From for Node { } } -/// Individual constant declaration. -#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] -#[derive(Clone, Debug, Trace, Finalize, PartialEq)] -pub struct LetDecl { - name: Identifier, - init: Option, -} - -impl fmt::Display for LetDecl { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(&self.name, f)?; - if let Some(ref init) = self.init { - write!(f, " = {}", init)?; - } - Ok(()) - } -} - -impl LetDecl { - /// Creates a new variable declaration. - pub(in crate::syntax) fn new(name: N, init: I) -> Self - where - N: Into, - I: Into>, - { - Self { - name: name.into(), - init: init.into(), - } - } - - /// Gets the name of the variable. - pub fn name(&self) -> &str { - self.name.as_ref() - } - - /// Gets the initialization node for the variable, if any. - pub fn init(&self) -> Option<&Node> { - self.init.as_ref() - } -} +pub type LetDecl = Declaration; diff --git a/boa/src/syntax/ast/node/declaration/mod.rs b/boa/src/syntax/ast/node/declaration/mod.rs index ffcf0927eec..51ba04b3f32 100644 --- a/boa/src/syntax/ast/node/declaration/mod.rs +++ b/boa/src/syntax/ast/node/declaration/mod.rs @@ -1,29 +1,205 @@ //! Declaration nodes +use crate::{ + environment::lexical_environment::VariableScope, + exec::Executable, + gc::{Finalize, Trace}, + syntax::ast::node::{join_nodes, Identifier, Node}, + Context, Result, Value, +}; +use std::fmt; + +#[cfg(feature = "deser")] +use serde::{Deserialize, Serialize}; pub mod arrow_function_decl; pub mod async_function_decl; pub mod async_function_expr; -pub mod const_decl_list; +// pub mod const_decl_list; pub mod function_decl; pub mod function_expr; -pub mod let_decl_list; -pub mod var_decl_list; +// pub mod let_decl_list; +// pub mod var_decl_list; pub use self::{ arrow_function_decl::ArrowFunctionDecl, async_function_decl::AsyncFunctionDecl, async_function_expr::AsyncFunctionExpr, - const_decl_list::{ConstDecl, ConstDeclList}, + // const_decl_list::{ConstDecl, ConstDeclList}, function_decl::FunctionDecl, function_expr::FunctionExpr, - let_decl_list::{LetDecl, LetDeclList}, - var_decl_list::{VarDecl, VarDeclList}, + // let_decl_list::{LetDecl, LetDeclList}, + // var_decl_list::{VarDecl, VarDeclList}, }; #[cfg(test)] mod tests; -/// A trait for lists of declarations such as LetDeclList -pub(crate) trait DeclarationList { - fn names(&self) -> Box + '_>; +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub enum DeclarationType { + Let, + Const, + Var, +} + +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct DeclarationList { + #[cfg_attr(feature = "deser", serde(flatten))] + list: Box<[Declaration]>, + declaration_type: DeclarationType, +} + +impl Executable for DeclarationList { + fn run(&self, context: &mut Context) -> Result { + for decl in self.as_ref() { + use DeclarationType::*; + let val = match (&self.declaration_type, decl.init()) { + (Const, None) => { + return context.throw_syntax_error("missing = in const declaration") + } + (_, Some(init)) => init.run(context)?, + (_, None) => Value::undefined(), + }; + context + .realm_mut() + .environment + .create_mutable_binding( + decl.name().to_owned(), + false, + match &self.declaration_type { + Var => VariableScope::Function, + _ => VariableScope::Block, + }, + ) + .map_err(|e| e.to_error(context))?; + context + .realm_mut() + .environment + .initialize_binding(decl.name(), val) + .map_err(|e| e.to_error(context))?; + } + Ok(Value::undefined()) + } +} + +impl DeclarationList { + pub(in crate::syntax) fn new_let_decl_list(list: T) -> Self + where + T: Into>, + { + Self { + list: list.into(), + declaration_type: DeclarationType::Let, + } + } + pub(in crate::syntax) fn new_const_decl_list(list: T) -> Self + where + T: Into>, + { + Self { + list: list.into(), + declaration_type: DeclarationType::Const, + } + } + pub(in crate::syntax) fn new_var_decl_list(list: T) -> Self + where + T: Into>, + { + Self { + list: list.into(), + declaration_type: DeclarationType::Var, + } + } +} +// impl From for DeclarationList +// where +// T: Into>, +// { +// fn from(list: T) -> Self { +// Self { list: list.into() } +// } +// } + +// impl From for DeclarationList { +// fn from(decl: LetDecl) -> Self { +// Self { +// list: Box::new([decl]), +// } +// } +// } + +impl AsRef<[Declaration]> for DeclarationList { + fn as_ref(&self) -> &[Declaration] { + &self.list + } +} + +impl fmt::Display for DeclarationList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.list.is_empty() { + use DeclarationType::*; + match &self.declaration_type { + Let => write!(f, "let ")?, + Const => write!(f, "const ")?, + Var => write!(f, "var ")?, + } + join_nodes(f, &self.list) + } else { + Ok(()) + } + } +} + +impl From for Node { + fn from(list: DeclarationList) -> Self { + use DeclarationType::*; + match &list.declaration_type { + Let => Self::LetDeclList(list), + Const => Self::ConstDeclList(list), + Var => Self::VarDeclList(list), + } + } +} + +/// Individual declaration. +#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] +#[derive(Clone, Debug, Trace, Finalize, PartialEq)] +pub struct Declaration { + name: Identifier, + init: Option, +} + +impl fmt::Display for Declaration { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.name, f)?; + if let Some(ref init) = self.init { + write!(f, " = {}", init)?; + } + Ok(()) + } +} + +impl Declaration { + /// Creates a new variable declaration. + pub(in crate::syntax) fn new(name: N, init: I) -> Self + where + N: Into, + I: Into>, + { + Self { + name: name.into(), + init: init.into(), + } + } + + /// Gets the name of the variable. + pub fn name(&self) -> &str { + self.name.as_ref() + } + + /// Gets the initialization node for the variable, if any. + pub fn init(&self) -> Option<&Node> { + self.init.as_ref() + } } diff --git a/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs b/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs index d9daec765cc..061a94c4cd7 100644 --- a/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs +++ b/boa/src/syntax/ast/node/declaration/var_decl_list/mod.rs @@ -2,7 +2,7 @@ use crate::{ environment::lexical_environment::VariableScope, exec::Executable, gc::{Finalize, Trace}, - syntax::ast::node::{join_nodes, Identifier, Node, declaration::DeclarationList}, + syntax::ast::node::{join_nodes, Identifier, Node}, Context, Result, Value, }; use std::fmt; @@ -64,12 +64,6 @@ impl Executable for VarDeclList { } } -impl DeclarationList for VarDeclList { - fn names(&self) -> Box + '_> { - return Box::new(self.as_ref().iter().map(|i| i.name())) - } -} - impl From for VarDeclList where T: Into>, diff --git a/boa/src/syntax/ast/node/mod.rs b/boa/src/syntax/ast/node/mod.rs index 01daf908eb6..8489c053b61 100644 --- a/boa/src/syntax/ast/node/mod.rs +++ b/boa/src/syntax/ast/node/mod.rs @@ -29,8 +29,8 @@ pub use self::{ call::Call, conditional::{ConditionalOp, If}, declaration::{ - ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, ConstDecl, ConstDeclList, - FunctionDecl, FunctionExpr, LetDecl, LetDeclList, VarDecl, VarDeclList, + ArrowFunctionDecl, AsyncFunctionDecl, AsyncFunctionExpr, Declaration, DeclarationList, + FunctionDecl, FunctionExpr, }, field::{GetConstField, GetField}, identifier::Identifier, @@ -109,7 +109,7 @@ pub enum Node { Const(Const), /// A constant declaration list. [More information](./declaration/struct.ConstDeclList.html). - ConstDeclList(ConstDeclList), + ConstDeclList(DeclarationList), /// A continue statement. [More information](./iteration/struct.Continue.html). Continue(Continue), @@ -142,7 +142,7 @@ pub enum Node { If(If), /// A `let` declaration list. [More information](./declaration/struct.LetDeclList.html). - LetDeclList(LetDeclList), + LetDeclList(DeclarationList), /// A local identifier node. [More information](./identifier/struct.Identifier.html). Identifier(Identifier), @@ -192,7 +192,7 @@ pub enum Node { UnaryOp(UnaryOp), /// Array declaration node. [More information](./declaration/struct.VarDeclList.html). - VarDeclList(VarDeclList), + VarDeclList(DeclarationList), /// A 'while {...}' node. [More information](./iteration/struct.WhileLoop.html). WhileLoop(WhileLoop), diff --git a/boa/src/syntax/ast/node/statement_list/mod.rs b/boa/src/syntax/ast/node/statement_list/mod.rs index 78af439317e..7339bd4ee7b 100644 --- a/boa/src/syntax/ast/node/statement_list/mod.rs +++ b/boa/src/syntax/ast/node/statement_list/mod.rs @@ -3,7 +3,7 @@ use crate::{ exec::{Executable, InterpreterState}, gc::{empty_trace, Finalize, Trace}, - syntax::ast::node::{Node, declaration::DeclarationList}, + syntax::ast::node::Node, BoaProfiler, Context, Result, Value, }; use std::{fmt, ops::Deref, rc::Rc}; @@ -62,14 +62,9 @@ impl StatementList { let mut set = HashSet::new(); for stmt in self.items() { match stmt { - Node::LetDeclList(decl) => { - for name in decl.names() { - set.insert(name); - } - } - Node::ConstDeclList(decl) => { - for name in decl.names() { - set.insert(name); + Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) => { + for decl in decl_list.as_ref() { + set.insert(decl.name()); } } _ => (), @@ -81,9 +76,9 @@ impl StatementList { pub fn var_declared_names(&self) -> HashSet<&str> { let mut set = HashSet::new(); for stmt in self.items() { - if let Node::VarDeclList(decl) = stmt { - for name in decl.names() { - set.insert(name); + if let Node::VarDeclList(decl_list) = stmt { + for decl in decl_list.as_ref() { + set.insert(decl.name()); } } } diff --git a/boa/src/syntax/parser/statement/declaration/lexical.rs b/boa/src/syntax/parser/statement/declaration/lexical.rs index 20ad78c98be..f7cb40d54f7 100644 --- a/boa/src/syntax/parser/statement/declaration/lexical.rs +++ b/boa/src/syntax/parser/statement/declaration/lexical.rs @@ -11,7 +11,7 @@ use crate::syntax::lexer::TokenKind; use crate::{ syntax::{ ast::{ - node::{ConstDecl, ConstDeclList, LetDecl, LetDeclList, Node}, + node::{Declaration, DeclarationList, Node}, Keyword, Punctuator, }, parser::{ @@ -158,7 +158,7 @@ where if self.is_const { if self.const_init_required { if init.is_some() { - const_decls.push(ConstDecl::new(ident, init)); + const_decls.push(Declaration::new(ident, init)); } else { return Err(ParseError::expected( vec![TokenKind::Punctuator(Punctuator::Assign)], @@ -167,10 +167,10 @@ where )); } } else { - const_decls.push(ConstDecl::new(ident, init)) + const_decls.push(Declaration::new(ident, init)) } } else { - let_decls.push(LetDecl::new(ident, init)); + let_decls.push(Declaration::new(ident, init)); } match cursor.peek_semicolon()? { @@ -201,9 +201,9 @@ where } if self.is_const { - Ok(ConstDeclList::from(const_decls).into()) + Ok(DeclarationList::new_const_decl_list(const_decls).into()) } else { - Ok(LetDeclList::from(let_decls).into()) + Ok(DeclarationList::new_let_decl_list(let_decls).into()) } } } diff --git a/boa/src/syntax/parser/statement/mod.rs b/boa/src/syntax/parser/statement/mod.rs index 089f48ae47a..995fcb3cb5a 100644 --- a/boa/src/syntax/parser/statement/mod.rs +++ b/boa/src/syntax/parser/statement/mod.rs @@ -40,8 +40,8 @@ use super::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, TokenParser use crate::{ syntax::{ - ast::{node, node::declaration::DeclarationList, Keyword, Node, Punctuator}, - lexer::{Error as LexError, InputElement, TokenKind}, + ast::{node, Keyword, Node, Punctuator}, + lexer::{Error as LexError, InputElement, Position, TokenKind}, parser::expression::await_expr::AwaitExpression, }, BoaProfiler, @@ -271,107 +271,74 @@ where let _timer = BoaProfiler::global().start_event("StatementList", "Parsing"); let mut items = Vec::new(); - unsafe { - // SAFETY: - // `items` and ALL of its contents must NOT be dropped or moved before the two - // declared names sets are dropped. `items` performing reallocation is safe - // because the unsafely borrowed values are boxed. - // - // We want to check if there are any redeclarations of declared names while parsing - // statements so that early errors can be rasied. To do this, two `HashSet<&str>` sets - // are populated with immutable references to declared names, stored as `Box` in - // `item` values, which get moved to the `items` vector. - // - // Under borrow checker rules, `item` must be borrowed to borrow any values it - // contains. This means that when we borrow any declared names in `item`, we will not - // be able to move it to the `items` vector. We can't borrow `item` after moving it to - // the `items` vector either, because it's not possible to push to a vector while it is - // borrowed immutably. - // - // The safe alternative would be to copy declared names, changing the sets to be - // `HashSet` or `HashSet>`, but this would require copying every - // single declared name. - // - // Alternatively, it's possible to perform this process in two loops, but then it - // becomes more difficult to return helpful errors since we won't have access to the - // parser position of the declaration which caused the error. - - let mut UNSAFE_lexically_declared_names: HashSet<&str> = HashSet::new(); - let mut UNSAFE_var_declared_names: HashSet<&str> = HashSet::new(); - - loop { - // Store position of let/const/var tokens - let position = match cursor.peek(0)? { - Some(token) if self.break_nodes.contains(token.kind()) => break, - None => break, - Some(token) => token.span().end(), - }; - - let item = StatementListItem::new( - self.allow_yield, - self.allow_await, - self.allow_return, - self.in_block, - ) - .parse(cursor)?; - - // Handle any redeclarations - // https://tc39.es/ecma262/#sec-block-static-semantics-early-errors - // - // SAFETY: All unsafe actions happen in this match statement! - match &item { - Node::LetDeclList(x) => { - for name in x.names() { - // if name in VarDeclaredNames or can't be added to - // LexicallyDeclaredNames, raise an error - if UNSAFE_var_declared_names.contains(name) - || !UNSAFE_lexically_declared_names.insert(&*(name as *const str)) - { - return Err(ParseError::lex(LexError::Syntax( - format!("Redeclaration of variable `{}`", name).into(), - position, - ))); - } - } - } - Node::ConstDeclList(x) => { - for name in x.names() { + loop { + // Store position of let/const/var tokens + match cursor.peek(0)? { + Some(token) if self.break_nodes.contains(token.kind()) => break, + None => break, + Some(_) => (), + } + + let item = StatementListItem::new( + self.allow_yield, + self.allow_await, + self.allow_return, + self.in_block, + ) + .parse(cursor)?; + + items.push(item); + + // move the cursor forward for any consecutive semicolon. + while cursor.next_if(Punctuator::Semicolon)?.is_some() {} + } + + // Handle any redeclarations + // https://tc39.es/ecma262/#sec-block-static-semantics-early-errors + { + let mut lexically_declared_names: HashSet<&str> = HashSet::new(); + let mut var_declared_names: HashSet<&str> = HashSet::new(); + + // TODO: Use more helpful positions in errors when spans are added to Nodes + for item in &items { + match item { + Node::LetDeclList(decl_list) | Node::ConstDeclList(decl_list) => { + for decl in decl_list.as_ref() { // if name in VarDeclaredNames or can't be added to // LexicallyDeclaredNames, raise an error - if UNSAFE_var_declared_names.contains(name) - || !UNSAFE_lexically_declared_names.insert(&*(name as *const str)) + if var_declared_names.contains(decl.name()) + || !lexically_declared_names.insert(decl.name()) { return Err(ParseError::lex(LexError::Syntax( - format!("Redeclaration of variable `{}`", name).into(), - position, + format!("Redeclaration of variable `{}`", decl.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, ))); } } } - Node::VarDeclList(x) => { - for name in x.names() { + Node::VarDeclList(decl_list) => { + for decl in decl_list.as_ref() { // if name in LexicallyDeclaredNames, raise an error - if UNSAFE_lexically_declared_names.contains(name) { + if lexically_declared_names.contains(decl.name()) { return Err(ParseError::lex(LexError::Syntax( - format!("Redeclaration of variable `{}`", name).into(), - position, + format!("Redeclaration of variable `{}`", decl.name()).into(), + match cursor.peek(0)? { + Some(token) => token.span().end(), + None => Position::new(1, 1), + }, ))); } // otherwise, add to VarDeclaredNames - UNSAFE_var_declared_names.insert(&*(name as *const str)); + var_declared_names.insert(decl.name()); } } _ => (), } - items.push(item); - - // move the cursor forward for any consecutive semicolon. - while cursor.next_if(Punctuator::Semicolon)?.is_some() {} } - - // Unsafe sets are dropped here } - items.sort_by(Node::hoistable_order); Ok(items.into()) diff --git a/boa/src/syntax/parser/statement/variable/mod.rs b/boa/src/syntax/parser/statement/variable/mod.rs index f65f62e06a7..49583aeba1a 100644 --- a/boa/src/syntax/parser/statement/variable/mod.rs +++ b/boa/src/syntax/parser/statement/variable/mod.rs @@ -3,7 +3,7 @@ use crate::{ syntax::{ ast::{ - node::{VarDecl, VarDeclList}, + node::{Declaration, DeclarationList}, Keyword, Punctuator, }, lexer::TokenKind, @@ -52,7 +52,7 @@ impl TokenParser for VariableStatement where R: Read, { - type Output = VarDeclList; + type Output = DeclarationList; fn parse(self, cursor: &mut Cursor) -> Result { let _timer = BoaProfiler::global().start_event("VariableStatement", "Parsing"); @@ -106,7 +106,7 @@ impl TokenParser for VariableDeclarationList where R: Read, { - type Output = VarDeclList; + type Output = DeclarationList; fn parse(self, cursor: &mut Cursor) -> Result { let mut list = Vec::new(); @@ -127,7 +127,7 @@ where } } - Ok(VarDeclList::from(list)) + Ok(DeclarationList::new_var_decl_list(list)) } } @@ -164,7 +164,7 @@ impl TokenParser for VariableDeclaration where R: Read, { - type Output = VarDecl; + type Output = Declaration; fn parse(self, cursor: &mut Cursor) -> Result { // TODO: BindingPattern @@ -181,6 +181,6 @@ where None }; - Ok(VarDecl::new(name, init)) + Ok(Declaration::new(name, init)) } }