diff --git a/boa_ast/src/declaration/mod.rs b/boa_ast/src/declaration/mod.rs index 9deefbc836..1a51c438cf 100644 --- a/boa_ast/src/declaration/mod.rs +++ b/boa_ast/src/declaration/mod.rs @@ -14,10 +14,7 @@ //! [class]: https://tc39.es/ecma262/#prod-ClassDeclaration //! [diff]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements#difference_between_statements_and_declarations -use super::{ - expression::Identifier, - function::{AsyncFunction, AsyncGenerator, Class, Function, Generator}, -}; +use super::function::{AsyncFunction, AsyncGenerator, Class, Function, Generator}; use boa_interner::{Interner, ToIndentedString, ToInternedString}; use core::ops::ControlFlow; @@ -51,72 +48,6 @@ pub enum Declaration { Lexical(LexicalDeclaration), } -impl Declaration { - /// Return the lexically declared names of a `Declaration`. - /// - /// The returned list may contain duplicates. - /// - /// If a declared name originates from a function declaration it is flagged as `true` in the returned list. - /// - /// More information: - /// - [ECMAScript specification][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames - pub(crate) fn lexically_declared_names(&self) -> Vec<(Identifier, bool)> { - match self { - Declaration::Function(f) => { - if let Some(name) = f.name() { - vec![(name, true)] - } else { - Vec::new() - } - } - Declaration::Generator(g) => { - if let Some(name) = g.name() { - vec![(name, false)] - } else { - Vec::new() - } - } - Declaration::AsyncFunction(af) => { - if let Some(name) = af.name() { - vec![(name, false)] - } else { - Vec::new() - } - } - Declaration::AsyncGenerator(ag) => { - if let Some(name) = ag.name() { - vec![(name, false)] - } else { - Vec::new() - } - } - Declaration::Class(cl) => { - if let Some(name) = cl.name() { - vec![(name, false)] - } else { - Vec::new() - } - } - Declaration::Lexical(lexical) => { - let mut names = Vec::new(); - for decl in lexical.variable_list().as_ref() { - match decl.binding() { - Binding::Identifier(ident) => { - names.push((*ident, false)); - } - Binding::Pattern(pattern) => { - names.extend(pattern.idents().into_iter().map(|name| (name, false))); - } - } - } - names - } - } - } -} - impl ToIndentedString for Declaration { fn to_indented_string(&self, interner: &Interner, indentation: usize) -> String { match self { diff --git a/boa_ast/src/declaration/variable.rs b/boa_ast/src/declaration/variable.rs index 0fb28753ec..cee81c8186 100644 --- a/boa_ast/src/declaration/variable.rs +++ b/boa_ast/src/declaration/variable.rs @@ -299,12 +299,6 @@ impl Variable { pub fn init(&self) -> Option<&Expression> { self.init.as_ref() } - - /// Gets the list of declared identifiers. - #[must_use] - pub fn idents(&self) -> Vec { - self.binding.idents() - } } impl VisitWith for Variable { @@ -358,16 +352,6 @@ impl From for Binding { } } -impl Binding { - /// Gets the list of declared identifiers. - pub(crate) fn idents(&self) -> Vec { - match self { - Binding::Identifier(id) => vec![*id], - Binding::Pattern(ref pat) => pat.idents(), - } - } -} - impl ToInternedString for Binding { fn to_interned_string(&self, interner: &Interner) -> String { match self { diff --git a/boa_ast/src/expression/mod.rs b/boa_ast/src/expression/mod.rs index ae1c8eb47a..e0fc2587d8 100644 --- a/boa_ast/src/expression/mod.rs +++ b/boa_ast/src/expression/mod.rs @@ -152,7 +152,7 @@ pub enum Expression { /// A FormalParameterList. /// /// This is only used in the parser itself. - /// It is not a valid AST node. + /// It is not a valid expression node. #[doc(hidden)] FormalParameterList(FormalParameterList), } diff --git a/boa_ast/src/function/parameters.rs b/boa_ast/src/function/parameters.rs index 104bf1f3bb..7bede15e81 100644 --- a/boa_ast/src/function/parameters.rs +++ b/boa_ast/src/function/parameters.rs @@ -1,7 +1,7 @@ use crate::{ declaration::{Binding, Variable}, - expression::{Expression, Identifier}, - pattern::Pattern, + expression::Expression, + operations::bound_names, try_break, visitor::{VisitWith, Visitor, VisitorMut}, }; @@ -40,7 +40,7 @@ impl FormalParameterList { let mut names = FxHashSet::default(); for parameter in ¶meters { - let parameter_names = parameter.names(); + let parameter_names = bound_names(parameter); for name in parameter_names { if name == Sym::ARGUMENTS { @@ -224,19 +224,6 @@ impl FormalParameter { } } - /// Gets the name of the formal parameter. - #[must_use] - pub fn names(&self) -> Vec { - match self.variable.binding() { - Binding::Identifier(ident) => vec![*ident], - Binding::Pattern(pattern) => match pattern { - Pattern::Object(object_pattern) => object_pattern.idents(), - - Pattern::Array(array_pattern) => array_pattern.idents(), - }, - } - } - /// Gets the variable of the formal parameter #[must_use] pub fn variable(&self) -> &Variable { @@ -255,7 +242,7 @@ impl FormalParameter { self.is_rest_param } - /// Returns `true` if the parameter is a simple [`Identifier`]. + /// Returns `true` if the parameter is an identifier. #[must_use] pub fn is_identifier(&self) -> bool { matches!(&self.variable.binding(), Binding::Identifier(_)) diff --git a/boa_ast/src/operations.rs b/boa_ast/src/operations.rs index 256dd4a767..0a95273fc8 100644 --- a/boa_ast/src/operations.rs +++ b/boa_ast/src/operations.rs @@ -3,18 +3,22 @@ //! [spec]: https://tc39.es/ecma262/#sec-syntax-directed-operations use core::ops::ControlFlow; +use std::convert::Infallible; use boa_interner::Sym; +use rustc_hash::FxHashSet; use crate::{ + declaration::VarDeclaration, expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield}, function::{ ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, Function, Generator, }, property::{MethodDefinition, PropertyDefinition}, - visitor::{VisitWith, Visitor}, - Expression, + statement::LabelledItem, + visitor::{NodeRef, VisitWith, Visitor}, + Declaration, Expression, Statement, StatementList, StatementListItem, }; /// Represents all the possible symbols searched for by the [`Contains`][contains] operation. @@ -51,6 +55,7 @@ pub enum ContainsSymbol { /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-contains #[must_use] +#[inline] pub fn contains(node: &N, symbol: ContainsSymbol) -> bool where N: VisitWith, @@ -62,22 +67,27 @@ where impl<'ast> Visitor<'ast> for ContainsVisitor { type BreakTy = (); + #[inline] fn visit_function(&mut self, _: &'ast Function) -> ControlFlow { ControlFlow::Continue(()) } + #[inline] fn visit_async_function(&mut self, _: &'ast AsyncFunction) -> ControlFlow { ControlFlow::Continue(()) } + #[inline] fn visit_generator(&mut self, _: &'ast Generator) -> ControlFlow { ControlFlow::Continue(()) } + #[inline] fn visit_async_generator(&mut self, _: &'ast AsyncGenerator) -> ControlFlow { ControlFlow::Continue(()) } + #[inline] fn visit_class(&mut self, node: &'ast Class) -> ControlFlow { if !node.elements().is_empty() && self.0 == ContainsSymbol::ClassBody { return ControlFlow::Break(()); @@ -91,6 +101,7 @@ where } // `ComputedPropertyContains`: https://tc39.es/ecma262/#sec-static-semantics-computedpropertycontains + #[inline] fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { match node { ClassElement::MethodDefinition(name, _) @@ -101,6 +112,7 @@ where } } + #[inline] fn visit_property_definition( &mut self, node: &'ast PropertyDefinition, @@ -115,6 +127,7 @@ where node.visit_with(self) } + #[inline] fn visit_arrow_function( &mut self, node: &'ast ArrowFunction, @@ -134,6 +147,7 @@ where node.visit_with(self) } + #[inline] fn visit_async_arrow_function( &mut self, node: &'ast AsyncArrowFunction, @@ -153,6 +167,7 @@ where node.visit_with(self) } + #[inline] fn visit_super_property_access( &mut self, node: &'ast SuperPropertyAccess, @@ -163,6 +178,7 @@ where node.visit_with(self) } + #[inline] fn visit_super_call(&mut self, node: &'ast SuperCall) -> ControlFlow { if [ContainsSymbol::SuperCall, ContainsSymbol::Super].contains(&self.0) { return ControlFlow::Break(()); @@ -170,6 +186,7 @@ where node.visit_with(self) } + #[inline] fn visit_yield(&mut self, node: &'ast Yield) -> ControlFlow { if self.0 == ContainsSymbol::YieldExpression { return ControlFlow::Break(()); @@ -178,6 +195,7 @@ where node.visit_with(self) } + #[inline] fn visit_await(&mut self, node: &'ast Await) -> ControlFlow { if self.0 == ContainsSymbol::AwaitExpression { return ControlFlow::Break(()); @@ -186,6 +204,7 @@ where node.visit_with(self) } + #[inline] fn visit_expression(&mut self, node: &'ast Expression) -> ControlFlow { if node == &Expression::This && self.0 == ContainsSymbol::This { return ControlFlow::Break(()); @@ -217,6 +236,7 @@ where impl<'ast> Visitor<'ast> for ContainsArgsVisitor { type BreakTy = (); + #[inline] fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow { if node.sym() == Sym::ARGUMENTS { ControlFlow::Break(()) @@ -225,22 +245,27 @@ where } } + #[inline] fn visit_function(&mut self, _: &'ast Function) -> ControlFlow { ControlFlow::Continue(()) } + #[inline] fn visit_async_function(&mut self, _: &'ast AsyncFunction) -> ControlFlow { ControlFlow::Continue(()) } + #[inline] fn visit_generator(&mut self, _: &'ast Generator) -> ControlFlow { ControlFlow::Continue(()) } + #[inline] fn visit_async_generator(&mut self, _: &'ast AsyncGenerator) -> ControlFlow { ControlFlow::Continue(()) } + #[inline] fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { match node { ClassElement::MethodDefinition(name, _) @@ -250,6 +275,7 @@ where node.visit_with(self) } + #[inline] fn visit_property_definition( &mut self, node: &'ast PropertyDefinition, @@ -270,6 +296,7 @@ where /// /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-hasdirectsuper #[must_use] +#[inline] pub fn has_direct_super(method: &MethodDefinition) -> bool { match method { MethodDefinition::Get(f) | MethodDefinition::Set(f) | MethodDefinition::Ordinary(f) => { @@ -280,3 +307,389 @@ pub fn has_direct_super(method: &MethodDefinition) -> bool { MethodDefinition::Async(f) => contains(f, ContainsSymbol::SuperCall), } } + +/// A container that [`BoundNamesVisitor`] can use to push the found identifiers. +trait IdentList { + fn add(&mut self, value: Identifier, function: bool); +} + +impl IdentList for Vec { + #[inline] + fn add(&mut self, value: Identifier, _function: bool) { + self.push(value); + } +} + +impl IdentList for Vec<(Identifier, bool)> { + #[inline] + fn add(&mut self, value: Identifier, function: bool) { + self.push((value, function)); + } +} + +impl IdentList for FxHashSet { + #[inline] + fn add(&mut self, value: Identifier, _function: bool) { + self.insert(value); + } +} + +/// The [`Visitor`] used to obtain the bound names of a node. +#[derive(Debug)] +struct BoundNamesVisitor<'a, T: IdentList>(&'a mut T); + +impl<'ast, T: IdentList> Visitor<'ast> for BoundNamesVisitor<'_, T> { + type BreakTy = Infallible; + + #[inline] + fn visit_identifier(&mut self, node: &'ast Identifier) -> ControlFlow { + self.0.add(*node, false); + ControlFlow::Continue(()) + } + #[inline] + fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow { + ControlFlow::Continue(()) + } + // TODO: add "*default" for module default functions without name + #[inline] + fn visit_function(&mut self, node: &'ast Function) -> ControlFlow { + if let Some(ident) = node.name() { + self.0.add(ident, true); + } + ControlFlow::Continue(()) + } + #[inline] + fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow { + if let Some(ident) = node.name() { + self.0.add(ident, false); + } + ControlFlow::Continue(()) + } + #[inline] + fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow { + if let Some(ident) = node.name() { + self.0.add(ident, false); + } + ControlFlow::Continue(()) + } + #[inline] + fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow { + if let Some(ident) = node.name() { + self.0.add(ident, false); + } + ControlFlow::Continue(()) + } + #[inline] + fn visit_class(&mut self, node: &'ast Class) -> ControlFlow { + if let Some(ident) = node.name() { + self.0.add(ident, false); + } + ControlFlow::Continue(()) + } +} + +/// Returns a list with the bound names of an AST node, which may contain duplicates. +/// +/// This is equivalent to the [`BoundNames`][spec] syntax operation in the spec. +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-boundnames +#[must_use] +#[inline] +pub fn bound_names<'a, N>(node: &'a N) -> Vec +where + &'a N: Into>, +{ + let mut names = Vec::new(); + BoundNamesVisitor(&mut names).visit(node.into()); + + names +} + +/// The [`Visitor`] used to obtain the lexically declared names of a node. +#[derive(Debug)] +struct LexicallyDeclaredNamesVisitor<'a, T: IdentList>(&'a mut T); + +impl<'ast, T: IdentList> Visitor<'ast> for LexicallyDeclaredNamesVisitor<'_, T> { + type BreakTy = Infallible; + #[inline] + fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow { + ControlFlow::Continue(()) + } + #[inline] + fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { + if let Statement::Labelled(labelled) = node { + return self.visit_labelled(labelled); + } + ControlFlow::Continue(()) + } + #[inline] + fn visit_declaration(&mut self, node: &'ast Declaration) -> ControlFlow { + BoundNamesVisitor(self.0).visit_declaration(node) + } + #[inline] + fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { + match node { + LabelledItem::Function(f) => BoundNamesVisitor(self.0).visit_function(f), + LabelledItem::Statement(_) => ControlFlow::Continue(()), + } + } + #[inline] + fn visit_function(&mut self, node: &'ast Function) -> ControlFlow { + top_level_lexicals(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow { + top_level_lexicals(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow { + top_level_lexicals(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow { + top_level_lexicals(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow { + top_level_lexicals(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_async_arrow_function( + &mut self, + node: &'ast AsyncArrowFunction, + ) -> ControlFlow { + top_level_lexicals(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { + if let ClassElement::StaticBlock(stmts) = node { + top_level_lexicals(stmts, self.0); + } + ControlFlow::Continue(()) + } + + // TODO: ScriptBody : StatementList + // 1. Return TopLevelLexicallyDeclaredNames of StatementList. + // But we don't have that node yet. In the meantime, use `top_level_lexically_declared_names` directly. +} + +/// Returns a list with the lexical bindings of a node, which may contain duplicates. +/// +/// This is equivalent to the [`LexicallyDeclaredNames`][spec] syntax operation in the spec. +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames +#[must_use] +#[inline] +pub fn lexically_declared_names<'a, N>(node: &'a N) -> Vec +where + &'a N: Into>, +{ + let mut names = Vec::new(); + LexicallyDeclaredNamesVisitor(&mut names).visit(node.into()); + names +} + +/// Returns a list with the lexical bindings of a node, which may contain duplicates. +/// +/// If a declared name originates from a function declaration it is flagged as `true` in the returned +/// list. (See [B.3.2.4 Changes to Block Static Semantics: Early Errors]) +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames +/// [changes]: https://tc39.es/ecma262/#sec-block-duplicates-allowed-static-semantics +#[must_use] +#[inline] +pub fn lexically_declared_names_legacy<'a, N>(node: &'a N) -> Vec<(Identifier, bool)> +where + &'a N: Into>, +{ + let mut names = Vec::new(); + LexicallyDeclaredNamesVisitor(&mut names).visit(node.into()); + names +} + +/// The [`Visitor`] used to obtain the var declared names of a node. +#[derive(Debug)] +struct VarDeclaredNamesVisitor<'a>(&'a mut FxHashSet); + +impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { + type BreakTy = Infallible; + #[inline] + fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow { + ControlFlow::Continue(()) + } + #[inline] + fn visit_declaration(&mut self, _: &'ast Declaration) -> ControlFlow { + ControlFlow::Continue(()) + } + #[inline] + fn visit_var_declaration(&mut self, node: &'ast VarDeclaration) -> ControlFlow { + BoundNamesVisitor(self.0).visit_var_declaration(node) + } + #[inline] + fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { + match node { + LabelledItem::Function(_) => ControlFlow::Continue(()), + LabelledItem::Statement(stmt) => stmt.visit_with(self), + } + } + #[inline] + fn visit_function(&mut self, node: &'ast Function) -> ControlFlow { + top_level_vars(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow { + top_level_vars(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow { + top_level_vars(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow { + top_level_vars(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow { + top_level_vars(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_async_arrow_function( + &mut self, + node: &'ast AsyncArrowFunction, + ) -> ControlFlow { + top_level_vars(node.body(), self.0); + ControlFlow::Continue(()) + } + #[inline] + fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { + if let ClassElement::StaticBlock(stmts) = node { + top_level_vars(stmts, self.0); + } + node.visit_with(self) + } + + // TODO: ScriptBody : StatementList + // 1. Return TopLevelVarDeclaredNames of StatementList. + // But we don't have that node yet. In the meantime, use `top_level_var_declared_names` directly. +} + +/// Returns a set with the var bindings of a node, with no duplicates. +/// +/// This is equivalent to the [`VarDeclaredNames`][spec] syntax operation in the spec. +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-vardeclarednames +#[must_use] +#[inline] +pub fn var_declared_names<'a, N>(node: &'a N) -> FxHashSet +where + &'a N: Into>, +{ + let mut names = FxHashSet::default(); + VarDeclaredNamesVisitor(&mut names).visit(node.into()); + names +} + +/// Utility function that collects the top level lexicals of a statement list into `names`. +#[inline] +fn top_level_lexicals(stmts: &StatementList, names: &mut T) { + for stmt in stmts.statements() { + if let StatementListItem::Declaration(decl) = stmt { + match decl { + // Note + // At the top level of a function, or script, function declarations are treated like + // var declarations rather than like lexical declarations. + Declaration::Function(_) + | Declaration::Generator(_) + | Declaration::AsyncFunction(_) + | Declaration::AsyncGenerator(_) => {} + Declaration::Class(class) => { + BoundNamesVisitor(names).visit_class(class); + } + Declaration::Lexical(decl) => { + BoundNamesVisitor(names).visit_lexical_declaration(decl); + } + } + } + } +} + +/// Returns a list with the lexical bindings of a top-level statement list, which may contain duplicates. +/// +/// This is equivalent to the [`TopLevelLexicallyDeclaredNames`][spec] syntax operation in the spec. +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallydeclarednames +#[must_use] +#[inline] +pub fn top_level_lexically_declared_names(stmts: &StatementList) -> Vec { + let mut names = Vec::new(); + top_level_lexicals(stmts, &mut names); + names +} + +/// Utility function that collects the top level vars of a statement list into `names`. +#[inline] +fn top_level_vars(stmts: &StatementList, names: &mut FxHashSet) { + for stmt in stmts.statements() { + match stmt { + StatementListItem::Declaration(decl) => { + match decl { + // Note + // At the top level of a function, or script, function declarations are treated like + // var declarations rather than like lexical declarations. + Declaration::Function(f) => { + BoundNamesVisitor(names).visit_function(f); + } + Declaration::Generator(f) => { + BoundNamesVisitor(names).visit_generator(f); + } + Declaration::AsyncFunction(f) => { + BoundNamesVisitor(names).visit_async_function(f); + } + Declaration::AsyncGenerator(f) => { + BoundNamesVisitor(names).visit_async_generator(f); + } + Declaration::Class(_) | Declaration::Lexical(_) => {} + } + } + StatementListItem::Statement(stmt) => { + let mut stmt = Some(stmt); + while let Some(Statement::Labelled(labelled)) = stmt { + match labelled.item() { + LabelledItem::Function(f) => { + BoundNamesVisitor(names).visit_function(f); + stmt = None; + } + LabelledItem::Statement(s) => stmt = Some(s), + } + } + if let Some(stmt) = stmt { + VarDeclaredNamesVisitor(names).visit(stmt); + } + } + } + } +} + +/// Returns a list with the var bindings of a top-level statement list, with no duplicates. +/// +/// This is equivalent to the [`TopLevelVarDeclaredNames`][spec] syntax operation in the spec. +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvardeclarednames +#[must_use] +#[inline] +pub fn top_level_var_declared_names(stmts: &StatementList) -> FxHashSet { + let mut names = FxHashSet::default(); + top_level_vars(stmts, &mut names); + names +} diff --git a/boa_ast/src/pattern.rs b/boa_ast/src/pattern.rs index 99af84b5c1..aed259ae88 100644 --- a/boa_ast/src/pattern.rs +++ b/boa_ast/src/pattern.rs @@ -76,20 +76,6 @@ impl ToInternedString for Pattern { } } -impl Pattern { - /// Gets the list of identifiers in the pattern. - /// - /// A single pattern may have 0 to n identifiers. - #[inline] - #[must_use] - pub fn idents(&self) -> Vec { - match &self { - Pattern::Object(pattern) => pattern.idents(), - Pattern::Array(pattern) => pattern.idents(), - } - } -} - impl VisitWith for Pattern { fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where @@ -167,16 +153,6 @@ impl ObjectPattern { &self.0 } - /// Gets the list of identifiers declared by the object binding pattern. - #[inline] - #[must_use] - pub fn idents(&self) -> Vec { - self.0 - .iter() - .flat_map(ObjectPatternElement::idents) - .collect() - } - /// Returns true if the object binding pattern has a rest element. #[inline] #[must_use] @@ -263,15 +239,6 @@ impl ArrayPattern { pub fn bindings(&self) -> &[ArrayPatternElement] { &self.0 } - - /// Gets the list of identifiers declared by the array binding pattern. - #[inline] - pub(crate) fn idents(&self) -> Vec { - self.0 - .iter() - .flat_map(ArrayPatternElement::idents) - .collect() - } } impl VisitWith for ArrayPattern { @@ -394,26 +361,6 @@ pub enum ObjectPatternElement { }, } -impl ObjectPatternElement { - /// Gets the list of identifiers declared by the object binding pattern. - #[inline] - pub(crate) fn idents(&self) -> Vec { - match self { - Self::SingleName { ident, .. } | Self::RestProperty { ident, .. } => { - vec![*ident] - } - Self::AssignmentPropertyAccess { - name: PropertyName::Literal(lit), - .. - } => { - vec![(*lit).into()] - } - Self::Pattern { pattern, .. } => pattern.idents(), - _ => Vec::new(), - } - } -} - impl ToInternedString for ObjectPatternElement { fn to_interned_string(&self, interner: &Interner) -> String { match self { @@ -530,16 +477,7 @@ impl VisitWith for ObjectPatternElement { ControlFlow::Continue(()) } } - ObjectPatternElement::RestProperty { - ident, - excluded_keys, - } => { - try_break!(visitor.visit_identifier(ident)); - for key in excluded_keys { - try_break!(visitor.visit_identifier(key)); - } - ControlFlow::Continue(()) - } + ObjectPatternElement::RestProperty { ident, .. } => visitor.visit_identifier(ident), ObjectPatternElement::AssignmentPropertyAccess { name, access, @@ -553,15 +491,8 @@ impl VisitWith for ObjectPatternElement { ControlFlow::Continue(()) } } - ObjectPatternElement::AssignmentRestPropertyAccess { - access, - excluded_keys, - } => { - try_break!(visitor.visit_property_access(access)); - for key in excluded_keys { - try_break!(visitor.visit_identifier(key)); - } - ControlFlow::Continue(()) + ObjectPatternElement::AssignmentRestPropertyAccess { access, .. } => { + visitor.visit_property_access(access) } ObjectPatternElement::Pattern { name, @@ -597,16 +528,7 @@ impl VisitWith for ObjectPatternElement { ControlFlow::Continue(()) } } - ObjectPatternElement::RestProperty { - ident, - excluded_keys, - } => { - try_break!(visitor.visit_identifier_mut(ident)); - for key in excluded_keys { - try_break!(visitor.visit_identifier_mut(key)); - } - ControlFlow::Continue(()) - } + ObjectPatternElement::RestProperty { ident, .. } => visitor.visit_identifier_mut(ident), ObjectPatternElement::AssignmentPropertyAccess { name, access, @@ -620,15 +542,8 @@ impl VisitWith for ObjectPatternElement { ControlFlow::Continue(()) } } - ObjectPatternElement::AssignmentRestPropertyAccess { - access, - excluded_keys, - } => { - try_break!(visitor.visit_property_access_mut(access)); - for key in excluded_keys { - try_break!(visitor.visit_identifier_mut(key)); - } - ControlFlow::Continue(()) + ObjectPatternElement::AssignmentRestPropertyAccess { access, .. } => { + visitor.visit_property_access_mut(access) } ObjectPatternElement::Pattern { name, @@ -746,21 +661,6 @@ pub enum ArrayPatternElement { }, } -impl ArrayPatternElement { - /// Gets the list of identifiers in the array pattern element. - #[inline] - pub(crate) fn idents(&self) -> Vec { - match self { - Self::SingleName { ident, .. } => { - vec![*ident] - } - Self::Pattern { pattern, .. } | Self::PatternRest { pattern } => pattern.idents(), - Self::SingleNameRest { ident } => vec![*ident], - _ => Vec::new(), - } - } -} - impl ToInternedString for ArrayPatternElement { fn to_interned_string(&self, interner: &Interner) -> String { match self { diff --git a/boa_ast/src/statement/block.rs b/boa_ast/src/statement/block.rs index ef130c1492..37dea28870 100644 --- a/boa_ast/src/statement/block.rs +++ b/boa_ast/src/statement/block.rs @@ -1,7 +1,6 @@ //! Block AST node. use crate::{ - expression::Identifier, visitor::{VisitWith, Visitor, VisitorMut}, Statement, StatementList, }; @@ -37,13 +36,6 @@ impl Block { pub fn statement_list(&self) -> &StatementList { &self.statements } - - /// Get the lexically declared names of the block. - #[inline] - #[must_use] - pub fn lexically_declared_names(&self) -> Vec<(Identifier, bool)> { - self.statements.lexically_declared_names() - } } impl From for Block diff --git a/boa_ast/src/statement/iteration/for_loop.rs b/boa_ast/src/statement/iteration/for_loop.rs index feb99865e1..4c56e3784b 100644 --- a/boa_ast/src/statement/iteration/for_loop.rs +++ b/boa_ast/src/statement/iteration/for_loop.rs @@ -1,8 +1,7 @@ use crate::try_break; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use crate::{ - declaration::{LexicalDeclaration, VarDeclaration, Variable}, - expression::Identifier, + declaration::{LexicalDeclaration, VarDeclaration}, statement::Statement, Expression, }; @@ -205,27 +204,6 @@ pub enum ForLoopInitializer { Lexical(LexicalDeclaration), } -impl ForLoopInitializer { - /// Return the bound names of a for loop initializer. - /// - /// More information: - /// - [ECMAScript specification][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-boundnames - #[must_use] - pub fn bound_names(&self) -> Vec { - match self { - ForLoopInitializer::Lexical(lex) => lex - .variable_list() - .as_ref() - .iter() - .flat_map(Variable::idents) - .collect(), - _ => Vec::new(), - } - } -} - impl ToInternedString for ForLoopInitializer { fn to_interned_string(&self, interner: &Interner) -> String { match self { diff --git a/boa_ast/src/statement/iteration/for_of_loop.rs b/boa_ast/src/statement/iteration/for_of_loop.rs index d2b8966392..c8f3cbfec7 100644 --- a/boa_ast/src/statement/iteration/for_of_loop.rs +++ b/boa_ast/src/statement/iteration/for_of_loop.rs @@ -49,7 +49,7 @@ impl ForOfLoop { /// Gets the initializer of the for...of loop. #[inline] #[must_use] - pub fn init(&self) -> &IterableLoopInitializer { + pub fn initializer(&self) -> &IterableLoopInitializer { &self.init } diff --git a/boa_ast/src/statement/iteration/mod.rs b/boa_ast/src/statement/iteration/mod.rs index f33092957c..78ef2f5e66 100644 --- a/boa_ast/src/statement/iteration/mod.rs +++ b/boa_ast/src/statement/iteration/mod.rs @@ -51,24 +51,6 @@ pub enum IterableLoopInitializer { Pattern(Pattern), } -impl IterableLoopInitializer { - /// Return the bound names of a for loop initializer. - /// - /// The returned list may contain duplicates. - /// - /// More information: - /// - [ECMAScript specification][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-boundnames - #[must_use] - pub fn bound_names(&self) -> Vec { - match self { - Self::Let(binding) | Self::Const(binding) => binding.idents(), - _ => Vec::new(), - } - } -} - impl ToInternedString for IterableLoopInitializer { fn to_interned_string(&self, interner: &Interner) -> String { let (binding, pre) = match self { diff --git a/boa_ast/src/statement/mod.rs b/boa_ast/src/statement/mod.rs index 2f040e0cd0..1978221e5d 100644 --- a/boa_ast/src/statement/mod.rs +++ b/boa_ast/src/statement/mod.rs @@ -17,7 +17,6 @@ mod r#try; pub mod iteration; -use self::iteration::{ForLoopInitializer, IterableLoopInitializer}; pub use self::{ block::Block, iteration::{Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop}, @@ -32,12 +31,8 @@ use core::ops::ControlFlow; use crate::visitor::{VisitWith, Visitor, VisitorMut}; use boa_interner::{Interner, ToIndentedString, ToInternedString}; -use rustc_hash::FxHashSet; -use super::{ - declaration::{Binding, VarDeclaration}, - expression::{Expression, Identifier}, -}; +use super::{declaration::VarDeclaration, expression::Expression}; /// The `Statement` Parse Node. /// @@ -138,93 +133,6 @@ impl Statement { s } - /// Gets the var declared names of this `Statement`. - pub fn var_declared_names(&self, vars: &mut FxHashSet) { - match self { - Self::Var(VarDeclaration(list)) => { - for decl in list.as_ref() { - vars.extend(decl.idents()); - } - } - Self::Block(block) => { - for node in block.statement_list().statements() { - node.var_declared_names(vars); - } - } - Self::If(if_statement) => { - if_statement.body().var_declared_names(vars); - if let Some(node) = if_statement.else_node() { - node.var_declared_names(vars); - } - } - Self::DoWhileLoop(do_while_loop) => { - do_while_loop.body().var_declared_names(vars); - } - Self::WhileLoop(while_loop) => { - while_loop.body().var_declared_names(vars); - } - Self::ForLoop(for_loop) => { - if let Some(ForLoopInitializer::Var(VarDeclaration(list))) = for_loop.init() { - for variable in list.as_ref() { - match variable.binding() { - Binding::Identifier(ident) => { - vars.insert(*ident); - } - Binding::Pattern(pattern) => { - for ident in pattern.idents() { - vars.insert(ident); - } - } - } - } - } - for_loop.body().var_declared_names(vars); - } - Self::ForInLoop(for_in_loop) => { - if let IterableLoopInitializer::Var(bind) = for_in_loop.initializer() { - vars.extend(bind.idents()); - } - for_in_loop.body().var_declared_names(vars); - } - Self::ForOfLoop(for_of_loop) => { - if let IterableLoopInitializer::Var(bind) = for_of_loop.init() { - vars.extend(bind.idents()); - } - for_of_loop.body().var_declared_names(vars); - } - Self::Switch(switch) => { - for case in switch.cases() { - for node in case.body().statements() { - node.var_declared_names(vars); - } - } - if let Some(stmts) = switch.default() { - stmts.var_declared_names(vars); - } - } - Self::Try(try_statement) => { - for node in try_statement.block().statement_list().statements() { - node.var_declared_names(vars); - } - if let Some(catch) = try_statement.catch() { - for node in catch.block().statement_list().statements() { - node.var_declared_names(vars); - } - } - if let Some(finally) = try_statement.finally() { - for node in finally.block().statement_list().statements() { - node.var_declared_names(vars); - } - } - } - Self::Labelled(labelled) => match labelled.item() { - LabelledItem::Function(_) => {} - LabelledItem::Statement(stmt) => stmt.var_declared_names(vars), - }, - _ => {} - } - } - /// Abstract operation [`IsLabelledFunction`][spec]. /// /// This recursively checks if this `Statement` is a labelled function, since adding diff --git a/boa_ast/src/statement_list.rs b/boa_ast/src/statement_list.rs index ebc1503b30..f035ecaf69 100644 --- a/boa_ast/src/statement_list.rs +++ b/boa_ast/src/statement_list.rs @@ -1,15 +1,14 @@ //! Statement list node. -use super::{declaration::Binding, Declaration}; +use super::Declaration; use crate::{ - expression::Identifier, statement::Statement, try_break, visitor::{VisitWith, Visitor, VisitorMut}, }; use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; -use rustc_hash::FxHashSet; + use std::cmp::Ordering; /// An item inside a [`StatementList`] Parse Node, as defined by the [spec]. @@ -42,15 +41,6 @@ impl StatementListItem { (_, _) => Ordering::Equal, } } - - /// Gets the var declared names of this `StatementListItem`. - #[inline] - pub fn var_declared_names(&self, vars: &mut FxHashSet) { - match self { - StatementListItem::Statement(stmt) => stmt.var_declared_names(vars), - StatementListItem::Declaration(_) => {} - } - } } impl ToIndentedString for StatementListItem { @@ -151,80 +141,6 @@ impl StatementList { pub fn set_strict(&mut self, strict: bool) { self.strict = strict; } - - /// Returns the var declared names of a `StatementList`. - #[inline] - pub fn var_declared_names(&self, vars: &mut FxHashSet) { - for stmt in &*self.statements { - stmt.var_declared_names(vars); - } - } - - /// Returns the lexically declared names of a `StatementList`. - /// - /// The returned list may contain duplicates. - /// - /// If a declared name originates from a function declaration it is flagged as `true` in the returned list. - /// - /// More information: - /// - [ECMAScript specification][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallydeclarednames - #[must_use] - pub fn lexically_declared_names(&self) -> Vec<(Identifier, bool)> { - let mut names = Vec::new(); - - for node in self.statements() { - match node { - StatementListItem::Statement(_) => {} - StatementListItem::Declaration(decl) => { - names.extend(decl.lexically_declared_names()); - } - } - } - - names - } - - /// Return the top level lexically declared names of a `StatementList`. - /// - /// The returned list may contain duplicates. - /// - /// More information: - /// - [ECMAScript specification][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevellexicallydeclarednames - #[must_use] - pub fn lexically_declared_names_top_level(&self) -> Vec { - let mut names = Vec::new(); - - for node in self.statements() { - if let StatementListItem::Declaration(decl) = node { - match decl { - Declaration::Class(decl) => { - if let Some(name) = decl.name() { - names.push(name); - } - } - Declaration::Lexical(list) => { - for variable in list.variable_list().as_ref() { - match variable.binding() { - Binding::Identifier(ident) => { - names.push(*ident); - } - Binding::Pattern(pattern) => { - names.extend(pattern.idents()); - } - } - } - } - _ => {} - } - } - } - - names - } } impl From> for StatementList { diff --git a/boa_ast/src/visitor.rs b/boa_ast/src/visitor.rs index f997b3a131..5cf62e2b5c 100644 --- a/boa_ast/src/visitor.rs +++ b/boa_ast/src/visitor.rs @@ -3,6 +3,43 @@ //! This module contains visitors which can be used to inspect or modify AST nodes. This allows for //! fine-grained manipulation of ASTs for analysis, rewriting, or instrumentation. +use std::ops::ControlFlow; + +use crate::{ + declaration::{ + Binding, Declaration, LexicalDeclaration, VarDeclaration, Variable, VariableList, + }, + expression::{ + access::{ + PrivatePropertyAccess, PropertyAccess, PropertyAccessField, SimplePropertyAccess, + SuperPropertyAccess, + }, + literal::{ArrayLiteral, Literal, ObjectLiteral, TemplateElement, TemplateLiteral}, + operator::{ + assign::{Assign, AssignTarget}, + Binary, Conditional, Unary, + }, + Await, Call, Expression, Identifier, New, Optional, OptionalOperation, + OptionalOperationKind, Spread, SuperCall, TaggedTemplate, Yield, + }, + function::{ + ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, + FormalParameter, FormalParameterList, Function, Generator, + }, + pattern::{ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern}, + property::{MethodDefinition, PropertyDefinition, PropertyName}, + statement::{ + iteration::{ + Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForLoopInitializer, ForOfLoop, + IterableLoopInitializer, WhileLoop, + }, + Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw, + Try, + }, + StatementList, StatementListItem, +}; +use boa_interner::Sym; + /// `Try`-like conditional unwrapping of `ControlFlow`. #[macro_export] macro_rules! try_break { @@ -14,46 +51,11 @@ macro_rules! try_break { }; } -use crate::declaration::{ - Binding, Declaration, LexicalDeclaration, VarDeclaration, Variable, VariableList, -}; -use crate::expression::access::{ - PrivatePropertyAccess, PropertyAccess, PropertyAccessField, SimplePropertyAccess, - SuperPropertyAccess, -}; -use crate::expression::literal::{ - ArrayLiteral, Literal, ObjectLiteral, TemplateElement, TemplateLiteral, -}; -use crate::expression::operator::assign::{Assign, AssignTarget}; -use crate::expression::operator::{Binary, Conditional, Unary}; -use crate::expression::{ - Await, Call, Expression, Identifier, New, Optional, OptionalOperation, OptionalOperationKind, - Spread, SuperCall, TaggedTemplate, Yield, -}; -use crate::function::{ - ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, - FormalParameter, FormalParameterList, Function, Generator, -}; -use crate::pattern::{ - ArrayPattern, ArrayPatternElement, ObjectPattern, ObjectPatternElement, Pattern, -}; -use crate::property::{MethodDefinition, PropertyDefinition, PropertyName}; -use crate::statement::iteration::{ - Break, Continue, DoWhileLoop, ForInLoop, ForLoop, ForLoopInitializer, ForOfLoop, - IterableLoopInitializer, WhileLoop, -}; -use crate::statement::{ - Block, Case, Catch, Finally, If, Labelled, LabelledItem, Return, Statement, Switch, Throw, Try, -}; -use crate::{StatementList, StatementListItem}; -use boa_interner::Sym; - /// Creates the default visit function implementation for a particular type macro_rules! define_visit { ($fn_name:ident, $type_name:ident) => { #[doc = concat!("Visits a `", stringify!($type_name), "` with this visitor")] - #[must_use] - fn $fn_name(&mut self, node: &'ast $type_name) -> core::ops::ControlFlow { + fn $fn_name(&mut self, node: &'ast $type_name) -> ControlFlow { node.visit_with(self) } }; @@ -63,16 +65,134 @@ macro_rules! define_visit { macro_rules! define_visit_mut { ($fn_name:ident, $type_name:ident) => { #[doc = concat!("Visits a `", stringify!($type_name), "` with this visitor, mutably")] - #[must_use] - fn $fn_name( - &mut self, - node: &'ast mut $type_name, - ) -> core::ops::ControlFlow { + fn $fn_name(&mut self, node: &'ast mut $type_name) -> ControlFlow { node.visit_with_mut(self) } }; } +/// Generates the `NodeRef` and `NodeMutRef` enums from a list of variants. +macro_rules! node_ref { + ( + $( + $Variant:ident + ),* + $(,)? + ) => { + /// A reference to a node visitable by a [`Visitor`]. + #[derive(Debug, Clone, Copy)] + #[allow(missing_docs)] + pub enum NodeRef<'a> { + $( + $Variant(&'a $Variant) + ),* + } + + $( + impl<'a> From<&'a $Variant> for NodeRef<'a> { + fn from(node: &'a $Variant) -> NodeRef<'a> { + Self::$Variant(node) + } + } + )* + + /// A mutable reference to a node visitable by a [`VisitorMut`]. + #[derive(Debug)] + #[allow(missing_docs)] + pub enum NodeRefMut<'a> { + $( + $Variant(&'a mut $Variant) + ),* + } + + $( + impl<'a> From<&'a mut $Variant> for NodeRefMut<'a> { + fn from(node: &'a mut $Variant) -> NodeRefMut<'a> { + Self::$Variant(node) + } + } + )* + } +} + +node_ref! { + StatementList, + StatementListItem, + Statement, + Declaration, + Function, + Generator, + AsyncFunction, + AsyncGenerator, + Class, + LexicalDeclaration, + Block, + VarDeclaration, + Expression, + If, + DoWhileLoop, + WhileLoop, + ForLoop, + ForInLoop, + ForOfLoop, + Switch, + Continue, + Break, + Return, + Labelled, + Throw, + Try, + Identifier, + FormalParameterList, + ClassElement, + VariableList, + Variable, + Binding, + Pattern, + Literal, + ArrayLiteral, + ObjectLiteral, + Spread, + ArrowFunction, + AsyncArrowFunction, + TemplateLiteral, + PropertyAccess, + New, + Call, + SuperCall, + Optional, + TaggedTemplate, + Assign, + Unary, + Binary, + Conditional, + Await, + Yield, + ForLoopInitializer, + IterableLoopInitializer, + Case, + Sym, + LabelledItem, + Catch, + Finally, + FormalParameter, + PropertyName, + MethodDefinition, + ObjectPattern, + ArrayPattern, + PropertyDefinition, + TemplateElement, + SimplePropertyAccess, + PrivatePropertyAccess, + SuperPropertyAccess, + OptionalOperation, + AssignTarget, + ObjectPatternElement, + ArrayPatternElement, + PropertyAccessField, + OptionalOperationKind, +} + /// Represents an AST visitor. /// /// This implementation is based largely on [chalk](https://github.com/rust-lang/chalk/blob/23d39c90ceb9242fbd4c43e9368e813e7c2179f7/chalk-ir/src/visit.rs)'s @@ -156,6 +276,90 @@ pub trait Visitor<'ast>: Sized { define_visit!(visit_array_pattern_element, ArrayPatternElement); define_visit!(visit_property_access_field, PropertyAccessField); define_visit!(visit_optional_operation_kind, OptionalOperationKind); + + /// Generic entry point for a node that is visitable by a `Visitor`. + /// + /// This is usually used for generic functions that need to visit an unnamed AST node. + fn visit>>(&mut self, node: N) -> ControlFlow { + let node = node.into(); + match node { + NodeRef::StatementList(n) => self.visit_statement_list(n), + NodeRef::StatementListItem(n) => self.visit_statement_list_item(n), + NodeRef::Statement(n) => self.visit_statement(n), + NodeRef::Declaration(n) => self.visit_declaration(n), + NodeRef::Function(n) => self.visit_function(n), + NodeRef::Generator(n) => self.visit_generator(n), + NodeRef::AsyncFunction(n) => self.visit_async_function(n), + NodeRef::AsyncGenerator(n) => self.visit_async_generator(n), + NodeRef::Class(n) => self.visit_class(n), + NodeRef::LexicalDeclaration(n) => self.visit_lexical_declaration(n), + NodeRef::Block(n) => self.visit_block(n), + NodeRef::VarDeclaration(n) => self.visit_var_declaration(n), + NodeRef::Expression(n) => self.visit_expression(n), + NodeRef::If(n) => self.visit_if(n), + NodeRef::DoWhileLoop(n) => self.visit_do_while_loop(n), + NodeRef::WhileLoop(n) => self.visit_while_loop(n), + NodeRef::ForLoop(n) => self.visit_for_loop(n), + NodeRef::ForInLoop(n) => self.visit_for_in_loop(n), + NodeRef::ForOfLoop(n) => self.visit_for_of_loop(n), + NodeRef::Switch(n) => self.visit_switch(n), + NodeRef::Continue(n) => self.visit_continue(n), + NodeRef::Break(n) => self.visit_break(n), + NodeRef::Return(n) => self.visit_return(n), + NodeRef::Labelled(n) => self.visit_labelled(n), + NodeRef::Throw(n) => self.visit_throw(n), + NodeRef::Try(n) => self.visit_try(n), + NodeRef::Identifier(n) => self.visit_identifier(n), + NodeRef::FormalParameterList(n) => self.visit_formal_parameter_list(n), + NodeRef::ClassElement(n) => self.visit_class_element(n), + NodeRef::VariableList(n) => self.visit_variable_list(n), + NodeRef::Variable(n) => self.visit_variable(n), + NodeRef::Binding(n) => self.visit_binding(n), + NodeRef::Pattern(n) => self.visit_pattern(n), + NodeRef::Literal(n) => self.visit_literal(n), + NodeRef::ArrayLiteral(n) => self.visit_array_literal(n), + NodeRef::ObjectLiteral(n) => self.visit_object_literal(n), + NodeRef::Spread(n) => self.visit_spread(n), + NodeRef::ArrowFunction(n) => self.visit_arrow_function(n), + NodeRef::AsyncArrowFunction(n) => self.visit_async_arrow_function(n), + NodeRef::TemplateLiteral(n) => self.visit_template_literal(n), + NodeRef::PropertyAccess(n) => self.visit_property_access(n), + NodeRef::New(n) => self.visit_new(n), + NodeRef::Call(n) => self.visit_call(n), + NodeRef::SuperCall(n) => self.visit_super_call(n), + NodeRef::Optional(n) => self.visit_optional(n), + NodeRef::TaggedTemplate(n) => self.visit_tagged_template(n), + NodeRef::Assign(n) => self.visit_assign(n), + NodeRef::Unary(n) => self.visit_unary(n), + NodeRef::Binary(n) => self.visit_binary(n), + NodeRef::Conditional(n) => self.visit_conditional(n), + NodeRef::Await(n) => self.visit_await(n), + NodeRef::Yield(n) => self.visit_yield(n), + NodeRef::ForLoopInitializer(n) => self.visit_for_loop_initializer(n), + NodeRef::IterableLoopInitializer(n) => self.visit_iterable_loop_initializer(n), + NodeRef::Case(n) => self.visit_case(n), + NodeRef::Sym(n) => self.visit_sym(n), + NodeRef::LabelledItem(n) => self.visit_labelled_item(n), + NodeRef::Catch(n) => self.visit_catch(n), + NodeRef::Finally(n) => self.visit_finally(n), + NodeRef::FormalParameter(n) => self.visit_formal_parameter(n), + NodeRef::PropertyName(n) => self.visit_property_name(n), + NodeRef::MethodDefinition(n) => self.visit_method_definition(n), + NodeRef::ObjectPattern(n) => self.visit_object_pattern(n), + NodeRef::ArrayPattern(n) => self.visit_array_pattern(n), + NodeRef::PropertyDefinition(n) => self.visit_property_definition(n), + NodeRef::TemplateElement(n) => self.visit_template_element(n), + NodeRef::SimplePropertyAccess(n) => self.visit_simple_property_access(n), + NodeRef::PrivatePropertyAccess(n) => self.visit_private_property_access(n), + NodeRef::SuperPropertyAccess(n) => self.visit_super_property_access(n), + NodeRef::OptionalOperation(n) => self.visit_optional_operation(n), + NodeRef::AssignTarget(n) => self.visit_assign_target(n), + NodeRef::ObjectPatternElement(n) => self.visit_object_pattern_element(n), + NodeRef::ArrayPatternElement(n) => self.visit_array_pattern_element(n), + NodeRef::PropertyAccessField(n) => self.visit_property_access_field(n), + NodeRef::OptionalOperationKind(n) => self.visit_optional_operation_kind(n), + } + } } /// Represents an AST visitor which can modify AST content. @@ -241,33 +445,117 @@ pub trait VisitorMut<'ast>: Sized { define_visit_mut!(visit_array_pattern_element_mut, ArrayPatternElement); define_visit_mut!(visit_property_access_field_mut, PropertyAccessField); define_visit_mut!(visit_optional_operation_kind_mut, OptionalOperationKind); + + /// Generic entry point for a node that is visitable by a `VisitorMut`. + /// + /// This is usually used for generic functions that need to visit an unnamed AST node. + fn visit>>(&mut self, node: N) -> ControlFlow { + let node = node.into(); + match node { + NodeRefMut::StatementList(n) => self.visit_statement_list_mut(n), + NodeRefMut::StatementListItem(n) => self.visit_statement_list_item_mut(n), + NodeRefMut::Statement(n) => self.visit_statement_mut(n), + NodeRefMut::Declaration(n) => self.visit_declaration_mut(n), + NodeRefMut::Function(n) => self.visit_function_mut(n), + NodeRefMut::Generator(n) => self.visit_generator_mut(n), + NodeRefMut::AsyncFunction(n) => self.visit_async_function_mut(n), + NodeRefMut::AsyncGenerator(n) => self.visit_async_generator_mut(n), + NodeRefMut::Class(n) => self.visit_class_mut(n), + NodeRefMut::LexicalDeclaration(n) => self.visit_lexical_declaration_mut(n), + NodeRefMut::Block(n) => self.visit_block_mut(n), + NodeRefMut::VarDeclaration(n) => self.visit_var_declaration_mut(n), + NodeRefMut::Expression(n) => self.visit_expression_mut(n), + NodeRefMut::If(n) => self.visit_if_mut(n), + NodeRefMut::DoWhileLoop(n) => self.visit_do_while_loop_mut(n), + NodeRefMut::WhileLoop(n) => self.visit_while_loop_mut(n), + NodeRefMut::ForLoop(n) => self.visit_for_loop_mut(n), + NodeRefMut::ForInLoop(n) => self.visit_for_in_loop_mut(n), + NodeRefMut::ForOfLoop(n) => self.visit_for_of_loop_mut(n), + NodeRefMut::Switch(n) => self.visit_switch_mut(n), + NodeRefMut::Continue(n) => self.visit_continue_mut(n), + NodeRefMut::Break(n) => self.visit_break_mut(n), + NodeRefMut::Return(n) => self.visit_return_mut(n), + NodeRefMut::Labelled(n) => self.visit_labelled_mut(n), + NodeRefMut::Throw(n) => self.visit_throw_mut(n), + NodeRefMut::Try(n) => self.visit_try_mut(n), + NodeRefMut::Identifier(n) => self.visit_identifier_mut(n), + NodeRefMut::FormalParameterList(n) => self.visit_formal_parameter_list_mut(n), + NodeRefMut::ClassElement(n) => self.visit_class_element_mut(n), + NodeRefMut::VariableList(n) => self.visit_variable_list_mut(n), + NodeRefMut::Variable(n) => self.visit_variable_mut(n), + NodeRefMut::Binding(n) => self.visit_binding_mut(n), + NodeRefMut::Pattern(n) => self.visit_pattern_mut(n), + NodeRefMut::Literal(n) => self.visit_literal_mut(n), + NodeRefMut::ArrayLiteral(n) => self.visit_array_literal_mut(n), + NodeRefMut::ObjectLiteral(n) => self.visit_object_literal_mut(n), + NodeRefMut::Spread(n) => self.visit_spread_mut(n), + NodeRefMut::ArrowFunction(n) => self.visit_arrow_function_mut(n), + NodeRefMut::AsyncArrowFunction(n) => self.visit_async_arrow_function_mut(n), + NodeRefMut::TemplateLiteral(n) => self.visit_template_literal_mut(n), + NodeRefMut::PropertyAccess(n) => self.visit_property_access_mut(n), + NodeRefMut::New(n) => self.visit_new_mut(n), + NodeRefMut::Call(n) => self.visit_call_mut(n), + NodeRefMut::SuperCall(n) => self.visit_super_call_mut(n), + NodeRefMut::Optional(n) => self.visit_optional_mut(n), + NodeRefMut::TaggedTemplate(n) => self.visit_tagged_template_mut(n), + NodeRefMut::Assign(n) => self.visit_assign_mut(n), + NodeRefMut::Unary(n) => self.visit_unary_mut(n), + NodeRefMut::Binary(n) => self.visit_binary_mut(n), + NodeRefMut::Conditional(n) => self.visit_conditional_mut(n), + NodeRefMut::Await(n) => self.visit_await_mut(n), + NodeRefMut::Yield(n) => self.visit_yield_mut(n), + NodeRefMut::ForLoopInitializer(n) => self.visit_for_loop_initializer_mut(n), + NodeRefMut::IterableLoopInitializer(n) => self.visit_iterable_loop_initializer_mut(n), + NodeRefMut::Case(n) => self.visit_case_mut(n), + NodeRefMut::Sym(n) => self.visit_sym_mut(n), + NodeRefMut::LabelledItem(n) => self.visit_labelled_item_mut(n), + NodeRefMut::Catch(n) => self.visit_catch_mut(n), + NodeRefMut::Finally(n) => self.visit_finally_mut(n), + NodeRefMut::FormalParameter(n) => self.visit_formal_parameter_mut(n), + NodeRefMut::PropertyName(n) => self.visit_property_name_mut(n), + NodeRefMut::MethodDefinition(n) => self.visit_method_definition_mut(n), + NodeRefMut::ObjectPattern(n) => self.visit_object_pattern_mut(n), + NodeRefMut::ArrayPattern(n) => self.visit_array_pattern_mut(n), + NodeRefMut::PropertyDefinition(n) => self.visit_property_definition_mut(n), + NodeRefMut::TemplateElement(n) => self.visit_template_element_mut(n), + NodeRefMut::SimplePropertyAccess(n) => self.visit_simple_property_access_mut(n), + NodeRefMut::PrivatePropertyAccess(n) => self.visit_private_property_access_mut(n), + NodeRefMut::SuperPropertyAccess(n) => self.visit_super_property_access_mut(n), + NodeRefMut::OptionalOperation(n) => self.visit_optional_operation_mut(n), + NodeRefMut::AssignTarget(n) => self.visit_assign_target_mut(n), + NodeRefMut::ObjectPatternElement(n) => self.visit_object_pattern_element_mut(n), + NodeRefMut::ArrayPatternElement(n) => self.visit_array_pattern_element_mut(n), + NodeRefMut::PropertyAccessField(n) => self.visit_property_access_field_mut(n), + NodeRefMut::OptionalOperationKind(n) => self.visit_optional_operation_kind_mut(n), + } + } } /// Denotes that a type may be visited, providing a method which allows a visitor to traverse its /// private fields. pub trait VisitWith { /// Visit this node with the provided visitor. - fn visit_with<'a, V>(&'a self, visitor: &mut V) -> core::ops::ControlFlow + fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow where V: Visitor<'a>; /// Visit this node with the provided visitor mutably, allowing the visitor to modify private /// fields. - fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> core::ops::ControlFlow + fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>; } // implementation for Sym as it is out-of-crate impl VisitWith for Sym { - fn visit_with<'a, V>(&'a self, _visitor: &mut V) -> core::ops::ControlFlow + fn visit_with<'a, V>(&'a self, _visitor: &mut V) -> ControlFlow where V: Visitor<'a>, { core::ops::ControlFlow::Continue(()) } - fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> core::ops::ControlFlow + fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> ControlFlow where V: VisitorMut<'a>, { diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index ae361bfcd2..7bc8514014 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -17,9 +17,9 @@ use crate::{ property::Attribute, Context, JsResult, JsValue, }; +use boa_ast::operations::top_level_var_declared_names; use boa_gc::Gc; use boa_profiler::Profiler; -use rustc_hash::FxHashSet; #[derive(Debug, Clone, Copy)] pub(crate) struct Eval; @@ -68,11 +68,28 @@ impl Eval { mut strict: bool, context: &mut Context, ) -> JsResult { + /// Possible actions that can be executed after exiting this function to restore the environment to its + /// original state. + #[derive(Debug)] enum EnvStackAction { Truncate(usize), Restore(Vec>), } + /// Restores the environment after calling `eval` or after throwing an error. + fn restore_environment(context: &mut Context, action: EnvStackAction) { + match action { + EnvStackAction::Truncate(size) => { + context.realm.environments.truncate(size); + } + EnvStackAction::Restore(envs) => { + // Pop all environments created during the eval execution and restore the original stack. + context.realm.environments.truncate(1); + context.realm.environments.extend(envs); + } + } + } + // 1. Assert: If direct is false, then strictCaller is also false. debug_assert!(direct || !strict); @@ -129,13 +146,12 @@ impl Eval { // environment for all eval calls. if !strict { // Error if any var declaration in the eval code already exists as a let/const declaration in the current running environment. - let mut vars = FxHashSet::default(); - body.var_declared_names(&mut vars); if let Some(name) = context .realm .environments - .has_lex_binding_until_function_environment(&vars) + .has_lex_binding_until_function_environment(&top_level_var_declared_names(&body)) { + restore_environment(context, action); let name = context.interner().resolve_expect(name.sym()); let msg = format!("variable declaration {name} in eval function already exists as a lexical variable"); return Err(JsNativeError::syntax().with_message(msg).into()); @@ -158,16 +174,7 @@ impl Eval { } let result = context.execute(code_block); - match action { - EnvStackAction::Truncate(size) => { - context.realm.environments.truncate(size); - } - EnvStackAction::Restore(envs) => { - // Pop all environments created during the eval execution and restore the original stack. - context.realm.environments.truncate(1); - context.realm.environments.extend(envs); - } - } + restore_environment(context, action); result } diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index 4532eaaaa8..d84f6a3861 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -5,7 +5,7 @@ use crate::{ symbol::{self, WellKnownSymbols}, Context, JsValue, }; -use boa_ast::function::FormalParameterList; +use boa_ast::{function::FormalParameterList, operations::bound_names}; use boa_gc::{Finalize, Gc, Trace}; use rustc_hash::FxHashMap; @@ -199,18 +199,16 @@ impl Arguments { let mut bindings = FxHashMap::default(); let mut property_index = 0; - 'outer: for formal in formals.as_ref() { - for name in formal.names() { - if property_index >= len { - break 'outer; - } - let binding_index = bindings.len() + 1; - let entry = bindings - .entry(name) - .or_insert((binding_index, property_index)); - entry.1 = property_index; - property_index += 1; + for name in bound_names(formals) { + if property_index >= len { + break; } + let binding_index = bindings.len() + 1; + let entry = bindings + .entry(name) + .or_insert((binding_index, property_index)); + entry.1 = property_index; + property_index += 1; } let mut map = ParameterMap { diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index c414169616..dc8591b295 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -32,7 +32,7 @@ use crate::{ }; use boa_ast::{ function::FormalParameterList, - operations::{contains, ContainsSymbol}, + operations::{bound_names, contains, lexically_declared_names, ContainsSymbol}, StatementList, }; use boa_gc::{self, custom_trace, Finalize, Gc, Trace}; @@ -573,13 +573,11 @@ impl BuiltInFunctionObject { // Early Error: If BindingIdentifier is present and the source text matched by BindingIdentifier is strict mode code, // it is a Syntax Error if the StringValue of BindingIdentifier is "eval" or "arguments". if body.strict() { - for parameter in parameters.as_ref() { - for name in parameter.names() { - if name == Sym::ARGUMENTS || name == Sym::EVAL { - return Err(JsNativeError::syntax() - .with_message(" Unexpected 'eval' or 'arguments' in strict mode") - .into()); - } + for name in bound_names(¶meters) { + if name == Sym::ARGUMENTS || name == Sym::EVAL { + return Err(JsNativeError::syntax() + .with_message(" Unexpected 'eval' or 'arguments' in strict mode") + .into()); } } } @@ -606,20 +604,15 @@ impl BuiltInFunctionObject { // also occurs in the LexicallyDeclaredNames of FunctionBody. // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors { - let lexically_declared_names = body.lexically_declared_names(); - for param in parameters.as_ref() { - for param_name in param.names() { - if lexically_declared_names - .iter() - .any(|(name, _)| *name == param_name) - { - return Err(JsNativeError::syntax() - .with_message(format!( - "Redeclaration of formal parameter `{}`", - context.interner().resolve_expect(param_name.sym()) - )) - .into()); - } + let lexically_declared_names = lexically_declared_names(&body); + for name in bound_names(¶meters) { + if lexically_declared_names.contains(&name) { + return Err(JsNativeError::syntax() + .with_message(format!( + "Redeclaration of formal parameter `{}`", + context.interner().resolve_expect(name.sym()) + )) + .into()); } } } diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs index 3690c13e61..815ec30952 100644 --- a/boa_engine/src/bytecompiler/function.rs +++ b/boa_engine/src/bytecompiler/function.rs @@ -4,7 +4,9 @@ use crate::{ vm::{BindingOpcode, CodeBlock, Opcode}, Context, JsResult, }; -use boa_ast::{declaration::Binding, function::FormalParameterList, StatementList}; +use boa_ast::{ + declaration::Binding, function::FormalParameterList, operations::bound_names, StatementList, +}; use boa_gc::Gc; use boa_interner::Sym; use rustc_hash::FxHashMap; @@ -153,7 +155,7 @@ impl FunctionCompiler { compiler.emit_binding(BindingOpcode::InitArg, *ident); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { compiler.context.create_mutable_binding(ident, false, false); } // TODO: throw custom error if ident is in init diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 245b332063..081a6b2259 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -21,6 +21,7 @@ use boa_ast::{ ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, FormalParameterList, Function, Generator, }, + operations::bound_names, pattern::{ArrayPatternElement, ObjectPatternElement, Pattern}, property::{MethodDefinition, PropertyDefinition, PropertyName}, statement::{ @@ -1920,7 +1921,7 @@ impl<'b> ByteCompiler<'b> { label: Option, configurable_globals: bool, ) -> JsResult<()> { - let init_bound_names = for_in_loop.initializer().bound_names(); + let init_bound_names = bound_names(for_in_loop.initializer()); if init_bound_names.is_empty() { self.compile_expr(for_in_loop.target(), true)?; } else { @@ -1971,7 +1972,7 @@ impl<'b> ByteCompiler<'b> { self.emit_binding(BindingOpcode::InitVar, *ident); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { self.context.create_mutable_binding(ident, true, false); } self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?; @@ -1983,7 +1984,7 @@ impl<'b> ByteCompiler<'b> { self.emit_binding(BindingOpcode::InitLet, *ident); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { self.context.create_mutable_binding(ident, false, false); } self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?; @@ -1995,14 +1996,14 @@ impl<'b> ByteCompiler<'b> { self.emit_binding(BindingOpcode::InitConst, *ident); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { self.context.create_immutable_binding(ident, true); } self.compile_declaration_pattern(pattern, BindingOpcode::InitConst)?; } }, IterableLoopInitializer::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { self.context.create_mutable_binding(ident, true, true); } self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?; @@ -2035,7 +2036,7 @@ impl<'b> ByteCompiler<'b> { label: Option, configurable_globals: bool, ) -> JsResult<()> { - let init_bound_names = for_of_loop.init().bound_names(); + let init_bound_names = bound_names(for_of_loop.initializer()); if init_bound_names.is_empty() { self.compile_expr(for_of_loop.iterable(), true)?; } else { @@ -2077,7 +2078,7 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode_with_operand(Opcode::ForInLoopNext) }; - match for_of_loop.init() { + match for_of_loop.initializer() { IterableLoopInitializer::Identifier(ref ident) => { self.context.create_mutable_binding(*ident, true, true); let binding = self.context.set_mutable_binding(*ident); @@ -2097,7 +2098,7 @@ impl<'b> ByteCompiler<'b> { self.emit_binding(BindingOpcode::InitVar, *ident); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { self.context.create_mutable_binding(ident, true, false); } self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?; @@ -2109,7 +2110,7 @@ impl<'b> ByteCompiler<'b> { self.emit_binding(BindingOpcode::InitLet, *ident); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { self.context.create_mutable_binding(ident, false, false); } self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?; @@ -2121,14 +2122,14 @@ impl<'b> ByteCompiler<'b> { self.emit_binding(BindingOpcode::InitConst, *ident); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { self.context.create_immutable_binding(ident, true); } self.compile_declaration_pattern(pattern, BindingOpcode::InitConst)?; } }, IterableLoopInitializer::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { self.context.create_mutable_binding(ident, true, true); } self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?; @@ -2566,7 +2567,7 @@ impl<'b> ByteCompiler<'b> { self.emit_binding(BindingOpcode::InitLet, *ident); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { self.context.create_mutable_binding(ident, false, false); } self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?; @@ -3052,7 +3053,7 @@ impl<'b> ByteCompiler<'b> { .create_mutable_binding(*ident, true, configurable_globals); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { if ident == Sym::ARGUMENTS { has_identifier_argument = true; } @@ -3079,7 +3080,7 @@ impl<'b> ByteCompiler<'b> { self.context.create_mutable_binding(*ident, false, false); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { if ident == Sym::ARGUMENTS { has_identifier_argument = true; } @@ -3100,7 +3101,7 @@ impl<'b> ByteCompiler<'b> { self.context.create_immutable_binding(*ident, true); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { if ident == Sym::ARGUMENTS { has_identifier_argument = true; } @@ -3258,7 +3259,7 @@ impl<'b> ByteCompiler<'b> { compiler.emit_binding(BindingOpcode::InitArg, *ident); } Binding::Pattern(pattern) => { - for ident in pattern.idents() { + for ident in bound_names(pattern) { compiler.context.create_mutable_binding(ident, false, false); } compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg)?; 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 d0885aa825..0353779154 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs +++ b/boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs @@ -17,6 +17,7 @@ use crate::syntax::{ name_in_lexically_declared_names, AllowAwait, AllowIn, AllowYield, Cursor, TokenParser, }, }; +use ast::operations::{bound_names, top_level_lexically_declared_names}; use boa_ast::{ self as ast, declaration::Variable, @@ -150,14 +151,14 @@ where "Illegal 'use strict' directive in function with non-simple parameter list".into(), params_start_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 name_in_lexically_declared_names( - ¶ms, - &body.lexically_declared_names_top_level(), + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), params_start_position, )?; diff --git a/boa_engine/src/syntax/parser/expression/assignment/async_arrow_function.rs b/boa_engine/src/syntax/parser/expression/assignment/async_arrow_function.rs index f231753041..d81238b0ef 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/async_arrow_function.rs +++ b/boa_engine/src/syntax/parser/expression/assignment/async_arrow_function.rs @@ -18,7 +18,7 @@ use crate::syntax::{ }, }; use ast::{ - operations::{contains, ContainsSymbol}, + operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, Keyword, }; use boa_ast::{ @@ -144,8 +144,8 @@ where // Early Error: It is a Syntax Error if any element of the BoundNames of CoverCallExpressionAndAsyncArrowHead // also occurs in the LexicallyDeclaredNames of AsyncConciseBody. name_in_lexically_declared_names( - ¶ms, - &body.lexically_declared_names_top_level(), + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), params_start_position, )?; diff --git a/boa_engine/src/syntax/parser/expression/assignment/mod.rs b/boa_engine/src/syntax/parser/expression/assignment/mod.rs index dd8d435900..6c861f57ec 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/mod.rs +++ b/boa_engine/src/syntax/parser/expression/assignment/mod.rs @@ -26,7 +26,7 @@ use crate::syntax::{ ParseResult, TokenParser, }, }; -use ast::operations::{contains, ContainsSymbol}; +use ast::operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}; use boa_ast::{ self as ast, expression::{ @@ -242,8 +242,8 @@ where // also occurs in the LexicallyDeclaredNames of ConciseBody. // https://tc39.es/ecma262/#sec-arrow-function-definitions-static-semantics-early-errors name_in_lexically_declared_names( - ¶meters, - &body.lexically_declared_names_top_level(), + &bound_names(¶meters), + &top_level_lexically_declared_names(&body), position, )?; diff --git a/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs index 2faed43b69..69bc75aea9 100644 --- a/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs @@ -12,7 +12,7 @@ use crate::syntax::{ use boa_ast::{ expression::Identifier, function::AsyncFunction, - operations::{contains, ContainsSymbol}, + operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, Keyword, Position, Punctuator, }; use boa_interner::{Interner, Sym}; @@ -132,8 +132,8 @@ where // also occurs in the LexicallyDeclaredNames of FunctionBody. // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors name_in_lexically_declared_names( - ¶ms, - &body.lexically_declared_names_top_level(), + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), params_start_position, )?; diff --git a/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs index 30c8f21a1f..5afe3e8aed 100644 --- a/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs @@ -21,7 +21,7 @@ use crate::syntax::{ use boa_ast::{ expression::Identifier, function::AsyncGenerator, - operations::{contains, ContainsSymbol}, + operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, Keyword, Position, Punctuator, }; use boa_interner::{Interner, Sym}; @@ -164,8 +164,8 @@ where // It is a Syntax Error if any element of the BoundNames of FormalParameters // also occurs in the LexicallyDeclaredNames of FunctionBody. name_in_lexically_declared_names( - ¶ms, - &body.lexically_declared_names_top_level(), + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), params_start_position, )?; diff --git a/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs index 0e1a45ae65..614690c0d0 100644 --- a/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs @@ -21,7 +21,7 @@ use crate::syntax::{ use boa_ast::{ expression::Identifier, function::Function, - operations::{contains, ContainsSymbol}, + operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, Keyword, Position, Punctuator, }; use boa_interner::{Interner, Sym}; @@ -126,8 +126,8 @@ where // also occurs in the LexicallyDeclaredNames of FunctionBody. // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors name_in_lexically_declared_names( - ¶ms, - &body.lexically_declared_names_top_level(), + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), params_start_position, )?; diff --git a/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs index 9c1c8a883a..f034229a28 100644 --- a/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs @@ -21,7 +21,7 @@ use crate::syntax::{ use boa_ast::{ expression::Identifier, function::Generator, - operations::{contains, ContainsSymbol}, + operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, Position, Punctuator, }; use boa_interner::{Interner, Sym}; @@ -128,8 +128,8 @@ where // also occurs in the LexicallyDeclaredNames of FunctionBody. // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors name_in_lexically_declared_names( - ¶ms, - &body.lexically_declared_names_top_level(), + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), params_start_position, )?; 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 a26b1a19d4..7573647c04 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 @@ -25,7 +25,9 @@ use boa_ast::{ Identifier, }, function::{AsyncFunction, AsyncGenerator, FormalParameterList, Function, Generator}, - operations::{contains, has_direct_super, ContainsSymbol}, + operations::{ + bound_names, contains, has_direct_super, top_level_lexically_declared_names, ContainsSymbol, + }, property::{self, MethodDefinition}, Expression, Keyword, Punctuator, }; @@ -443,24 +445,13 @@ where ))); } - // It is a Syntax Error if any element of the BoundNames of FormalParameters also occurs in the LexicallyDeclaredNames of FunctionBody. - let lexically_declared_names = body.lexically_declared_names(); - for parameter in params.as_ref() { - for name in ¶meter.names() { - if lexically_declared_names.contains(&(*name, false)) { - return Err(ParseError::general( - "formal parameter declared in lexically declared names", - params_start_position, - )); - } - if lexically_declared_names.contains(&(*name, true)) { - return Err(ParseError::general( - "formal parameter declared in lexically declared names", - params_start_position, - )); - } - } - } + // It is a Syntax Error if any element of the BoundNames of FormalParameters also occurs in the + // LexicallyDeclaredNames of FunctionBody. + name_in_lexically_declared_names( + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), + params_start_position, + )?; let method = MethodDefinition::Ordinary(Function::new(None, params, body)); @@ -691,6 +682,12 @@ where let class_element_name = ClassElementName::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; + let params_start_position = cursor + .peek(0, interner)? + .ok_or(ParseError::AbruptEnd)? + .span() + .start(); + let params = UniqueFormalParameters::new(true, false).parse(cursor, interner)?; let body_start = cursor @@ -720,9 +717,9 @@ where // Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also // occurs in the LexicallyDeclaredNames of GeneratorBody. name_in_lexically_declared_names( - ¶ms, - &body.lexically_declared_names_top_level(), - body_start, + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), + params_start_position, )?; let method = MethodDefinition::Generator(Generator::new(None, params, body, false)); @@ -834,9 +831,9 @@ where // Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also // occurs in the LexicallyDeclaredNames of GeneratorBody. name_in_lexically_declared_names( - ¶ms, - &body.lexically_declared_names_top_level(), - body_start, + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), + params_start_position, )?; let method = @@ -891,6 +888,12 @@ where let class_element_name = ClassElementName::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; + let params_start_position = cursor + .peek(0, interner)? + .ok_or(ParseError::AbruptEnd)? + .span() + .start(); + let params = UniqueFormalParameters::new(false, true).parse(cursor, interner)?; let body_start = cursor @@ -920,9 +923,9 @@ where // Early Error: It is a Syntax Error if any element of the BoundNames of UniqueFormalParameters also // occurs in the LexicallyDeclaredNames of GeneratorBody. name_in_lexically_declared_names( - ¶ms, - &body.lexically_declared_names_top_level(), - body_start, + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), + params_start_position, )?; let method = MethodDefinition::Async(AsyncFunction::new(None, params, body, false)); diff --git a/boa_engine/src/syntax/parser/mod.rs b/boa_engine/src/syntax/parser/mod.rs index 5be3d0548e..71bfcfae61 100644 --- a/boa_engine/src/syntax/parser/mod.rs +++ b/boa_engine/src/syntax/parser/mod.rs @@ -20,10 +20,10 @@ use crate::{ function::{FormalParameters, FunctionStatementList}, }, }, - Context, JsString, + Context, }; use boa_interner::Interner; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use std::io::Read; pub use self::error::{ParseError, ParseResult}; @@ -31,7 +31,10 @@ pub use self::error::{ParseError, ParseResult}; use boa_ast::{ expression::Identifier, function::FormalParameterList, - operations::{contains, contains_arguments, ContainsSymbol}, + operations::{ + contains, contains_arguments, top_level_lexically_declared_names, + top_level_var_declared_names, ContainsSymbol, + }, Position, StatementList, }; @@ -306,59 +309,20 @@ impl Script { // It is a Syntax Error if the LexicallyDeclaredNames of ScriptBody contains any duplicate entries. // It is a Syntax Error if any element of the LexicallyDeclaredNames of ScriptBody also occurs in the VarDeclaredNames of ScriptBody. - let mut var_declared_names = FxHashSet::default(); - statement_list.var_declared_names(&mut var_declared_names); - let lexically_declared_names = statement_list.lexically_declared_names(); - let mut lexically_declared_names_map: FxHashMap = - FxHashMap::default(); - for (name, is_function_declaration) in &lexically_declared_names { - if let Some(existing_is_function_declaration) = - lexically_declared_names_map.get(name) - { - if !(*is_function_declaration && *existing_is_function_declaration) { - return Err(ParseError::general( - "lexical name declared multiple times", - Position::new(1, 1), - )); - } - } - lexically_declared_names_map.insert(*name, *is_function_declaration); - - if !is_function_declaration && var_declared_names.contains(name) { - return Err(ParseError::general( - "lexical name declared in var names", - Position::new(1, 1), - )); - } - if context.has_binding(*name) { + let mut lexical_names = FxHashSet::default(); + for name in top_level_lexically_declared_names(&statement_list) { + if !lexical_names.insert(name) { return Err(ParseError::general( "lexical name declared multiple times", Position::new(1, 1), )); } - if !is_function_declaration { - let name_str = context.interner().resolve_expect(name.sym()); - let desc = context - .realm - .global_property_map - .string_property_map() - .get(&name_str.into_common::(false)); - let non_configurable_binding_exists = match desc { - Some(desc) => !matches!(desc.configurable(), Some(true)), - None => false, - }; - if non_configurable_binding_exists { - return Err(ParseError::general( - "lexical name declared in var names", - Position::new(1, 1), - )); - } - } } - for name in var_declared_names { - if context.has_binding(name) { + + for name in top_level_var_declared_names(&statement_list) { + if lexical_names.contains(&name) { return Err(ParseError::general( - "lexical name declared in var names", + "lexical name declared multiple times", Position::new(1, 1), )); } @@ -400,26 +364,23 @@ where .parse(cursor, interner)?; if !self.direct_eval { - for node in body.statements() { - // It is a Syntax Error if StatementList Contains super unless the source text containing super is eval - // code that is being processed by a direct eval. - // Additional early error rules for super within direct eval are defined in 19.2.1.1. - if contains(node, ContainsSymbol::Super) { - return Err(ParseError::general( - "invalid super usage", - Position::new(1, 1), - )); - } - - // It is a Syntax Error if StatementList Contains NewTarget unless the source text containing NewTarget - // is eval code that is being processed by a direct eval. - // Additional early error rules for NewTarget in direct eval are defined in 19.2.1.1. - if contains(node, ContainsSymbol::NewTarget) { - return Err(ParseError::general( - "invalid new.target usage", - Position::new(1, 1), - )); - } + // It is a Syntax Error if StatementList Contains super unless the source text containing super is eval + // code that is being processed by a direct eval. + // Additional early error rules for super within direct eval are defined in 19.2.1.1. + if contains(&body, ContainsSymbol::Super) { + return Err(ParseError::general( + "invalid super usage", + Position::new(1, 1), + )); + } + // It is a Syntax Error if StatementList Contains NewTarget unless the source text containing NewTarget + // is eval code that is being processed by a direct eval. + // Additional early error rules for NewTarget in direct eval are defined in 19.2.1.1. + if contains(&body, ContainsSymbol::NewTarget) { + return Err(ParseError::general( + "invalid new.target usage", + Position::new(1, 1), + )); } } @@ -429,18 +390,16 @@ where /// Helper to check if any parameter names are declared in the given list. fn name_in_lexically_declared_names( - parameter_list: &FormalParameterList, - names: &[Identifier], + bound_names: &[Identifier], + lexical_names: &[Identifier], position: Position, ) -> Result<(), ParseError> { - for parameter in parameter_list.as_ref() { - for name in ¶meter.names() { - if names.contains(name) { - return Err(ParseError::General { - message: "formal parameter declared in lexically declared names", - position, - }); - } + for name in bound_names { + if lexical_names.contains(name) { + return Err(ParseError::General { + message: "formal parameter declared in lexically declared names", + position, + }); } } Ok(()) diff --git a/boa_engine/src/syntax/parser/statement/block/mod.rs b/boa_engine/src/syntax/parser/statement/block/mod.rs index 4e62c2a2ac..1f08adcf49 100644 --- a/boa_engine/src/syntax/parser/statement/block/mod.rs +++ b/boa_engine/src/syntax/parser/statement/block/mod.rs @@ -15,10 +15,13 @@ use crate::syntax::{ lexer::TokenKind, parser::{AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser}, }; -use boa_ast::{expression::Identifier, statement, Punctuator}; +use boa_ast::{ + operations::{lexically_declared_names_legacy, var_declared_names}, + statement, Punctuator, +}; use boa_interner::Interner; use boa_profiler::Profiler; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap; use std::io::Read; /// The possible `TokenKind` which indicate the end of a block statement. @@ -93,29 +96,28 @@ where .map(statement::Block::from)?; cursor.expect(Punctuator::CloseBlock, "block", interner)?; - let lexically_declared_names = statement_list.lexically_declared_names(); - let mut lexically_declared_names_map: FxHashMap = FxHashMap::default(); - for (name, is_function_declaration) in &lexically_declared_names { - if let Some(existing_is_function_declaration) = lexically_declared_names_map.get(name) { - if !(!cursor.strict_mode() - && *is_function_declaration - && *existing_is_function_declaration) - { - return Err(ParseError::general( - "lexical name declared multiple times", - position, - )); + // It is a Syntax Error if the LexicallyDeclaredNames of StatementList contains any duplicate + // entries, unless the source text matched by this production is not strict mode code and the + // duplicate entries are only bound by FunctionDeclarations. + let mut lexical_names = FxHashMap::default(); + for (name, is_fn) in lexically_declared_names_legacy(&statement_list) { + if let Some(is_fn_previous) = lexical_names.insert(name, is_fn) { + match (cursor.strict_mode(), is_fn, is_fn_previous) { + (false, true, true) => {} + _ => { + return Err(ParseError::general( + "lexical name declared multiple times", + position, + )); + } } } - lexically_declared_names_map.insert(*name, *is_function_declaration); } - let mut var_declared_names = FxHashSet::default(); - for node in statement_list.statement_list().statements() { - node.var_declared_names(&mut var_declared_names); - } - for (lex_name, _) in &lexically_declared_names { - if var_declared_names.contains(lex_name) { + // It is a Syntax Error if any element of the LexicallyDeclaredNames of StatementList also + // occurs in the VarDeclaredNames of StatementList. + for name in var_declared_names(&statement_list) { + if lexical_names.contains_key(&name) { return Err(ParseError::general( "lexical name declared in var names", position, diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs index 18af68b058..c240ee406c 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs @@ -13,6 +13,7 @@ use crate::syntax::{ AllowAwait, AllowDefault, AllowYield, Cursor, ParseError, ParseResult, TokenParser, }, }; +use ast::operations::{lexically_declared_names, var_declared_names}; use boa_ast::{ self as ast, expression::Identifier, @@ -637,30 +638,18 @@ where StatementList::new(false, true, false, &FUNCTION_BREAK_TOKENS) .parse(cursor, interner)?; - let lexically_declared_names = statement_list.lexically_declared_names(); - let mut lexically_declared_names_map: FxHashMap = - FxHashMap::default(); - for (name, is_function_declaration) in &lexically_declared_names { - if let Some(existing_is_function_declaration) = - lexically_declared_names_map.get(name) - { - if !(!cursor.strict_mode() - && *is_function_declaration - && *existing_is_function_declaration) - { - return Err(ParseError::general( - "lexical name declared multiple times", - position, - )); - } + let mut lexical_names = FxHashSet::default(); + for name in &lexically_declared_names(&statement_list) { + if !lexical_names.insert(*name) { + return Err(ParseError::general( + "lexical name declared multiple times", + position, + )); } - lexically_declared_names_map.insert(*name, *is_function_declaration); } - let mut var_declared_names = FxHashSet::default(); - statement_list.var_declared_names(&mut var_declared_names); - for (lex_name, _) in &lexically_declared_names { - if var_declared_names.contains(lex_name) { + for name in var_declared_names(&statement_list) { + if lexical_names.contains(&name) { return Err(ParseError::general( "lexical name declared in var names", position, diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs index b6961ac306..a142cf9c51 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs @@ -32,7 +32,7 @@ use crate::syntax::{ use boa_ast::{ expression::Identifier, function::FormalParameterList, - operations::{contains, ContainsSymbol}, + operations::{bound_names, contains, top_level_lexically_declared_names, ContainsSymbol}, Declaration, Keyword, Position, Punctuator, StatementList, }; use boa_interner::{Interner, Sym}; @@ -209,8 +209,8 @@ fn parse_callable_declaration( // also occurs in the LexicallyDeclaredNames of FunctionBody. // https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors name_in_lexically_declared_names( - ¶ms, - &body.lexically_declared_names_top_level(), + &bound_names(¶ms), + &top_level_lexically_declared_names(&body), params_start_position, )?; diff --git a/boa_engine/src/syntax/parser/statement/declaration/lexical.rs b/boa_engine/src/syntax/parser/statement/declaration/lexical.rs index feeb796949..165b0780db 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/lexical.rs +++ b/boa_engine/src/syntax/parser/statement/declaration/lexical.rs @@ -16,6 +16,7 @@ use crate::syntax::{ AllowAwait, AllowIn, AllowYield, ParseError, ParseResult, TokenParser, }, }; +use ast::operations::bound_names; use boa_ast::{self as ast, declaration::Variable, pattern::Pattern, Keyword, Punctuator}; use boa_interner::{Interner, Sym}; use boa_profiler::Profiler; @@ -276,7 +277,7 @@ where let declaration = Pattern::Object(bindings.into()); - if declaration.idents().contains(&Sym::LET.into()) { + if bound_names(&declaration).contains(&Sym::LET.into()) { return Err(ParseError::lex(LexError::Syntax( "'let' is disallowed as a lexically bound name".into(), position, @@ -304,7 +305,7 @@ where let declaration = Pattern::Array(bindings.into()); - if declaration.idents().contains(&Sym::LET.into()) { + if bound_names(&declaration).contains(&Sym::LET.into()) { return Err(ParseError::lex(LexError::Syntax( "'let' is disallowed as a lexically bound name".into(), position, diff --git a/boa_engine/src/syntax/parser/statement/iteration/for_statement.rs b/boa_engine/src/syntax/parser/statement/iteration/for_statement.rs index 146739f747..0ca2137f74 100644 --- a/boa_engine/src/syntax/parser/statement/iteration/for_statement.rs +++ b/boa_engine/src/syntax/parser/statement/iteration/for_statement.rs @@ -16,6 +16,7 @@ use crate::syntax::{ AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, }, }; +use ast::operations::{bound_names, var_declared_names}; use boa_ast::{ self as ast, statement::{ @@ -156,68 +157,22 @@ where position, )); } - (Some(init), TokenKind::Keyword((Keyword::In, false))) => { - let init = - initializer_to_iterable_loop_initializer(init, position, cursor.strict_mode())?; - - let _next = cursor.next(interner)?; - let expr = Expression::new(None, true, self.allow_yield, self.allow_await) - .parse(cursor, interner)?; - - cursor.expect(Punctuator::CloseParen, "for in statement", interner)?; - - let position = cursor - .peek(0, interner)? - .ok_or(ParseError::AbruptEnd)? - .span() - .start(); - - let body = Statement::new(self.allow_yield, self.allow_await, self.allow_return) - .parse(cursor, interner)?; - - // Early Error: It is a Syntax Error if IsLabelledFunction(Statement) is true. - if body.is_labelled_function() { - return Err(ParseError::wrong_labelled_function_declaration(position)); - } - - // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". - // It is a Syntax Error if any element of the BoundNames of ForDeclaration also occurs in the VarDeclaredNames of Statement. - // It is a Syntax Error if the BoundNames of ForDeclaration contains any duplicate entries. - let mut vars = FxHashSet::default(); - body.var_declared_names(&mut vars); - let mut bound_names = FxHashSet::default(); - for name in init.bound_names() { - if name == Sym::LET { - return Err(ParseError::general( - "Cannot use 'let' as a lexically bound name", - position, - )); - } - if vars.contains(&name) { - return Err(ParseError::general( - "For loop initializer declared in loop body", - position, - )); - } - if !bound_names.insert(name) { - return Err(ParseError::general( - "For loop initializer cannot contain duplicate identifiers", - position, - )); - } - } - - return Ok(ForInLoop::new(init, expr, body).into()); + (Some(_), TokenKind::Keyword((Keyword::In, false))) if r#await => { + return Err(ParseError::general( + "`await` can only be used in a `for await .. of` loop", + position, + )); } - (Some(init), TokenKind::Keyword((Keyword::Of, false))) => { + (Some(init), TokenKind::Keyword((kw @ (Keyword::In | Keyword::Of), false))) => { + let kw = *kw; let init = initializer_to_iterable_loop_initializer(init, position, cursor.strict_mode())?; let _next = cursor.next(interner)?; - let iterable = Expression::new(None, true, self.allow_yield, self.allow_await) + let expr = Expression::new(None, true, self.allow_yield, self.allow_await) .parse(cursor, interner)?; - cursor.expect(Punctuator::CloseParen, "for of statement", interner)?; + cursor.expect(Punctuator::CloseParen, "for in/of statement", interner)?; let position = cursor .peek(0, interner)? @@ -233,34 +188,42 @@ where return Err(ParseError::wrong_labelled_function_declaration(position)); } - // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". - // It is a Syntax Error if any element of the BoundNames of ForDeclaration also occurs in the VarDeclaredNames of Statement. - // It is a Syntax Error if the BoundNames of ForDeclaration contains any duplicate entries. - let mut vars = FxHashSet::default(); - body.var_declared_names(&mut vars); - let mut bound_names = FxHashSet::default(); - for name in init.bound_names() { - if name == Sym::LET { - return Err(ParseError::general( - "Cannot use 'let' as a lexically bound name", - position, - )); - } - if vars.contains(&name) { - return Err(ParseError::general( - "For loop initializer declared in loop body", - position, - )); - } - if !bound_names.insert(name) { - return Err(ParseError::general( - "For loop initializer cannot contain duplicate identifiers", - position, - )); + // Checks are only applicable to lexical bindings. + if matches!( + &init, + IterableLoopInitializer::Const(_) | IterableLoopInitializer::Let(_) + ) { + // It is a Syntax Error if the BoundNames of ForDeclaration contains "let". + // It is a Syntax Error if any element of the BoundNames of ForDeclaration also occurs in the VarDeclaredNames of Statement. + // It is a Syntax Error if the BoundNames of ForDeclaration contains any duplicate entries. + let vars = var_declared_names(&body); + let mut names = FxHashSet::default(); + for name in bound_names(&init) { + if name == Sym::LET { + return Err(ParseError::general( + "Cannot use 'let' as a lexically bound name", + position, + )); + } + if vars.contains(&name) { + return Err(ParseError::general( + "For loop initializer declared in loop body", + position, + )); + } + if !names.insert(name) { + return Err(ParseError::general( + "For loop initializer cannot contain duplicate identifiers", + position, + )); + } } } - - return Ok(ForOfLoop::new(init, iterable, body, r#await).into()); + return Ok(if kw == Keyword::In { + ForInLoop::new(init, expr, body).into() + } else { + ForOfLoop::new(init, expr, body, r#await).into() + }); } (init, _) => init, }; @@ -319,10 +282,10 @@ where // Early Error: It is a Syntax Error if any element of the BoundNames of // LexicalDeclaration also occurs in the VarDeclaredNames of Statement. - let mut vars = FxHashSet::default(); - body.var_declared_names(&mut vars); - if let Some(ref init) = init { - for name in init.bound_names() { + // Note: only applies to lexical bindings. + if let Some(ForLoopInitializer::Lexical(ref decl)) = init { + let vars = var_declared_names(&body); + for name in bound_names(decl) { if vars.contains(&name) { return Err(ParseError::general( "For loop initializer declared in loop body", diff --git a/boa_engine/src/syntax/parser/statement/iteration/tests.rs b/boa_engine/src/syntax/parser/statement/iteration/tests.rs index 2db171ccd1..981d0723ec 100644 --- a/boa_engine/src/syntax/parser/statement/iteration/tests.rs +++ b/boa_engine/src/syntax/parser/statement/iteration/tests.rs @@ -244,3 +244,9 @@ fn do_while_spaces() { fn reject_const_no_init_for_loop() { check_invalid("for (const h;;);"); } + +/// Checks rejection of for await .. in loops +#[test] +fn reject_for_await_in_loop() { + check_invalid("for await (x in [1,2,3]);"); +} diff --git a/boa_engine/src/syntax/parser/statement/switch/mod.rs b/boa_engine/src/syntax/parser/statement/switch/mod.rs index 05f9950d41..4995ca8cef 100644 --- a/boa_engine/src/syntax/parser/statement/switch/mod.rs +++ b/boa_engine/src/syntax/parser/statement/switch/mod.rs @@ -8,12 +8,11 @@ use crate::syntax::{ Cursor, ParseError, ParseResult, TokenParser, }, }; -use boa_ast::{ - self as ast, expression::Identifier, statement, statement::Switch, Keyword, Punctuator, -}; +use ast::operations::{lexically_declared_names_legacy, var_declared_names}; +use boa_ast::{self as ast, statement, statement::Switch, Keyword, Punctuator}; use boa_interner::Interner; use boa_profiler::Profiler; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashMap; use std::io::Read; /// The possible `TokenKind` which indicate the end of a case statement. @@ -70,11 +69,48 @@ where cursor.expect(Punctuator::CloseParen, "switch statement", interner)?; + let position = cursor + .peek(0, interner)? + .ok_or(ParseError::AbruptEnd)? + .span() + .start(); + let (cases, default) = CaseBlock::new(self.allow_yield, self.allow_await, self.allow_return) .parse(cursor, interner)?; - Ok(Switch::new(condition, cases, default)) + let switch = Switch::new(condition, cases, default); + + // It is a Syntax Error if the LexicallyDeclaredNames of CaseBlock contains any duplicate + // entries, unless the source text matched by this production is not strict mode code and the + // duplicate entries are only bound by FunctionDeclarations. + let mut lexical_names = FxHashMap::default(); + for (name, is_fn) in lexically_declared_names_legacy(&switch) { + if let Some(is_fn_previous) = lexical_names.insert(name, is_fn) { + match (cursor.strict_mode(), is_fn, is_fn_previous) { + (false, true, true) => {} + _ => { + return Err(ParseError::general( + "lexical name declared multiple times", + position, + )); + } + } + } + } + + // It is a Syntax Error if any element of the LexicallyDeclaredNames of CaseBlock also occurs + // in the VarDeclaredNames of CaseBlock. + for name in var_declared_names(&switch) { + if lexical_names.contains_key(&name) { + return Err(ParseError::general( + "lexical name declared in var declared names", + position, + )); + } + } + + Ok(switch) } } @@ -119,11 +155,6 @@ where let mut cases = Vec::new(); let mut default = None; - let position = cursor - .peek(0, interner)? - .ok_or(ParseError::AbruptEnd)? - .span() - .start(); loop { let token = cursor.next(interner)?.ok_or(ParseError::AbruptEnd)?; match token.kind() { @@ -184,46 +215,6 @@ where } } - // It is a Syntax Error if the LexicallyDeclaredNames of CaseBlock contains any duplicate entries. - // It is a Syntax Error if any element of the LexicallyDeclaredNames of CaseBlock also occurs in the VarDeclaredNames of CaseBlock. - let mut lexically_declared_names = Vec::new(); - let mut var_declared_names = FxHashSet::default(); - for case in &cases { - lexically_declared_names.extend(case.body().lexically_declared_names()); - - case.body().var_declared_names(&mut var_declared_names); - } - if let Some(default_clause) = &default { - lexically_declared_names.extend(default_clause.lexically_declared_names()); - - default_clause.var_declared_names(&mut var_declared_names); - } - - let mut lexically_declared_names_map: FxHashMap = FxHashMap::default(); - for (name, is_function_declaration) in &lexically_declared_names { - if let Some(existing_is_function_declaration) = lexically_declared_names_map.get(name) { - if !(!cursor.strict_mode() - && *is_function_declaration - && *existing_is_function_declaration) - { - return Err(ParseError::general( - "lexical name declared multiple times", - position, - )); - } - } - lexically_declared_names_map.insert(*name, *is_function_declaration); - } - - for (name, _) in &lexically_declared_names { - if var_declared_names.contains(name) { - return Err(ParseError::general( - "lexical name declared in var declared names", - position, - )); - } - } - Ok((cases.into_boxed_slice(), default)) } } 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 0cbc2906dc..e64b95de6a 100644 --- a/boa_engine/src/syntax/parser/statement/try_stm/catch.rs +++ b/boa_engine/src/syntax/parser/statement/try_stm/catch.rs @@ -5,7 +5,11 @@ use crate::syntax::{ AllowAwait, AllowReturn, AllowYield, Cursor, ParseError, ParseResult, TokenParser, }, }; -use boa_ast::{declaration::Binding, statement, Keyword, Punctuator}; +use boa_ast::{ + declaration::Binding, + operations::{bound_names, lexically_declared_names, var_declared_names}, + statement, Keyword, Punctuator, +}; use boa_interner::Interner; use boa_profiler::Profiler; use rustc_hash::FxHashSet; @@ -68,17 +72,21 @@ where // It is a Syntax Error if BoundNames of CatchParameter contains any duplicate elements. // https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors - if let Some(Binding::Pattern(pattern)) = &catch_param { - let mut set = FxHashSet::default(); - for ident in pattern.idents() { - if !set.insert(ident) { - return Err(ParseError::general( - "duplicate catch parameter identifier", - position, - )); + let bound_names: Option> = catch_param + .as_ref() + .map(|binding| { + let mut set = FxHashSet::default(); + for ident in bound_names(binding) { + if !set.insert(ident) { + return Err(ParseError::general( + "duplicate catch parameter identifier", + position, + )); + } } - } - } + Ok(set) + }) + .transpose()?; let position = cursor .peek(0, interner)? @@ -92,41 +100,18 @@ where // It is a Syntax Error if any element of the BoundNames of CatchParameter also occurs in the VarDeclaredNames of Block unless CatchParameter is CatchParameter : BindingIdentifier . // https://tc39.es/ecma262/#sec-try-statement-static-semantics-early-errors // https://tc39.es/ecma262/#sec-variablestatements-in-catch-blocks - let lexically_declared_names = catch_block.lexically_declared_names(); - match &catch_param { - Some(Binding::Identifier(ident)) => { - if lexically_declared_names.contains(&(*ident, false)) { - return Err(ParseError::general( - "catch parameter identifier declared in catch body", - position, - )); - } - if lexically_declared_names.contains(&(*ident, true)) { + if let Some(bound_names) = bound_names { + for name in lexically_declared_names(&catch_block) { + if bound_names.contains(&name) { return Err(ParseError::general( "catch parameter identifier declared in catch body", position, )); } } - Some(Binding::Pattern(pattern)) => { - let mut var_declared_names = FxHashSet::default(); - for node in catch_block.statement_list().statements() { - node.var_declared_names(&mut var_declared_names); - } - for ident in pattern.idents() { - if lexically_declared_names.contains(&(ident, false)) { - return Err(ParseError::general( - "catch parameter identifier declared in catch body", - position, - )); - } - if lexically_declared_names.contains(&(ident, true)) { - return Err(ParseError::general( - "catch parameter identifier declared in catch body", - position, - )); - } - if var_declared_names.contains(&ident) { + if !matches!(&catch_param, Some(Binding::Identifier(_))) { + for name in var_declared_names(&catch_block) { + if bound_names.contains(&name) { return Err(ParseError::general( "catch parameter identifier declared in catch body", position, @@ -134,7 +119,6 @@ where } } } - _ => {} } let catch_node = statement::Catch::new(catch_param, catch_block); diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index f42bd71752..c23c75cc04 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -1396,19 +1396,7 @@ impl JsObject { false, ); - let mut arguments_in_parameter_names = false; - let mut is_simple_parameter_list = true; - let mut has_parameter_expressions = false; - - for param in code.params.as_ref().iter() { - has_parameter_expressions = has_parameter_expressions || param.init().is_some(); - arguments_in_parameter_names = arguments_in_parameter_names - || param.names().contains(&Sym::ARGUMENTS.into()); - is_simple_parameter_list = is_simple_parameter_list - && !param.is_rest_param() - && param.is_identifier() - && param.init().is_none(); - } + let has_expressions = code.params.has_expressions(); if let Some(binding) = code.arguments_binding { let arguments_obj = if code.strict || !code.params.is_simple() { @@ -1475,7 +1463,7 @@ impl JsObject { context.vm.pop_frame(); let mut environment = context.realm.environments.pop(); - if has_parameter_expressions { + if has_expressions { environment = context.realm.environments.pop(); }