Browse Source

Rewrite scope analysis operations using visitors (#2408)

This PR rewrites all syntax-directed operations that find declared names and variables using visitors.

Hopefully, this should be the last step before finally being able to separate the parser from the engine.

I checked the failing [tests](85373b4ce1/test/language/statements/for-await-of/async-gen-decl-dstr-obj-prop-elem-target-yield-expr.js (L49)) and they're apparently false positives, since they return `Promise { <rejected> ReferenceError: x is not initialized }` on the main branch.
pull/2410/head
José Julián Espina 2 years ago
parent
commit
8e14d76893
  1. 71
      boa_ast/src/declaration/mod.rs
  2. 16
      boa_ast/src/declaration/variable.rs
  3. 2
      boa_ast/src/expression/mod.rs
  4. 21
      boa_ast/src/function/parameters.rs
  5. 417
      boa_ast/src/operations.rs
  6. 112
      boa_ast/src/pattern.rs
  7. 8
      boa_ast/src/statement/block.rs
  8. 24
      boa_ast/src/statement/iteration/for_loop.rs
  9. 2
      boa_ast/src/statement/iteration/for_of_loop.rs
  10. 18
      boa_ast/src/statement/iteration/mod.rs
  11. 94
      boa_ast/src/statement/mod.rs
  12. 88
      boa_ast/src/statement_list.rs
  13. 378
      boa_ast/src/visitor.rs
  14. 35
      boa_engine/src/builtins/eval/mod.rs
  15. 22
      boa_engine/src/builtins/function/arguments.rs
  16. 37
      boa_engine/src/builtins/function/mod.rs
  17. 6
      boa_engine/src/bytecompiler/function.rs
  18. 33
      boa_engine/src/bytecompiler/mod.rs
  19. 7
      boa_engine/src/syntax/parser/expression/assignment/arrow_function.rs
  20. 6
      boa_engine/src/syntax/parser/expression/assignment/async_arrow_function.rs
  21. 6
      boa_engine/src/syntax/parser/expression/assignment/mod.rs
  22. 6
      boa_engine/src/syntax/parser/expression/primary/async_function_expression/mod.rs
  23. 6
      boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs
  24. 6
      boa_engine/src/syntax/parser/expression/primary/function_expression/mod.rs
  25. 6
      boa_engine/src/syntax/parser/expression/primary/generator_expression/mod.rs
  26. 59
      boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs
  27. 117
      boa_engine/src/syntax/parser/mod.rs
  28. 44
      boa_engine/src/syntax/parser/statement/block/mod.rs
  29. 31
      boa_engine/src/syntax/parser/statement/declaration/hoistable/class_decl/mod.rs
  30. 6
      boa_engine/src/syntax/parser/statement/declaration/hoistable/mod.rs
  31. 5
      boa_engine/src/syntax/parser/statement/declaration/lexical.rs
  32. 133
      boa_engine/src/syntax/parser/statement/iteration/for_statement.rs
  33. 6
      boa_engine/src/syntax/parser/statement/iteration/tests.rs
  34. 91
      boa_engine/src/syntax/parser/statement/switch/mod.rs
  35. 66
      boa_engine/src/syntax/parser/statement/try_stm/catch.rs
  36. 16
      boa_engine/src/vm/code_block.rs

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

16
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<Identifier> {
self.binding.idents()
}
}
impl VisitWith for Variable {
@ -358,16 +352,6 @@ impl From<Pattern> for Binding {
}
}
impl Binding {
/// Gets the list of declared identifiers.
pub(crate) fn idents(&self) -> Vec<Identifier> {
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 {

2
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),
}

21
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 &parameters {
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<Identifier> {
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(_))

417
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<N>(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<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_async_function(&mut self, _: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_generator(&mut self, _: &'ast Generator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_async_generator(&mut self, _: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_class(&mut self, node: &'ast Class) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> {
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<Self::BreakTy> {
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<Self::BreakTy> {
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<Self::BreakTy> {
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<Self::BreakTy> {
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<Self::BreakTy> {
if node.sym() == Sym::ARGUMENTS {
ControlFlow::Break(())
@ -225,22 +245,27 @@ where
}
}
#[inline]
fn visit_function(&mut self, _: &'ast Function) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_async_function(&mut self, _: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_generator(&mut self, _: &'ast Generator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_async_generator(&mut self, _: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
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<Identifier> {
#[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<Identifier> {
#[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::BreakTy> {
self.0.add(*node, false);
ControlFlow::Continue(())
}
#[inline]
fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
// TODO: add "*default" for module default functions without name
#[inline]
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, true);
}
ControlFlow::Continue(())
}
#[inline]
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
#[inline]
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
#[inline]
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
if let Some(ident) = node.name() {
self.0.add(ident, false);
}
ControlFlow::Continue(())
}
#[inline]
fn visit_class(&mut self, node: &'ast Class) -> ControlFlow<Self::BreakTy> {
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<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
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<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
if let Statement::Labelled(labelled) = node {
return self.visit_labelled(labelled);
}
ControlFlow::Continue(())
}
#[inline]
fn visit_declaration(&mut self, node: &'ast Declaration) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_declaration(node)
}
#[inline]
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
top_level_lexicals(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
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<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
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<NodeRef<'a>>,
{
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<Identifier>);
impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
type BreakTy = Infallible;
#[inline]
fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_declaration(&mut self, _: &'ast Declaration) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
}
#[inline]
fn visit_var_declaration(&mut self, node: &'ast VarDeclaration) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_var_declaration(node)
}
#[inline]
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
LabelledItem::Function(_) => ControlFlow::Continue(()),
LabelledItem::Statement(stmt) => stmt.visit_with(self),
}
}
#[inline]
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_async_function(&mut self, node: &'ast AsyncFunction) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_generator(&mut self, node: &'ast Generator) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_async_generator(&mut self, node: &'ast AsyncGenerator) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
#[inline]
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
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<Identifier>
where
&'a N: Into<NodeRef<'a>>,
{
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<T: IdentList>(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<Identifier> {
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<Identifier>) {
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<Identifier> {
let mut names = FxHashSet::default();
top_level_vars(stmts, &mut names);
names
}

112
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<Identifier> {
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<V::BreakTy>
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<Identifier> {
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<Identifier> {
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<Identifier> {
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<Identifier> {
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 {

8
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<T> From<T> for Block

24
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<Identifier> {
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 {

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

18
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<Identifier> {
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 {

94
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<Identifier>) {
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

88
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<Identifier>) {
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<Identifier>) {
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<Identifier> {
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<Box<[StatementListItem]>> for StatementList {

378
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<Self::BreakTy> {
fn $fn_name(&mut self, node: &'ast $type_name) -> ControlFlow<Self::BreakTy> {
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<Self::BreakTy> {
fn $fn_name(&mut self, node: &'ast mut $type_name) -> ControlFlow<Self::BreakTy> {
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<N: Into<NodeRef<'ast>>>(&mut self, node: N) -> ControlFlow<Self::BreakTy> {
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<N: Into<NodeRefMut<'ast>>>(&mut self, node: N) -> ControlFlow<Self::BreakTy> {
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<V::BreakTy>
fn visit_with<'a, V>(&'a self, visitor: &mut V) -> ControlFlow<V::BreakTy>
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<V::BreakTy>
fn visit_with_mut<'a, V>(&'a mut self, visitor: &mut V) -> ControlFlow<V::BreakTy>
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<V::BreakTy>
fn visit_with<'a, V>(&'a self, _visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: Visitor<'a>,
{
core::ops::ControlFlow::Continue(())
}
fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> core::ops::ControlFlow<V::BreakTy>
fn visit_with_mut<'a, V>(&'a mut self, _visitor: &mut V) -> ControlFlow<V::BreakTy>
where
V: VisitorMut<'a>,
{

35
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<JsValue> {
/// 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<Gc<DeclarativeEnvironment>>),
}
/// 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
}

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

37
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(&parameters) {
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(&parameters) {
if lexically_declared_names.contains(&name) {
return Err(JsNativeError::syntax()
.with_message(format!(
"Redeclaration of formal parameter `{}`",
context.interner().resolve_expect(name.sym())
))
.into());
}
}
}

6
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

33
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<Sym>,
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<Sym>,
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)?;

7
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(
&params,
&body.lexically_declared_names_top_level(),
&bound_names(&params),
&top_level_lexically_declared_names(&body),
params_start_position,
)?;

6
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(
&params,
&body.lexically_declared_names_top_level(),
&bound_names(&params),
&top_level_lexically_declared_names(&body),
params_start_position,
)?;

6
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(
&parameters,
&body.lexically_declared_names_top_level(),
&bound_names(&parameters),
&top_level_lexically_declared_names(&body),
position,
)?;

6
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(
&params,
&body.lexically_declared_names_top_level(),
&bound_names(&params),
&top_level_lexically_declared_names(&body),
params_start_position,
)?;

6
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(
&params,
&body.lexically_declared_names_top_level(),
&bound_names(&params),
&top_level_lexically_declared_names(&body),
params_start_position,
)?;

6
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(
&params,
&body.lexically_declared_names_top_level(),
&bound_names(&params),
&top_level_lexically_declared_names(&body),
params_start_position,
)?;

6
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(
&params,
&body.lexically_declared_names_top_level(),
&bound_names(&params),
&top_level_lexically_declared_names(&body),
params_start_position,
)?;

59
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 &parameter.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(&params),
&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(
&params,
&body.lexically_declared_names_top_level(),
body_start,
&bound_names(&params),
&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(
&params,
&body.lexically_declared_names_top_level(),
body_start,
&bound_names(&params),
&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(
&params,
&body.lexically_declared_names_top_level(),
body_start,
&bound_names(&params),
&top_level_lexically_declared_names(&body),
params_start_position,
)?;
let method = MethodDefinition::Async(AsyncFunction::new(None, params, body, false));

117
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<Identifier, bool> =
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::<JsString>(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 &parameter.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(())

44
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<Identifier, bool> = 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,

31
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<Identifier, bool> =
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,

6
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<R: Read, C: CallableDeclaration>(
// also occurs in the LexicallyDeclaredNames of FunctionBody.
// https://tc39.es/ecma262/#sec-function-definitions-static-semantics-early-errors
name_in_lexically_declared_names(
&params,
&body.lexically_declared_names_top_level(),
&bound_names(&params),
&top_level_lexically_declared_names(&body),
params_start_position,
)?;

5
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,

133
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",

6
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]);");
}

91
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<Identifier, bool> = 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))
}
}

66
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<FxHashSet<_>> = 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);

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

Loading…
Cancel
Save