diff --git a/boa_ast/src/operations.rs b/boa_ast/src/operations.rs index 69a054859aa..3e7e7a3880b 100644 --- a/boa_ast/src/operations.rs +++ b/boa_ast/src/operations.rs @@ -9,17 +9,20 @@ use boa_interner::{Interner, Sym}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ - declaration::{ExportDeclaration, ImportDeclaration, VarDeclaration}, + declaration::{ExportDeclaration, ImportDeclaration, VarDeclaration, Variable}, expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield}, function::{ ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement, Function, Generator, PrivateName, }, property::{MethodDefinition, PropertyDefinition}, - statement::LabelledItem, + statement::{ + iteration::{ForLoopInitializer, IterableLoopInitializer}, + LabelledItem, + }, try_break, visitor::{NodeRef, VisitWith, Visitor, VisitorMut}, - Declaration, Expression, Statement, StatementList, StatementListItem, + Declaration, Expression, ModuleItem, Statement, StatementList, StatementListItem, }; /// Represents all the possible symbols searched for by the [`Contains`][contains] operation. @@ -555,25 +558,125 @@ struct VarDeclaredNamesVisitor<'a>(&'a mut FxHashSet); impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { type BreakTy = Infallible; - fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow { - ControlFlow::Continue(()) + fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { + match node { + Statement::Empty + | Statement::Expression(_) + | Statement::Continue(_) + | Statement::Break(_) + | Statement::Return(_) + | Statement::Throw(_) => ControlFlow::Continue(()), + Statement::Block(node) => self.visit(node), + Statement::Var(node) => self.visit(node), + Statement::If(node) => self.visit(node), + Statement::DoWhileLoop(node) => self.visit(node), + Statement::WhileLoop(node) => self.visit(node), + Statement::ForLoop(node) => self.visit(node), + Statement::ForInLoop(node) => self.visit(node), + Statement::ForOfLoop(node) => self.visit(node), + Statement::Switch(node) => self.visit(node), + Statement::Labelled(node) => self.visit(node), + Statement::Try(node) => self.visit(node), + Statement::With(node) => self.visit(node), + } } - fn visit_declaration(&mut self, _: &'ast Declaration) -> ControlFlow { - ControlFlow::Continue(()) + fn visit_statement_list_item( + &mut self, + node: &'ast StatementListItem, + ) -> ControlFlow { + match node { + StatementListItem::Statement(stmt) => self.visit_statement(stmt), + StatementListItem::Declaration(_) => ControlFlow::Continue(()), + } } - fn visit_var_declaration(&mut self, node: &'ast VarDeclaration) -> ControlFlow { - BoundNamesVisitor(self.0).visit_var_declaration(node) + fn visit_variable(&mut self, node: &'ast Variable) -> ControlFlow { + BoundNamesVisitor(self.0).visit_variable(node) + } + + fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow { + if let Some(node) = node.else_node() { + self.visit(node); + } + self.visit(node.body()) + } + + fn visit_do_while_loop( + &mut self, + node: &'ast crate::statement::DoWhileLoop, + ) -> ControlFlow { + self.visit(node.body()) + } + + fn visit_while_loop( + &mut self, + node: &'ast crate::statement::WhileLoop, + ) -> ControlFlow { + self.visit(node.body()) + } + + fn visit_for_loop( + &mut self, + node: &'ast crate::statement::ForLoop, + ) -> ControlFlow { + if let Some(ForLoopInitializer::Var(node)) = node.init() { + BoundNamesVisitor(self.0).visit_var_declaration(node); + } + self.visit(node.body()) + } + + fn visit_for_in_loop( + &mut self, + node: &'ast crate::statement::ForInLoop, + ) -> ControlFlow { + if let IterableLoopInitializer::Var(node) = node.initializer() { + BoundNamesVisitor(self.0).visit_variable(node); + } + self.visit(node.body()) + } + + fn visit_for_of_loop( + &mut self, + node: &'ast crate::statement::ForOfLoop, + ) -> ControlFlow { + if let IterableLoopInitializer::Var(node) = node.initializer() { + BoundNamesVisitor(self.0).visit_variable(node); + } + self.visit(node.body()) + } + + fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow { + self.visit(node.statement()) + } + + fn visit_switch(&mut self, node: &'ast crate::statement::Switch) -> ControlFlow { + for case in node.cases() { + self.visit(case); + } + if let Some(node) = node.default() { + self.visit(node); + } + ControlFlow::Continue(()) } fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { match node { LabelledItem::Function(_) => ControlFlow::Continue(()), - LabelledItem::Statement(stmt) => stmt.visit_with(self), + LabelledItem::Statement(stmt) => self.visit(stmt), } } + fn visit_try(&mut self, node: &'ast crate::statement::Try) -> ControlFlow { + if let Some(node) = node.finally() { + self.visit(node); + } + if let Some(node) = node.catch() { + self.visit(node.block()); + } + self.visit(node.block()) + } + fn visit_function(&mut self, node: &'ast Function) -> ControlFlow { top_level_vars(node.body(), self.0); ControlFlow::Continue(()) @@ -594,19 +697,6 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> { ControlFlow::Continue(()) } - fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow { - top_level_vars(node.body(), self.0); - ControlFlow::Continue(()) - } - - fn visit_async_arrow_function( - &mut self, - node: &'ast AsyncArrowFunction, - ) -> ControlFlow { - top_level_vars(node.body(), self.0); - ControlFlow::Continue(()) - } - fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow { if let ClassElement::StaticBlock(stmts) = node { top_level_vars(stmts, self.0); @@ -1173,10 +1263,7 @@ where ControlFlow::Continue(()) } - fn visit_module_item( - &mut self, - node: &'ast crate::ModuleItem, - ) -> ControlFlow { + fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { match node { crate::ModuleItem::ImportDeclaration(_) | crate::ModuleItem::ExportDeclaration(_) => ControlFlow::Continue(()), @@ -1230,3 +1317,349 @@ where node.visit_with(&mut visitor).is_break() } + +/// Returns a list of lexically scoped declarations of the given node. +/// +/// This is equivalent to the [`LexicallyScopedDeclarations`][spec] syntax operation in the spec. +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallyscopeddeclarations +#[must_use] +pub fn lexically_scoped_declarations<'a, N>(node: &'a N) -> Vec +where + &'a N: Into>, +{ + let mut declarations = Vec::new(); + LexicallyScopedDeclarationsVisitor(&mut declarations).visit(node.into()); + declarations +} + +/// The [`Visitor`] used to obtain the lexically scoped declarations of a node. +#[derive(Debug)] +struct LexicallyScopedDeclarationsVisitor<'a>(&'a mut Vec); + +impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_> { + type BreakTy = Infallible; + + fn visit_statement_list_item( + &mut self, + node: &'ast StatementListItem, + ) -> ControlFlow { + match node { + StatementListItem::Statement(Statement::Labelled(labelled)) => { + self.visit_labelled(labelled) + } + StatementListItem::Statement(_) => ControlFlow::Continue(()), + StatementListItem::Declaration(declaration) => { + self.0.push(declaration.clone()); + ControlFlow::Continue(()) + } + } + } + + fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { + match node { + LabelledItem::Function(f) => { + self.0.push(f.clone().into()); + ControlFlow::Continue(()) + } + LabelledItem::Statement(_) => ControlFlow::Continue(()), + } + } + + fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { + match node { + ModuleItem::StatementListItem(item) => self.visit_statement_list_item(item), + ModuleItem::ExportDeclaration(ExportDeclaration::Declaration(declaration)) => { + self.0.push(declaration.clone()); + ControlFlow::Continue(()) + } + ModuleItem::ExportDeclaration(ExportDeclaration::DefaultFunction(f)) => { + self.0.push(f.clone().into()); + ControlFlow::Continue(()) + } + ModuleItem::ExportDeclaration(ExportDeclaration::DefaultGenerator(f)) => { + self.0.push(f.clone().into()); + ControlFlow::Continue(()) + } + ModuleItem::ExportDeclaration(ExportDeclaration::DefaultAsyncFunction(f)) => { + self.0.push(f.clone().into()); + ControlFlow::Continue(()) + } + ModuleItem::ExportDeclaration(ExportDeclaration::DefaultAsyncGenerator(f)) => { + self.0.push(f.clone().into()); + ControlFlow::Continue(()) + } + ModuleItem::ExportDeclaration(ExportDeclaration::DefaultClassDeclaration(f)) => { + self.0.push(f.clone().into()); + ControlFlow::Continue(()) + } + ModuleItem::ImportDeclaration(_) | ModuleItem::ExportDeclaration(_) => { + ControlFlow::Continue(()) + } + } + } +} + +/// The type of a var scoped declaration. +#[derive(Clone, Debug)] +pub enum VarScopedDeclaration { + /// See [`VarDeclaration`] + VariableDeclaration(Variable), + + /// See [`Function`] + Function(Function), + + /// See [`Generator`] + Generator(Generator), + + /// See [`AsyncFunction`] + AsyncFunction(AsyncFunction), + + /// See [`AsyncGenerator`] + AsyncGenerator(AsyncGenerator), +} + +impl VarScopedDeclaration { + /// Return the bound names of the declaration. + #[must_use] + pub fn bound_names(&self) -> Vec { + match self { + Self::VariableDeclaration(v) => bound_names(v), + Self::Function(f) => bound_names(f), + Self::Generator(g) => bound_names(g), + Self::AsyncFunction(f) => bound_names(f), + Self::AsyncGenerator(g) => bound_names(g), + } + } +} + +/// Returns a list of var scoped declarations of the given node. +/// +/// This is equivalent to the [`VarScopedDeclarations`][spec] syntax operation in the spec. +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-varscopeddeclarations +#[must_use] +pub fn var_scoped_declarations<'a, N>(node: &'a N) -> Vec +where + &'a N: Into>, +{ + let mut declarations = Vec::new(); + VarScopedDeclarationsVisitor(&mut declarations).visit(node.into()); + declarations +} + +/// The [`Visitor`] used to obtain the var scoped declarations of a node. +#[derive(Debug)] +struct VarScopedDeclarationsVisitor<'a>(&'a mut Vec); + +impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> { + type BreakTy = Infallible; + + fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow { + match node { + Statement::Block(s) => self.visit(s), + Statement::Var(s) => self.visit(s), + Statement::If(s) => self.visit(s), + Statement::DoWhileLoop(s) => self.visit(s), + Statement::WhileLoop(s) => self.visit(s), + Statement::ForLoop(s) => self.visit(s), + Statement::ForInLoop(s) => self.visit(s), + Statement::ForOfLoop(s) => self.visit(s), + Statement::Switch(s) => self.visit(s), + Statement::Labelled(s) => self.visit(s), + Statement::Try(s) => self.visit(s), + Statement::With(s) => self.visit(s), + Statement::Empty + | Statement::Expression(_) + | Statement::Continue(_) + | Statement::Break(_) + | Statement::Return(_) + | Statement::Throw(_) => ControlFlow::Continue(()), + } + } + + fn visit_statement_list_item( + &mut self, + node: &'ast StatementListItem, + ) -> ControlFlow { + match node { + StatementListItem::Declaration(_) => ControlFlow::Continue(()), + StatementListItem::Statement(s) => self.visit(s), + } + } + + fn visit_var_declaration(&mut self, node: &'ast VarDeclaration) -> ControlFlow { + for var in node.0.as_ref() { + self.0 + .push(VarScopedDeclaration::VariableDeclaration(var.clone())); + } + ControlFlow::Continue(()) + } + + fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow { + self.visit(node.body()); + if let Some(else_node) = node.else_node() { + self.visit(else_node); + } + ControlFlow::Continue(()) + } + + fn visit_do_while_loop( + &mut self, + node: &'ast crate::statement::DoWhileLoop, + ) -> ControlFlow { + self.visit(node.body()); + ControlFlow::Continue(()) + } + + fn visit_while_loop( + &mut self, + node: &'ast crate::statement::WhileLoop, + ) -> ControlFlow { + self.visit(node.body()); + ControlFlow::Continue(()) + } + + fn visit_for_loop( + &mut self, + node: &'ast crate::statement::ForLoop, + ) -> ControlFlow { + if let Some(ForLoopInitializer::Var(v)) = node.init() { + self.visit(v); + } + self.visit(node.body()); + ControlFlow::Continue(()) + } + + fn visit_for_in_loop( + &mut self, + node: &'ast crate::statement::ForInLoop, + ) -> ControlFlow { + if let IterableLoopInitializer::Var(var) = node.initializer() { + self.0 + .push(VarScopedDeclaration::VariableDeclaration(var.clone())); + } + self.visit(node.body()); + ControlFlow::Continue(()) + } + + fn visit_for_of_loop( + &mut self, + node: &'ast crate::statement::ForOfLoop, + ) -> ControlFlow { + if let IterableLoopInitializer::Var(var) = node.initializer() { + self.0 + .push(VarScopedDeclaration::VariableDeclaration(var.clone())); + } + self.visit(node.body()); + ControlFlow::Continue(()) + } + + fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow { + self.visit(node.statement()); + ControlFlow::Continue(()) + } + + fn visit_switch(&mut self, node: &'ast crate::statement::Switch) -> ControlFlow { + for case in node.cases() { + self.visit(case); + } + if let Some(default) = node.default() { + self.visit(default); + } + ControlFlow::Continue(()) + } + + fn visit_case(&mut self, node: &'ast crate::statement::Case) -> ControlFlow { + self.visit(node.body()); + ControlFlow::Continue(()) + } + + fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { + match node { + LabelledItem::Statement(s) => self.visit(s), + LabelledItem::Function(_) => ControlFlow::Continue(()), + } + } + + fn visit_catch(&mut self, node: &'ast crate::statement::Catch) -> ControlFlow { + self.visit(node.block()); + ControlFlow::Continue(()) + } + + fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow { + match node { + ModuleItem::ExportDeclaration(ExportDeclaration::VarStatement(var)) => self.visit(var), + ModuleItem::StatementListItem(item) => self.visit(item), + _ => ControlFlow::Continue(()), + } + } +} + +/// Returns a list of top level var scoped declarations of the given node. +/// +/// This is equivalent to the [`TopLevelVarScopedDeclarations`][spec] syntax operation in the spec. +/// +/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvarscopeddeclarations +#[must_use] +pub fn top_level_var_scoped_declarations<'a, N>(node: &'a N) -> Vec +where + &'a N: Into>, +{ + let mut declarations = Vec::new(); + TopLevelVarScopedDeclarationsVisitor(&mut declarations).visit(node.into()); + declarations +} + +/// The [`Visitor`] used to obtain the top level var scoped declarations of a node. +#[derive(Debug)] +struct TopLevelVarScopedDeclarationsVisitor<'a>(&'a mut Vec); + +impl<'ast> Visitor<'ast> for TopLevelVarScopedDeclarationsVisitor<'_> { + type BreakTy = Infallible; + + fn visit_statement_list_item( + &mut self, + node: &'ast StatementListItem, + ) -> ControlFlow { + match node { + StatementListItem::Declaration(d) => { + match d { + Declaration::Function(f) => { + self.0.push(VarScopedDeclaration::Function(f.clone())); + } + Declaration::Generator(f) => { + self.0.push(VarScopedDeclaration::Generator(f.clone())); + } + Declaration::AsyncFunction(f) => { + self.0.push(VarScopedDeclaration::AsyncFunction(f.clone())); + } + Declaration::AsyncGenerator(f) => { + self.0.push(VarScopedDeclaration::AsyncGenerator(f.clone())); + } + _ => {} + } + ControlFlow::Continue(()) + } + StatementListItem::Statement(Statement::Labelled(s)) => self.visit(s), + StatementListItem::Statement(s) => { + VarScopedDeclarationsVisitor(self.0).visit(s); + ControlFlow::Continue(()) + } + } + } + + fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow { + match node { + LabelledItem::Statement(Statement::Labelled(s)) => self.visit(s), + LabelledItem::Statement(s) => { + VarScopedDeclarationsVisitor(self.0).visit(s); + ControlFlow::Continue(()) + } + LabelledItem::Function(f) => { + self.0.push(VarScopedDeclaration::Function(f.clone())); + ControlFlow::Continue(()) + } + } + } +} diff --git a/boa_ast/src/statement_list.rs b/boa_ast/src/statement_list.rs index 5e4e2c43c7c..1f9d927ef88 100644 --- a/boa_ast/src/statement_list.rs +++ b/boa_ast/src/statement_list.rs @@ -9,8 +9,6 @@ use crate::{ use boa_interner::{Interner, ToIndentedString}; use core::ops::ControlFlow; -use std::cmp::Ordering; - /// An item inside a [`StatementList`] Parse Node, as defined by the [spec]. /// /// Items in a `StatementList` can be either [`Declaration`]s (functions, classes, let/const declarations) @@ -27,34 +25,6 @@ pub enum StatementListItem { Declaration(Declaration), } -impl StatementListItem { - /// Returns a node ordering based on the hoistability of each statement. - #[must_use] - pub const fn hoistable_order(a: &Self, b: &Self) -> Ordering { - match (a, b) { - ( - _, - Self::Declaration( - Declaration::Function(_) - | Declaration::Generator(_) - | Declaration::AsyncFunction(_) - | Declaration::AsyncGenerator(_), - ), - ) => Ordering::Greater, - ( - Self::Declaration( - Declaration::Function(_) - | Declaration::Generator(_) - | Declaration::AsyncFunction(_) - | Declaration::AsyncGenerator(_), - ), - _, - ) => Ordering::Less, - (_, _) => Ordering::Equal, - } - } -} - impl ToIndentedString for StatementListItem { /// Creates a string of the value of the node with the given indentation. For example, an /// indent level of 2 would produce this: diff --git a/boa_ast/src/visitor.rs b/boa_ast/src/visitor.rs index 1192157184e..df017da17b4 100644 --- a/boa_ast/src/visitor.rs +++ b/boa_ast/src/visitor.rs @@ -142,6 +142,7 @@ node_ref! { Break, Return, Labelled, + With, Throw, Try, Identifier, @@ -336,6 +337,7 @@ pub trait Visitor<'ast>: Sized { NodeRef::Break(n) => self.visit_break(n), NodeRef::Return(n) => self.visit_return(n), NodeRef::Labelled(n) => self.visit_labelled(n), + NodeRef::With(n) => self.visit_with(n), NodeRef::Throw(n) => self.visit_throw(n), NodeRef::Try(n) => self.visit_try(n), NodeRef::Identifier(n) => self.visit_identifier(n), @@ -532,6 +534,7 @@ pub trait VisitorMut<'ast>: Sized { 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::With(n) => self.visit_with_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), diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 25e9fe863a0..3bb21423b14 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -11,12 +11,10 @@ use crate::{ builtins::BuiltInObject, bytecompiler::ByteCompiler, context::intrinsics::Intrinsics, - environments::Environment, error::JsNativeError, object::JsObject, realm::Realm, Context, - JsArgs, JsResult, JsString, JsValue, -}; -use boa_ast::operations::{ - contains, contains_arguments, top_level_var_declared_names, ContainsSymbol, + environments::Environment, error::JsNativeError, object::JsObject, realm::Realm, vm::Opcode, + Context, JsArgs, JsResult, JsString, JsValue, }; +use boa_ast::operations::{contains, contains_arguments, ContainsSymbol}; use boa_gc::Gc; use boa_interner::Sym; use boa_parser::{Parser, Source}; @@ -224,35 +222,27 @@ impl Eval { } }); - // Only need to check on non-strict mode since strict mode automatically creates a function - // 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. - if let Some(name) = context - .vm - .environments - .has_lex_binding_until_function_environment(&top_level_var_declared_names(&body)) - { - 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()); - } - } + let mut compiler = ByteCompiler::new( + Sym::MAIN, + body.strict(), + false, + context.vm.environments.current_compile_environment(), + context, + ); + + compiler.push_compile_environment(strict); + let push_env = compiler.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); + + compiler.eval_declaration_instantiation(&body, strict)?; + compiler.compile_statement_list(&body, true); + + let env_info = compiler.pop_compile_environment(); + compiler.patch_jump_with_target(push_env.0, env_info.num_bindings as u32); + compiler.patch_jump_with_target(push_env.1, env_info.index as u32); + compiler.emit_opcode(Opcode::PopEnvironment); + + let code_block = Gc::new(compiler.finish()); - // TODO: check if private identifiers inside `eval` are valid. - - // Compile and execute the eval statement list. - let code_block = { - let mut compiler = ByteCompiler::new( - Sym::MAIN, - body.strict(), - false, - context.vm.environments.current_compile_environment(), - context, - ); - compiler.compile_statement_list_with_new_declarative(&body, true, strict); - Gc::new(compiler.finish()) - }; // Indirect calls don't need extensions, because a non-strict indirect call modifies only // the global object. // Strict direct calls also don't need extensions, since all strict eval calls push a new diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index b882a5715fa..8b9f2e8490c 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -176,7 +176,7 @@ impl Arguments { if property_index >= len { break; } - let binding_index = bindings.len() + 1; + let binding_index = bindings.len(); let entry = bindings .entry(name) .or_insert((binding_index, property_index)); diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index d079d5e7eda..7ecb727bedf 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -123,7 +123,7 @@ impl Json { context.realm().environment().compile_env(), context, ); - compiler.compile_statement_list(&statement_list, true, false); + compiler.compile_statement_list(&statement_list, true); Gc::new(compiler.finish()) }; let unfiltered = context.execute(code_block)?; diff --git a/boa_engine/src/bytecompiler/class.rs b/boa_engine/src/bytecompiler/class.rs index c9efa579cfb..2374e1640d9 100644 --- a/boa_engine/src/bytecompiler/class.rs +++ b/boa_engine/src/bytecompiler/class.rs @@ -1,10 +1,8 @@ use super::{ByteCompiler, Literal, NodeKind}; use crate::vm::{BindingOpcode, Opcode}; use boa_ast::{ - declaration::Binding, expression::Identifier, - function::{Class, ClassElement}, - operations::bound_names, + function::{Class, ClassElement, FormalParameterList}, property::{MethodDefinition, PropertyName}, }; use boa_gc::Gc; @@ -40,51 +38,22 @@ impl ByteCompiler<'_, '_> { if let Some(expr) = class.constructor() { compiler.length = expr.parameters().length(); compiler.params = expr.parameters().clone(); - compiler.create_mutable_binding(Sym::ARGUMENTS.into(), false, false); - compiler.arguments_binding = - Some(compiler.initialize_mutable_binding(Sym::ARGUMENTS.into(), false)); - for parameter in expr.parameters().as_ref() { - if parameter.is_rest_param() { - compiler.emit_opcode(Opcode::RestParameterInit); - } - match parameter.variable().binding() { - Binding::Identifier(ident) => { - compiler.create_mutable_binding(*ident, false, false); - if let Some(init) = parameter.variable().init() { - let skip = - compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); - compiler.compile_expr(init, true); - compiler.patch_jump(skip); - } - compiler.emit_binding(BindingOpcode::InitArg, *ident); - } - Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - compiler.create_mutable_binding(ident, false, false); - } - compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg); - } - } - } - if !expr.parameters().has_rest_parameter() { - compiler.emit_opcode(Opcode::RestParameterPop); - } - let env_label = if expr.parameters().has_expressions() { - compiler.num_bindings = compiler.current_environment.borrow().num_bindings(); - compiler.push_compile_environment(true); - compiler.function_environment_push_location = compiler.next_opcode_location(); - Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)) - } else { - None - }; - compiler.compile_statement_list(expr.body(), false, false); + let (env_labels, _) = compiler.function_declaration_instantiation( + expr.body(), + expr.parameters(), + false, + true, + false, + ); + + compiler.compile_statement_list(expr.body(), false); let env_info = compiler.pop_compile_environment(); - if let Some(env_label) = env_label { - compiler.patch_jump_with_target(env_label.0, env_info.num_bindings as u32); - compiler.patch_jump_with_target(env_label.1, env_info.index as u32); + if let Some(env_labels) = env_labels { + compiler.patch_jump_with_target(env_labels.0, env_info.num_bindings as u32); + compiler.patch_jump_with_target(env_labels.1, env_info.index as u32); compiler.pop_compile_environment(); } else { compiler.num_bindings = env_info.num_bindings; @@ -396,8 +365,16 @@ impl ByteCompiler<'_, '_> { compiler.push_compile_environment(false); compiler.create_immutable_binding(class_name.into(), true); compiler.push_compile_environment(true); - compiler.create_declarations(statement_list, false); - compiler.compile_statement_list(statement_list, false, false); + + compiler.function_declaration_instantiation( + statement_list, + &FormalParameterList::default(), + false, + true, + false, + ); + + compiler.compile_statement_list(statement_list, false); let env_info = compiler.pop_compile_environment(); compiler.pop_compile_environment(); compiler.num_bindings = env_info.num_bindings; diff --git a/boa_engine/src/bytecompiler/declarations.rs b/boa_engine/src/bytecompiler/declarations.rs new file mode 100644 index 00000000000..39fc6437537 --- /dev/null +++ b/boa_engine/src/bytecompiler/declarations.rs @@ -0,0 +1,917 @@ +use crate::{ + bytecompiler::{ByteCompiler, FunctionCompiler, FunctionSpec, Label, NodeKind}, + vm::{create_function_object_fast, create_generator_function_object, BindingOpcode, Opcode}, + JsNativeError, JsResult, +}; +use boa_ast::{ + declaration::{Binding, LexicalDeclaration, VariableList}, + function::FormalParameterList, + operations::{ + bound_names, lexically_scoped_declarations, top_level_lexically_declared_names, + top_level_var_declared_names, top_level_var_scoped_declarations, VarScopedDeclaration, + }, + visitor::NodeRef, + Declaration, StatementList, StatementListItem, +}; +use boa_interner::Sym; + +impl ByteCompiler<'_, '_> { + /// `GlobalDeclarationInstantiation ( script, env )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation + pub(crate) fn global_declaration_instantiation( + &mut self, + script: &StatementList, + ) -> JsResult<()> { + // Note: Step 1-4 Are currently handled while parsing. + // 1. Let lexNames be the LexicallyDeclaredNames of script. + // 2. Let varNames be the VarDeclaredNames of script. + // 3. For each element name of lexNames, do + // a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception. + // b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + // c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name). + // d. If hasRestrictedGlobal is true, throw a SyntaxError exception. + // 4. For each element name of varNames, do + // a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + + // 5. Let varDeclarations be the VarScopedDeclarations of script. + // Note: VarScopedDeclarations for a Script node is TopLevelVarScopedDeclarations. + let var_declarations = top_level_var_scoped_declarations(script); + + // 6. Let functionsToInitialize be a new empty List. + let mut functions_to_initialize = Vec::new(); + + // 7. Let declaredFunctionNames be a new empty List. + let mut declared_function_names = Vec::new(); + + // 8. For each element d of varDeclarations, in reverse List order, do + for declaration in var_declarations.iter().rev() { + // a. If d is not either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then + // a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. + // a.ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used. + let name = match declaration { + VarScopedDeclaration::Function(f) => f.name(), + VarScopedDeclaration::Generator(f) => f.name(), + VarScopedDeclaration::AsyncFunction(f) => f.name(), + VarScopedDeclaration::AsyncGenerator(f) => f.name(), + VarScopedDeclaration::VariableDeclaration(_) => { + continue; + } + }; + + // a.iii. Let fn be the sole element of the BoundNames of d. + let name = name.expect("function declaration must have a name"); + + // a.iv. If declaredFunctionNames does not contain fn, then + if !declared_function_names.contains(&name) { + // 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn). + let fn_definable = self.context.can_declare_global_function(name)?; + + // 2. If fnDefinable is false, throw a TypeError exception. + if !fn_definable { + return Err(JsNativeError::typ() + .with_message("cannot declare global function") + .into()); + } + + // 3. Append fn to declaredFunctionNames. + declared_function_names.push(name); + + // 4. Insert d as the first element of functionsToInitialize. + functions_to_initialize.push(declaration.clone()); + } + } + + functions_to_initialize.reverse(); + + // 9. Let declaredVarNames be a new empty List. + let mut declared_var_names = Vec::new(); + + // 10. For each element d of varDeclarations, do + // a. If d is either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then + for declaration in var_declarations { + let VarScopedDeclaration::VariableDeclaration(declaration) = declaration else { + continue; + }; + + // i. For each String vn of the BoundNames of d, do + for name in bound_names(&declaration) { + // 1. If declaredFunctionNames does not contain vn, then + if !declared_function_names.contains(&name) { + // a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn). + let definable = self.context.can_declare_global_var(name)?; + + // b. If vnDefinable is false, throw a TypeError exception. + if !definable { + return Err(JsNativeError::typ() + .with_message("cannot declare global variable") + .into()); + } + + // c. If declaredVarNames does not contain vn, then + if !declared_var_names.contains(&name) { + // i. Append vn to declaredVarNames. + declared_var_names.push(name); + } + } + } + } + + // 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object. + // However, if the global object is a Proxy exotic object it may exhibit behaviours + // that cause abnormal terminations in some of the following steps. + // 12. NOTE: Annex B.3.2.2 adds additional steps at this point. + // TODO: Support B.3.2.2. + + // 13. Let lexDeclarations be the LexicallyScopedDeclarations of script. + // 14. Let privateEnv be null. + // 15. For each element d of lexDeclarations, do + for statement in script.statements() { + // a. NOTE: Lexically declared names are only instantiated here but not initialized. + // b. For each element dn of the BoundNames of d, do + // i. If IsConstantDeclaration of d is true, then + // 1. Perform ? env.CreateImmutableBinding(dn, true). + // ii. Else, + // 1. Perform ? env.CreateMutableBinding(dn, false). + if let StatementListItem::Declaration(declaration) = statement { + match declaration { + Declaration::Class(class) => { + for name in bound_names(class) { + self.create_mutable_binding(name, false); + } + } + Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { + for name in bound_names(declaration) { + self.create_mutable_binding(name, false); + } + } + Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { + for name in bound_names(declaration) { + self.create_immutable_binding(name, true); + } + } + _ => {} + } + } + } + + // 16. For each Parse Node f of functionsToInitialize, do + for function in functions_to_initialize { + // a. Let fn be the sole element of the BoundNames of f. + let (name, generator, r#async, parameters, body) = match &function { + VarScopedDeclaration::Function(f) => { + (f.name(), false, false, f.parameters(), f.body()) + } + VarScopedDeclaration::Generator(f) => { + (f.name(), true, false, f.parameters(), f.body()) + } + VarScopedDeclaration::AsyncFunction(f) => { + (f.name(), false, true, f.parameters(), f.body()) + } + VarScopedDeclaration::AsyncGenerator(f) => { + (f.name(), true, true, f.parameters(), f.body()) + } + VarScopedDeclaration::VariableDeclaration(_) => { + continue; + } + }; + let name = name.expect("function declaration must have a name"); + + let code = FunctionCompiler::new() + .name(name.sym()) + .generator(generator) + .r#async(r#async) + .strict(self.strict) + .binding_identifier(Some(name.sym())) + .compile( + parameters, + body, + self.current_environment.clone(), + self.context, + ); + + // b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv. + let function = if generator { + create_generator_function_object(code, r#async, false, None, self.context) + } else { + create_function_object_fast(code, r#async, false, false, self.context) + }; + + // c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false). + self.context + .create_global_function_binding(name, function, false)?; + } + + // 17. For each String vn of declaredVarNames, do + for var in declared_var_names { + // a. Perform ? env.CreateGlobalVarBinding(vn, false). + self.context.create_global_var_binding(var, false)?; + } + + // 18. Return unused. + Ok(()) + } + + /// `BlockDeclarationInstantiation ( code, env )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-blockdeclarationinstantiation + pub(crate) fn block_declaration_instantiation<'a, N>(&mut self, block: &'a N) + where + &'a N: Into>, + { + // 1. Let declarations be the LexicallyScopedDeclarations of code. + let declarations = lexically_scoped_declarations(block); + + // 2. Let privateEnv be the running execution context's PrivateEnvironment. + // Note: Private environments are currently handled differently. + + // 3. For each element d of declarations, do + for d in &declarations { + // i. If IsConstantDeclaration of d is true, then + if let Declaration::Lexical(LexicalDeclaration::Const(d)) = d { + // a. For each element dn of the BoundNames of d, do + for dn in bound_names::<'_, VariableList>(d) { + // 1. Perform ! env.CreateImmutableBinding(dn, true). + self.create_immutable_binding(dn, true); + } + } + // ii. Else, + else { + // a. For each element dn of the BoundNames of d, do + for dn in bound_names::<'_, Declaration>(d) { + // 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6. + // TODO: Support B.3.2.6. + self.create_mutable_binding(dn, false); + } + } + } + + // Note: Not sure if the spec is wrong here or if our implementation just differs too much, + // but we need 3.a to be finished for all declarations before 3.b can be done. + + // b. If d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then + // i. Let fn be the sole element of the BoundNames of d. + // ii. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. + // iii. Perform ! env.InitializeBinding(fn, fo). NOTE: This step is replaced in section B.3.2.6. + // TODO: Support B.3.2.6. + for d in &declarations { + match d { + Declaration::Function(function) => { + self.function(function.into(), NodeKind::Declaration, false); + } + Declaration::Generator(function) => { + self.function(function.into(), NodeKind::Declaration, false); + } + Declaration::AsyncFunction(function) => { + self.function(function.into(), NodeKind::Declaration, false); + } + Declaration::AsyncGenerator(function) => { + self.function(function.into(), NodeKind::Declaration, false); + } + _ => {} + } + } + + // 4. Return unused. + } + + /// `EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation + pub(crate) fn eval_declaration_instantiation( + &mut self, + body: &StatementList, + strict: bool, + ) -> JsResult<()> { + let var_environment_is_global = self + .context + .vm + .environments + .is_next_outer_function_environment_global() + && !strict; + + // 2. Let varDeclarations be the VarScopedDeclarations of body. + let var_declarations = top_level_var_scoped_declarations(body); + + // 3. If strict is false, then + if !strict { + // 1. Let varNames be the VarDeclaredNames of body. + let var_names = top_level_var_declared_names(body); + + // a. If varEnv is a Global Environment Record, then + // i. For each element name of varNames, do + // 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception. + // 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration. + // b. Let thisEnv be lexEnv. + // c. Assert: The following loop will terminate. + // d. Repeat, while thisEnv is not varEnv, + // i. If thisEnv is not an Object Environment Record, then + // 1. NOTE: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts. + // 2. For each element name of varNames, do + // a. If ! thisEnv.HasBinding(name) is true, then + // i. Throw a SyntaxError exception. + // ii. NOTE: Annex B.3.4 defines alternate semantics for the above step. + // b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration. + // ii. Set thisEnv to thisEnv.[[OuterEnv]]. + if let Some(name) = self + .context + .vm + .environments + .has_lex_binding_until_function_environment(&var_names) + { + let name = self.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()); + } + } + + // 4. Let privateIdentifiers be a new empty List. + // 5. Let pointer be privateEnv. + // 6. Repeat, while pointer is not null, + // a. For each Private Name binding of pointer.[[Names]], do + // i. If privateIdentifiers does not contain binding.[[Description]], append binding.[[Description]] to privateIdentifiers. + // b. Set pointer to pointer.[[OuterPrivateEnvironment]]. + // 7. If AllPrivateIdentifiersValid of body with argument privateIdentifiers is false, throw a SyntaxError exception. + + // 8. Let functionsToInitialize be a new empty List. + let mut functions_to_initialize = Vec::new(); + + // 9. Let declaredFunctionNames be a new empty List. + let mut declared_function_names = Vec::new(); + + // 10. For each element d of varDeclarations, in reverse List order, do + for declaration in var_declarations.iter().rev() { + // a. If d is not either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then + // a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. + // a.ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used. + let name = match &declaration { + VarScopedDeclaration::Function(f) => f.name(), + VarScopedDeclaration::Generator(f) => f.name(), + VarScopedDeclaration::AsyncFunction(f) => f.name(), + VarScopedDeclaration::AsyncGenerator(f) => f.name(), + VarScopedDeclaration::VariableDeclaration(_) => { + continue; + } + }; + + // a.iii. Let fn be the sole element of the BoundNames of d. + let name = name.expect("function declaration must have a name"); + + // a.iv. If declaredFunctionNames does not contain fn, then + if !declared_function_names.contains(&name) { + // 1. If varEnv is a Global Environment Record, then + if var_environment_is_global { + // a. Let fnDefinable be ? varEnv.CanDeclareGlobalFunction(fn). + let fn_definable = self.context.can_declare_global_function(name)?; + + // b. If fnDefinable is false, throw a TypeError exception. + if !fn_definable { + return Err(JsNativeError::typ() + .with_message("cannot declare global function") + .into()); + } + } + + // 2. Append fn to declaredFunctionNames. + declared_function_names.push(name); + + // 3. Insert d as the first element of functionsToInitialize. + functions_to_initialize.push(declaration.clone()); + } + } + + functions_to_initialize.reverse(); + + // 11. NOTE: Annex B.3.2.3 adds additional steps at this point. + // TODO: Support B.3.2.3 + + // 12. Let declaredVarNames be a new empty List. + let mut declared_var_names = Vec::new(); + + // 13. For each element d of varDeclarations, do + for declaration in var_declarations { + // a. If d is either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then + let VarScopedDeclaration::VariableDeclaration(declaration) = declaration else { + continue; + }; + + // a.i. For each String vn of the BoundNames of d, do + for name in bound_names(&declaration) { + // 1. If declaredFunctionNames does not contain vn, then + if !declared_function_names.contains(&name) { + // a. If varEnv is a Global Environment Record, then + if var_environment_is_global { + // i. Let vnDefinable be ? varEnv.CanDeclareGlobalVar(vn). + let vn_definable = self.context.can_declare_global_var(name)?; + + // ii. If vnDefinable is false, throw a TypeError exception. + if !vn_definable { + return Err(JsNativeError::typ() + .with_message("cannot declare global variable") + .into()); + } + } + + // b. If declaredVarNames does not contain vn, then + if !declared_var_names.contains(&name) { + // i. Append vn to declaredVarNames. + declared_var_names.push(name); + } + } + } + } + + // 14. NOTE: No abnormal terminations occur after this algorithm step unless varEnv is a + // Global Environment Record and the global object is a Proxy exotic object. + + // 15. Let lexDeclarations be the LexicallyScopedDeclarations of body. + // 16. For each element d of lexDeclarations, do + for statement in body.statements() { + // a. NOTE: Lexically declared names are only instantiated here but not initialized. + // b. For each element dn of the BoundNames of d, do + // i. If IsConstantDeclaration of d is true, then + // 1. Perform ? lexEnv.CreateImmutableBinding(dn, true). + // ii. Else, + // 1. Perform ? lexEnv.CreateMutableBinding(dn, false). + if let StatementListItem::Declaration(declaration) = statement { + match declaration { + Declaration::Class(class) => { + for name in bound_names(class) { + self.create_mutable_binding(name, false); + } + } + Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { + for name in bound_names(declaration) { + self.create_mutable_binding(name, false); + } + } + Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { + for name in bound_names(declaration) { + self.create_immutable_binding(name, true); + } + } + _ => {} + } + } + } + + // 17. For each Parse Node f of functionsToInitialize, do + for function in functions_to_initialize { + // a. Let fn be the sole element of the BoundNames of f. + let (name, generator, r#async, parameters, body) = match &function { + VarScopedDeclaration::Function(f) => { + (f.name(), false, false, f.parameters(), f.body()) + } + VarScopedDeclaration::Generator(f) => { + (f.name(), true, false, f.parameters(), f.body()) + } + VarScopedDeclaration::AsyncFunction(f) => { + (f.name(), false, true, f.parameters(), f.body()) + } + VarScopedDeclaration::AsyncGenerator(f) => { + (f.name(), true, true, f.parameters(), f.body()) + } + VarScopedDeclaration::VariableDeclaration(_) => { + continue; + } + }; + let name = name.expect("function declaration must have a name"); + let code = FunctionCompiler::new() + .name(name.sym()) + .generator(generator) + .r#async(r#async) + .strict(self.strict) + .binding_identifier(Some(name.sym())) + .compile( + parameters, + body, + self.context.vm.environments.current_compile_environment(), + self.context, + ); + + // c. If varEnv is a Global Environment Record, then + if var_environment_is_global { + // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. + let function = if generator { + create_generator_function_object(code, r#async, false, None, self.context) + } else { + create_function_object_fast(code, r#async, false, false, self.context) + }; + + // i. Perform ? varEnv.CreateGlobalFunctionBinding(fn, fo, true). + self.context + .create_global_function_binding(name, function, true)?; + } + // d. Else, + else { + // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. + let index = self.functions.len() as u32; + self.functions.push(code); + if r#async && generator { + self.emit(Opcode::GetGeneratorAsync, &[index]); + } else if generator { + self.emit(Opcode::GetGenerator, &[index]); + } else if r#async { + self.emit(Opcode::GetFunctionAsync, &[index]); + } else { + self.emit(Opcode::GetFunction, &[index]); + } + self.emit_u8(0); + + // i. Let bindingExists be ! varEnv.HasBinding(fn). + let binding_exists = self.has_binding_eval(name, strict); + + // ii. If bindingExists is false, then + // iii. Else, + if binding_exists { + // 1. Perform ! varEnv.SetMutableBinding(fn, fo, false). + let binding = self.set_mutable_binding(name); + let index = self.get_or_insert_binding(binding); + self.emit(Opcode::SetName, &[index]); + } else { + // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. + // 2. Perform ! varEnv.CreateMutableBinding(fn, true). + // 3. Perform ! varEnv.InitializeBinding(fn, fo). + self.create_mutable_binding(name, !strict); + let binding = self.initialize_mutable_binding(name, !strict); + let index = self.get_or_insert_binding(binding); + self.emit(Opcode::DefInitVar, &[index]); + } + } + } + + // 18. For each String vn of declaredVarNames, do + for name in declared_var_names { + // a. If varEnv is a Global Environment Record, then + if var_environment_is_global { + // i. Perform ? varEnv.CreateGlobalVarBinding(vn, true). + self.context.create_global_var_binding(name, true)?; + } + // b. Else, + else { + // i. Let bindingExists be ! varEnv.HasBinding(vn). + let binding_exists = self.has_binding_eval(name, strict); + + // ii. If bindingExists is false, then + if !binding_exists { + // 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14. + // 2. Perform ! varEnv.CreateMutableBinding(vn, true). + // 3. Perform ! varEnv.InitializeBinding(vn, undefined). + self.create_mutable_binding(name, !strict); + let binding = self.initialize_mutable_binding(name, !strict); + let index = self.get_or_insert_binding(binding); + self.emit_opcode(Opcode::PushUndefined); + self.emit(Opcode::DefInitVar, &[index]); + } + } + } + + // 19. Return unused. + Ok(()) + } + + /// `FunctionDeclarationInstantiation ( func, argumentsList )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation + pub(crate) fn function_declaration_instantiation( + &mut self, + code: &StatementList, + formals: &FormalParameterList, + arrow: bool, + strict: bool, + generator: bool, + ) -> (Option<(Label, Label)>, bool) { + let mut env_labels = None; + let mut additional_env = false; + + // 1. Let calleeContext be the running execution context. + // 2. Let code be func.[[ECMAScriptCode]]. + // 3. Let strict be func.[[Strict]]. + // 4. Let formals be func.[[FormalParameters]]. + + // 5. Let parameterNames be the BoundNames of formals. + let mut parameter_names = bound_names(formals); + + // 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false. + // let has_duplicates = formals.has_duplicates(); + + // 7. Let simpleParameterList be IsSimpleParameterList of formals. + // let simple_parameter_list = formals.is_simple(); + + // 8. Let hasParameterExpressions be ContainsExpression of formals. + let has_parameter_expressions = formals.has_expressions(); + + // 9. Let varNames be the VarDeclaredNames of code. + let var_names = top_level_var_declared_names(code); + + // 10. Let varDeclarations be the VarScopedDeclarations of code. + let var_declarations = top_level_var_scoped_declarations(code); + + // 11. Let lexicalNames be the LexicallyDeclaredNames of code. + let lexical_names = top_level_lexically_declared_names(code); + + // 12. Let functionNames be a new empty List. + let mut function_names = Vec::new(); + + // 13. Let functionsToInitialize be a new empty List. + let mut functions_to_initialize = Vec::new(); + + // 14. For each element d of varDeclarations, in reverse List order, do + for declaration in var_declarations.iter().rev() { + // a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then + // a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration. + let function = match declaration { + VarScopedDeclaration::Function(f) => FunctionSpec::from(f), + VarScopedDeclaration::Generator(f) => FunctionSpec::from(f), + VarScopedDeclaration::AsyncFunction(f) => FunctionSpec::from(f), + VarScopedDeclaration::AsyncGenerator(f) => FunctionSpec::from(f), + VarScopedDeclaration::VariableDeclaration(_) => continue, + }; + + // a.ii. Let fn be the sole element of the BoundNames of d. + let name = function + .name + .expect("function declaration must have a name"); + + // a.iii. If functionNames does not contain fn, then + if !function_names.contains(&name) { + // 1. Insert fn as the first element of functionNames. + function_names.push(name); + + // 2. NOTE: If there are multiple function declarations for the same name, the last declaration is used. + // 3. Insert d as the first element of functionsToInitialize. + functions_to_initialize.push(function); + } + } + + function_names.reverse(); + functions_to_initialize.reverse(); + + //15. Let argumentsObjectNeeded be true. + let mut arguments_object_needed = true; + + let arguments = Sym::ARGUMENTS.into(); + + // 16. If func.[[ThisMode]] is lexical, then + // 17. Else if parameterNames contains "arguments", then + if arrow || parameter_names.contains(&arguments) { + // 16.a. NOTE: Arrow functions never have an arguments object. + // 16.b. Set argumentsObjectNeeded to false. + // 17.a. Set argumentsObjectNeeded to false. + arguments_object_needed = false; + } + // 18. Else if hasParameterExpressions is false, then + else if !has_parameter_expressions { + //a. If functionNames contains "arguments" or lexicalNames contains "arguments", then + if function_names.contains(&arguments) || lexical_names.contains(&arguments) { + // i. Set argumentsObjectNeeded to false. + arguments_object_needed = false; + } + } + + // 19. If strict is true or hasParameterExpressions is false, then + // a. NOTE: Only a single Environment Record is needed for the parameters, + // since calls to eval in strict mode code cannot create new bindings which are visible outside of the eval. + // b. Let env be the LexicalEnvironment of calleeContext. + // 20. Else, + if !strict && has_parameter_expressions { + // a. NOTE: A separate Environment Record is needed to ensure that bindings created by + // direct eval calls in the formal parameter list are outside the environment where parameters are declared. + // b. Let calleeEnv be the LexicalEnvironment of calleeContext. + // c. Let env be NewDeclarativeEnvironment(calleeEnv). + // d. Assert: The VariableEnvironment of calleeContext is calleeEnv. + // e. Set the LexicalEnvironment of calleeContext to env. + self.push_compile_environment(false); + additional_env = true; + } + + // 21. For each String paramName of parameterNames, do + for param_name in ¶meter_names { + // a. Let alreadyDeclared be ! env.HasBinding(paramName). + let already_declared = self.has_binding(*param_name); + + // b. NOTE: Early errors ensure that duplicate parameter names can only occur in non-strict + // functions that do not have parameter default values or rest parameters. + + // c. If alreadyDeclared is false, then + if !already_declared { + // i. Perform ! env.CreateMutableBinding(paramName, false). + self.create_mutable_binding(*param_name, false); + + // Note: These steps are not necessary in our implementation. + // ii. If hasDuplicates is true, then + // 1. Perform ! env.InitializeBinding(paramName, undefined). + } + } + + // 22. If argumentsObjectNeeded is true, then + if arguments_object_needed { + // Note: This happens at runtime. + // a. If strict is true or simpleParameterList is false, then + // i. Let ao be CreateUnmappedArgumentsObject(argumentsList). + // b. Else, + // i. NOTE: A mapped argument object is only provided for non-strict functions that don't have a rest parameter, any parameter default value initializers, or any destructured parameters. + // ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env). + + // c. If strict is true, then + if strict { + // i. Perform ! env.CreateImmutableBinding("arguments", false). + // ii. NOTE: In strict mode code early errors prevent attempting to assign to this binding, so its mutability is not observable. + self.create_immutable_binding(arguments, false); + self.arguments_binding = Some(self.initialize_immutable_binding(arguments)); + } + // d. Else, + else { + // i. Perform ! env.CreateMutableBinding("arguments", false). + self.create_mutable_binding(arguments, false); + self.arguments_binding = Some(self.initialize_mutable_binding(arguments, false)); + } + + // Note: This happens at runtime. + // e. Perform ! env.InitializeBinding("arguments", ao). + + // f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ». + parameter_names.push(arguments); + } + // 23. Else, + // a. Let parameterBindings be parameterNames. + let parameter_bindings = parameter_names; + + // 24. Let iteratorRecord be CreateListIteratorRecord(argumentsList). + // 25. If hasDuplicates is true, then + // a. Perform ? IteratorBindingInitialization of formals with arguments iteratorRecord and undefined. + // 26. Else, + // a. Perform ? IteratorBindingInitialization of formals with arguments iteratorRecord and env. + for parameter in formals.as_ref() { + if parameter.is_rest_param() { + self.emit_opcode(Opcode::RestParameterInit); + } + match parameter.variable().binding() { + Binding::Identifier(ident) => { + self.create_mutable_binding(*ident, false); + if let Some(init) = parameter.variable().init() { + let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); + self.compile_expr(init, true); + self.patch_jump(skip); + } + self.emit_binding(BindingOpcode::InitLet, *ident); + } + Binding::Pattern(pattern) => { + for ident in bound_names(pattern) { + self.create_mutable_binding(ident, false); + } + if let Some(init) = parameter.variable().init() { + let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); + self.compile_expr(init, true); + self.patch_jump(skip); + } + self.compile_declaration_pattern(pattern, BindingOpcode::InitLet); + } + } + } + if !formals.has_rest_parameter() { + self.emit_opcode(Opcode::RestParameterPop); + } + if generator { + self.emit_opcode(Opcode::PushUndefined); + self.emit_opcode(Opcode::Yield); + } + + // 27. If hasParameterExpressions is false, then + // 28. Else, + if has_parameter_expressions { + // a. NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body. + // b. Let varEnv be NewDeclarativeEnvironment(env). + // c. Set the VariableEnvironment of calleeContext to varEnv. + self.push_compile_environment(true); + self.function_environment_push_location = self.next_opcode_location(); + env_labels = Some(self.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)); + + // d. Let instantiatedVarNames be a new empty List. + let mut instantiated_var_names = Vec::new(); + + // e. For each element n of varNames, do + for n in var_names { + // i. If instantiatedVarNames does not contain n, then + if !instantiated_var_names.contains(&n) { + // 1. Append n to instantiatedVarNames. + instantiated_var_names.push(n); + + // 2. Perform ! varEnv.CreateMutableBinding(n, false). + self.create_mutable_binding(n, true); + + // 3. If parameterBindings does not contain n, or if functionNames contains n, then + if !parameter_bindings.contains(&n) || function_names.contains(&n) { + // a. Let initialValue be undefined. + self.emit_opcode(Opcode::PushUndefined); + } + // 4. Else, + else { + // a. Let initialValue be ! env.GetBindingValue(n, false). + let binding = self.get_binding_value(n); + let index = self.get_or_insert_binding(binding); + self.emit(Opcode::GetName, &[index]); + } + + // 5. Perform ! varEnv.InitializeBinding(n, initialValue). + let binding = self.initialize_mutable_binding(n, true); + let index = self.get_or_insert_binding(binding); + self.emit_opcode(Opcode::PushUndefined); + self.emit(Opcode::DefInitVar, &[index]); + + // 6. NOTE: A var with the same name as a formal parameter initially has + // the same value as the corresponding initialized parameter. + } + } + } else { + // a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars. + // b. Let instantiatedVarNames be a copy of the List parameterBindings. + let mut instantiated_var_names = parameter_bindings; + + // c. For each element n of varNames, do + for n in var_names { + // i. If instantiatedVarNames does not contain n, then + if !instantiated_var_names.contains(&n) { + // 1. Append n to instantiatedVarNames. + instantiated_var_names.push(n); + + // 2. Perform ! env.CreateMutableBinding(n, false). + self.create_mutable_binding(n, true); + + // 3. Perform ! env.InitializeBinding(n, undefined). + let binding = self.initialize_mutable_binding(n, true); + let index = self.get_or_insert_binding(binding); + self.emit_opcode(Opcode::PushUndefined); + self.emit(Opcode::DefInitVar, &[index]); + } + } + + // d. Let varEnv be env. + }; + + // 29. NOTE: Annex B.3.2.1 adds additional steps at this point. + // TODO: Support B.3.2.1 + + // 29. If strict is false, then + // a. Let lexEnv be NewDeclarativeEnvironment(varEnv). + // b. NOTE: Non-strict functions use a separate Environment Record for top-level lexical declarations so that a direct eval can determine whether any var scoped declarations introduced by the eval code conflict with pre-existing top-level lexically scoped declarations. This is not needed for strict functions because a strict direct eval always places all declarations into a new Environment Record. + // 31. Else, + // a. Let lexEnv be varEnv. + // 32. Set the LexicalEnvironment of calleeContext to lexEnv. + + // 33. Let lexDeclarations be the LexicallyScopedDeclarations of code. + // 34. For each element d of lexDeclarations, do + // a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized. + // b. For each element dn of the BoundNames of d, do + // i. If IsConstantDeclaration of d is true, then + // 1. Perform ! lexEnv.CreateImmutableBinding(dn, true). + // ii. Else, + // 1. Perform ! lexEnv.CreateMutableBinding(dn, false). + for statement in code.statements() { + if let StatementListItem::Declaration(declaration) = statement { + match declaration { + Declaration::Class(class) => { + for name in bound_names(class) { + self.create_mutable_binding(name, false); + } + } + Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { + for name in bound_names(declaration) { + self.create_mutable_binding(name, false); + } + } + Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { + for name in bound_names(declaration) { + self.create_immutable_binding(name, true); + } + } + _ => {} + } + } + } + + // 35. Let privateEnv be the PrivateEnvironment of calleeContext. + // 36. For each Parse Node f of functionsToInitialize, do + for function in functions_to_initialize { + // a. Let fn be the sole element of the BoundNames of f. + // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. + // c. Perform ! varEnv.SetMutableBinding(fn, fo, false). + self.function(function, NodeKind::Declaration, false); + } + + // 37. Return unused. + (env_labels, additional_env) + } +} diff --git a/boa_engine/src/bytecompiler/env.rs b/boa_engine/src/bytecompiler/env.rs index fd169c28e89..8db518ee634 100644 --- a/boa_engine/src/bytecompiler/env.rs +++ b/boa_engine/src/bytecompiler/env.rs @@ -1,14 +1,8 @@ +use super::ByteCompiler; +use crate::environments::{BindingLocator, CompileTimeEnvironment}; use boa_ast::expression::Identifier; use boa_gc::{Gc, GcRefCell}; -use crate::{ - environments::{BindingLocator, CompileTimeEnvironment}, - property::PropertyDescriptor, - JsString, JsValue, -}; - -use super::ByteCompiler; - /// Info returned by the [`ByteCompiler::pop_compile_environment`] method. #[derive(Debug, Clone, Copy)] pub(crate) struct PopEnvironmentInfo { @@ -64,47 +58,25 @@ impl ByteCompiler<'_, '_> { .has_binding_recursive(name) } + /// Check if a binding name exists in a environment. + /// If strict is `false` check until a function scope is reached. + pub(crate) fn has_binding_eval(&self, name: Identifier, strict: bool) -> bool { + self.current_environment + .borrow() + .has_binding_eval(name, strict) + } + /// Create a mutable binding at bytecode compile time. /// This function returns a syntax error, if the binding is a redeclaration. /// /// # Panics /// /// Panics if the global environment is not function scoped. - pub(crate) fn create_mutable_binding( - &mut self, - name: Identifier, - function_scope: bool, - configurable: bool, - ) { - if !self + pub(crate) fn create_mutable_binding(&mut self, name: Identifier, function_scope: bool) { + assert!(self .current_environment .borrow_mut() - .create_mutable_binding(name, function_scope) - { - let name_str = self - .context - .interner() - .resolve_expect(name.sym()) - .into_common::(false); - - let global_obj = self.context.global_object(); - - // TODO: defer global initialization to execution time. - if !global_obj - .has_own_property(name_str.clone(), self.context) - .unwrap_or_default() - { - global_obj.borrow_mut().insert( - name_str, - PropertyDescriptor::builder() - .value(JsValue::Undefined) - .writable(true) - .enumerable(true) - .configurable(configurable) - .build(), - ); - } - } + .create_mutable_binding(name, function_scope)); } /// Initialize a mutable binding at bytecode compile time and return it's binding locator. diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs index 5473d48f568..4a5370c4d7a 100644 --- a/boa_engine/src/bytecompiler/function.rs +++ b/boa_engine/src/bytecompiler/function.rs @@ -2,12 +2,10 @@ use crate::{ builtins::function::ThisMode, bytecompiler::ByteCompiler, environments::CompileTimeEnvironment, - vm::{BindingOpcode, CodeBlock, Opcode}, + vm::{CodeBlock, Opcode}, Context, }; -use boa_ast::{ - declaration::Binding, function::FormalParameterList, operations::bound_names, StatementList, -}; +use boa_ast::{function::FormalParameterList, StatementList}; use boa_gc::{Gc, GcRefCell}; use boa_interner::Sym; @@ -119,85 +117,23 @@ impl FunctionCompiler { // Function environment compiler.push_compile_environment(true); - // Only used to initialize bindings - if !self.strict && parameters.has_expressions() { - compiler.push_compile_environment(false); - }; - - // An arguments object is added when all of the following conditions are met - // - If not in an arrow function (10.2.11.16) - // - If the parameter list does not contain `arguments` (10.2.11.17) - // Note: This following just means, that we add an extra environment for the arguments. - // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18) - if !(self.arrow) && !parameters.has_arguments() { - let arguments = Sym::ARGUMENTS.into(); - compiler.arguments_binding = Some(if self.strict { - compiler.create_immutable_binding(arguments, true); - compiler.initialize_immutable_binding(arguments) - } else { - compiler.create_mutable_binding(arguments, false, false); - compiler.initialize_mutable_binding(arguments, false) - }); - } - - for parameter in parameters.as_ref() { - if parameter.is_rest_param() { - compiler.emit_opcode(Opcode::RestParameterInit); - } - - match parameter.variable().binding() { - Binding::Identifier(ident) => { - compiler.create_mutable_binding(*ident, false, false); - // TODO: throw custom error if ident is in init - if let Some(init) = parameter.variable().init() { - let skip = compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); - compiler.compile_expr(init, true); - compiler.patch_jump(skip); - } - compiler.emit_binding(BindingOpcode::InitLet, *ident); - } - Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - compiler.create_mutable_binding(ident, false, false); - } - // TODO: throw custom error if ident is in init - if let Some(init) = parameter.variable().init() { - let skip = compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); - compiler.compile_expr(init, true); - compiler.patch_jump(skip); - } - compiler.compile_declaration_pattern(pattern, BindingOpcode::InitLet); - } - } - } - - if !parameters.has_rest_parameter() { - compiler.emit_opcode(Opcode::RestParameterPop); - } - - let env_label = if parameters.has_expressions() { - compiler.push_compile_environment(true); - compiler.function_environment_push_location = compiler.next_opcode_location(); - Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)) - } else { - None - }; - - // When a generator object is created from a generator function, the generator executes until here to init parameters. - if self.generator { - compiler.emit_opcode(Opcode::PushUndefined); - compiler.emit_opcode(Opcode::Yield); - } + let (env_labels, additional_env) = compiler.function_declaration_instantiation( + body, + parameters, + self.arrow, + self.strict, + self.generator, + ); - compiler.compile_statement_list(body, false, false); + compiler.compile_statement_list(body, false); - if let Some(env_label) = env_label { + if let Some(env_labels) = env_labels { let env_info = compiler.pop_compile_environment(); - compiler.patch_jump_with_target(env_label.0, env_info.num_bindings as u32); - compiler.patch_jump_with_target(env_label.1, env_info.index as u32); + compiler.patch_jump_with_target(env_labels.0, env_info.num_bindings as u32); + compiler.patch_jump_with_target(env_labels.1, env_info.index as u32); } - if !self.strict && parameters.has_expressions() { + if additional_env { compiler.parameters_env_bindings = Some(compiler.pop_compile_environment().num_bindings); } diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 3e02ea1ac24..5dfe0ab96d0 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -2,6 +2,7 @@ mod class; mod declaration; +mod declarations; mod env; mod expression; mod function; @@ -27,7 +28,6 @@ use boa_ast::{ ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, FormalParameterList, Function, Generator, PrivateName, }, - operations::bound_names, pattern::Pattern, Declaration, Expression, Statement, StatementList, StatementListItem, }; @@ -40,7 +40,7 @@ pub(crate) use jump_control::JumpControlInfo; /// Describes how a node has been defined in the source code. #[derive(Debug, Clone, Copy, PartialEq)] -enum NodeKind { +pub(crate) enum NodeKind { Declaration, Expression, } @@ -59,9 +59,9 @@ enum FunctionKind { /// Describes the complete specification of a function node. #[derive(Debug, Clone, Copy, PartialEq)] #[allow(single_use_lifetimes)] -struct FunctionSpec<'a> { +pub(crate) struct FunctionSpec<'a> { kind: FunctionKind, - name: Option, + pub(crate) name: Option, parameters: &'a FormalParameterList, body: &'a StatementList, has_binding_identifier: bool, @@ -286,6 +286,7 @@ pub struct ByteCompiler<'ctx, 'host> { jump_info: Vec, in_async_generator: bool, json_parse: bool, + // TODO: remove when we separate scripts from the context context: &'ctx mut Context<'host>, } @@ -381,7 +382,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { } #[inline] - fn get_or_insert_binding(&mut self, binding: BindingLocator) -> u32 { + pub(crate) fn get_or_insert_binding(&mut self, binding: BindingLocator) -> u32 { if let Some(index) = self.bindings_map.get(&binding) { return *index; } @@ -418,11 +419,6 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { let index = self.get_or_insert_binding(binding); self.emit(Opcode::DefInitLet, &[index]); } - BindingOpcode::InitArg => { - let binding = self.initialize_mutable_binding(name, true); - let index = self.get_or_insert_binding(binding); - self.emit(Opcode::DefInitArg, &[index]); - } BindingOpcode::InitConst => { let binding = self.initialize_immutable_binding(name); let index = self.get_or_insert_binding(binding); @@ -441,7 +437,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { self.bytecode.len() as u32 } - fn emit(&mut self, opcode: Opcode, operands: &[u32]) { + pub(crate) fn emit(&mut self, opcode: Opcode, operands: &[u32]) { self.emit_opcode(opcode); for operand in operands { self.emit_u32(*operand); @@ -460,7 +456,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { self.bytecode.extend(value.to_ne_bytes()); } - fn emit_opcode(&mut self, opcode: Opcode) { + pub(crate) fn emit_opcode(&mut self, opcode: Opcode) { self.emit_u8(opcode as u8); } @@ -540,13 +536,13 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { /// Emit an opcode with two dummy operands. /// Return the `Label`s of the two operands. - fn emit_opcode_with_two_operands(&mut self, opcode: Opcode) -> (Label, Label) { + pub(crate) fn emit_opcode_with_two_operands(&mut self, opcode: Opcode) -> (Label, Label) { let index = self.next_opcode_location(); self.emit(opcode, &[Self::DUMMY_ADDRESS, Self::DUMMY_ADDRESS]); (Label { index }, Label { index: index + 4 }) } - fn patch_jump_with_target(&mut self, label: Label, target: u32) { + pub(crate) fn patch_jump_with_target(&mut self, label: Label, target: u32) { const U32_SIZE: usize = std::mem::size_of::(); let Label { index } = label; @@ -743,13 +739,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { } /// Compile a [`StatementList`]. - pub fn compile_statement_list( - &mut self, - list: &StatementList, - use_expr: bool, - configurable_globals: bool, - ) { - self.create_declarations(list, configurable_globals); + pub fn compile_statement_list(&mut self, list: &StatementList, use_expr: bool) { if use_expr { let expr_index = list .statements() @@ -765,56 +755,15 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { .count(); for (i, item) in list.statements().iter().enumerate() { - self.compile_stmt_list_item(item, i + 1 == expr_index, configurable_globals); + self.compile_stmt_list_item(item, i + 1 == expr_index); } } else { for item in list.statements() { - self.compile_stmt_list_item(item, false, configurable_globals); + self.compile_stmt_list_item(item, false); } } } - /// Compile a statement list in a new declarative environment. - pub(crate) fn compile_statement_list_with_new_declarative( - &mut self, - list: &StatementList, - use_expr: bool, - strict: bool, - ) { - self.push_compile_environment(strict); - let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); - - self.create_declarations(list, true); - - if use_expr { - let expr_index = list - .statements() - .iter() - .rev() - .skip_while(|item| { - matches!( - item, - &&StatementListItem::Statement(Statement::Empty | Statement::Var(_)) - | &&StatementListItem::Declaration(_) - ) - }) - .count(); - - for (i, item) in list.statements().iter().enumerate() { - self.compile_stmt_list_item(item, i + 1 == expr_index, true); - } - } else { - for item in list.statements() { - self.compile_stmt_list_item(item, false, true); - } - } - - let env_info = self.pop_compile_environment(); - self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32); - self.patch_jump_with_target(push_env.1, env_info.index as u32); - self.emit_opcode(Opcode::PopEnvironment); - } - /// Compile an [`Expression`]. #[inline] pub fn compile_expr(&mut self, expr: &Expression, use_expr: bool) { @@ -1071,15 +1020,10 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { } /// Compile a [`StatementListItem`]. - fn compile_stmt_list_item( - &mut self, - item: &StatementListItem, - use_expr: bool, - configurable_globals: bool, - ) { + fn compile_stmt_list_item(&mut self, item: &StatementListItem, use_expr: bool) { match item { StatementListItem::Statement(stmt) => { - self.compile_stmt(stmt, use_expr, configurable_globals); + self.compile_stmt(stmt, use_expr); } StatementListItem::Declaration(decl) => self.compile_decl(decl), } @@ -1088,25 +1032,19 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { /// Compile a [`Declaration`]. pub fn compile_decl(&mut self, decl: &Declaration) { match decl { - Declaration::Function(function) => { - self.function(function.into(), NodeKind::Declaration, false); - } - Declaration::Generator(function) => { - self.function(function.into(), NodeKind::Declaration, false); - } - Declaration::AsyncFunction(function) => { - self.function(function.into(), NodeKind::Declaration, false); - } - Declaration::AsyncGenerator(function) => { - self.function(function.into(), NodeKind::Declaration, false); - } Declaration::Class(class) => self.class(class, false), Declaration::Lexical(lexical) => self.compile_lexical_decl(lexical), + _ => {} } } /// Compile a function AST Node into bytecode. - fn function(&mut self, function: FunctionSpec<'_>, node_kind: NodeKind, use_expr: bool) { + pub(crate) fn function( + &mut self, + function: FunctionSpec<'_>, + node_kind: NodeKind, + use_expr: bool, + ) { let (generator, r#async, arrow) = ( function.is_generator(), function.is_async(), @@ -1362,178 +1300,6 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { self.compile_declaration_pattern_impl(pattern, def); } - /// Creates the declarations for a script. - pub(crate) fn create_declarations( - &mut self, - stmt_list: &StatementList, - configurable_globals: bool, - ) { - for node in stmt_list.statements() { - self.create_decls_from_stmt_list_item(node, configurable_globals); - } - } - - pub(crate) fn create_decls_from_var_decl( - &mut self, - list: &VarDeclaration, - configurable_globals: bool, - ) -> bool { - let mut has_identifier_argument = false; - for decl in list.0.as_ref() { - match decl.binding() { - Binding::Identifier(ident) => { - let ident = ident; - if *ident == Sym::ARGUMENTS { - has_identifier_argument = true; - } - self.create_mutable_binding(*ident, true, configurable_globals); - } - Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - if ident == Sym::ARGUMENTS { - has_identifier_argument = true; - } - self.create_mutable_binding(ident, true, configurable_globals); - } - } - } - } - has_identifier_argument - } - - pub(crate) fn create_decls_from_lexical_decl(&mut self, list: &LexicalDeclaration) -> bool { - let mut has_identifier_argument = false; - match list { - LexicalDeclaration::Let(list) => { - for decl in list.as_ref() { - match decl.binding() { - Binding::Identifier(ident) => { - let ident = ident; - if *ident == Sym::ARGUMENTS { - has_identifier_argument = true; - } - self.create_mutable_binding(*ident, false, false); - } - Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - if ident == Sym::ARGUMENTS { - has_identifier_argument = true; - } - self.create_mutable_binding(ident, false, false); - } - } - } - } - } - LexicalDeclaration::Const(list) => { - for decl in list.as_ref() { - match decl.binding() { - Binding::Identifier(ident) => { - let ident = ident; - if *ident == Sym::ARGUMENTS { - has_identifier_argument = true; - } - self.create_immutable_binding(*ident, true); - } - Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - if ident == Sym::ARGUMENTS { - has_identifier_argument = true; - } - self.create_immutable_binding(ident, true); - } - } - } - } - } - } - has_identifier_argument - } - - pub(crate) fn create_decls_from_decl( - &mut self, - declaration: &Declaration, - configurable_globals: bool, - ) -> bool { - match declaration { - Declaration::Lexical(decl) => self.create_decls_from_lexical_decl(decl), - Declaration::Function(decl) => { - let ident = decl.name().expect("function declaration must have a name"); - self.create_mutable_binding(ident, true, configurable_globals); - ident == Sym::ARGUMENTS - } - Declaration::Generator(decl) => { - let ident = decl.name().expect("generator declaration must have a name"); - - self.create_mutable_binding(ident, true, configurable_globals); - ident == Sym::ARGUMENTS - } - Declaration::AsyncFunction(decl) => { - let ident = decl - .name() - .expect("async function declaration must have a name"); - self.create_mutable_binding(ident, true, configurable_globals); - ident == Sym::ARGUMENTS - } - Declaration::AsyncGenerator(decl) => { - let ident = decl - .name() - .expect("async generator declaration must have a name"); - self.create_mutable_binding(ident, true, configurable_globals); - ident == Sym::ARGUMENTS - } - Declaration::Class(decl) => { - let ident = decl.name().expect("class declaration must have a name"); - self.create_mutable_binding(ident, false, configurable_globals); - false - } - } - } - - pub(crate) fn create_decls_from_stmt( - &mut self, - statement: &Statement, - configurable_globals: bool, - ) -> bool { - match statement { - Statement::Var(var) => self.create_decls_from_var_decl(var, configurable_globals), - Statement::DoWhileLoop(do_while_loop) => { - if !matches!(do_while_loop.body(), Statement::Block(_)) { - self.create_decls_from_stmt(do_while_loop.body(), configurable_globals); - } - false - } - Statement::ForInLoop(for_in_loop) => { - if !matches!(for_in_loop.body(), Statement::Block(_)) { - self.create_decls_from_stmt(for_in_loop.body(), configurable_globals); - } - false - } - Statement::ForOfLoop(for_of_loop) => { - if !matches!(for_of_loop.body(), Statement::Block(_)) { - self.create_decls_from_stmt(for_of_loop.body(), configurable_globals); - } - false - } - _ => false, - } - } - - pub(crate) fn create_decls_from_stmt_list_item( - &mut self, - item: &StatementListItem, - configurable_globals: bool, - ) -> bool { - match item { - StatementListItem::Declaration(decl) => { - self.create_decls_from_decl(decl, configurable_globals) - } - StatementListItem::Statement(stmt) => { - self.create_decls_from_stmt(stmt, configurable_globals) - } - } - } - fn class(&mut self, class: &Class, expression: bool) { self.compile_class(class, expression); } diff --git a/boa_engine/src/bytecompiler/module.rs b/boa_engine/src/bytecompiler/module.rs index abadbdf438e..01ee557ba98 100644 --- a/boa_engine/src/bytecompiler/module.rs +++ b/boa_engine/src/bytecompiler/module.rs @@ -6,19 +6,19 @@ use boa_ast::{ModuleItem, ModuleItemList}; impl ByteCompiler<'_, '_> { /// Compiles a [`ModuleItemList`]. #[inline] - pub fn compile_module_item_list(&mut self, list: &ModuleItemList, configurable_globals: bool) { + pub fn compile_module_item_list(&mut self, list: &ModuleItemList) { for node in list.items() { - self.compile_module_item(node, configurable_globals); + self.compile_module_item(node); } } /// Compiles a [`ModuleItem`]. #[inline] #[allow(clippy::single_match_else)] - pub fn compile_module_item(&mut self, item: &ModuleItem, configurable_globals: bool) { + pub fn compile_module_item(&mut self, item: &ModuleItem) { match item { ModuleItem::StatementListItem(stmt) => { - self.compile_stmt_list_item(stmt, false, configurable_globals); + self.compile_stmt_list_item(stmt, false); } _ => { // TODO: Remove after implementing modules. @@ -29,31 +29,4 @@ impl ByteCompiler<'_, '_> { } } } - - /// Creates the declarations for a module. - pub(crate) fn create_module_decls( - &mut self, - stmt_list: &ModuleItemList, - configurable_globals: bool, - ) { - for node in stmt_list.items() { - self.create_decls_from_module_item(node, configurable_globals); - } - } - - /// Creates the declarations from a [`ModuleItem`]. - #[inline] - pub(crate) fn create_decls_from_module_item( - &mut self, - item: &ModuleItem, - configurable_globals: bool, - ) -> bool { - match item { - ModuleItem::StatementListItem(stmt) => { - self.create_decls_from_stmt_list_item(stmt, configurable_globals) - } - // TODO: Implement modules - _ => false, - } - } } diff --git a/boa_engine/src/bytecompiler/statement/block.rs b/boa_engine/src/bytecompiler/statement/block.rs index 46a63c8c8af..f72a11e6319 100644 --- a/boa_engine/src/bytecompiler/statement/block.rs +++ b/boa_engine/src/bytecompiler/statement/block.rs @@ -1,19 +1,14 @@ use crate::{bytecompiler::ByteCompiler, vm::Opcode}; - use boa_ast::statement::Block; impl ByteCompiler<'_, '_> { /// Compile a [`Block`] `boa_ast` node - pub(crate) fn compile_block( - &mut self, - block: &Block, - use_expr: bool, - configurable_globals: bool, - ) { + pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) { self.push_compile_environment(false); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); - self.compile_statement_list(block.statement_list(), use_expr, configurable_globals); + self.block_declaration_instantiation(block); + self.compile_statement_list(block.statement_list(), use_expr); let env_info = self.pop_compile_environment(); self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32); diff --git a/boa_engine/src/bytecompiler/statement/if.rs b/boa_engine/src/bytecompiler/statement/if.rs index 5cf93979fd8..ebeab44fe93 100644 --- a/boa_engine/src/bytecompiler/statement/if.rs +++ b/boa_engine/src/bytecompiler/statement/if.rs @@ -2,11 +2,11 @@ use crate::bytecompiler::ByteCompiler; use boa_ast::statement::If; impl ByteCompiler<'_, '_> { - pub(crate) fn compile_if(&mut self, node: &If, use_expr: bool, configurable_globals: bool) { + pub(crate) fn compile_if(&mut self, node: &If, use_expr: bool) { self.compile_expr(node.cond(), true); let jelse = self.jump_if_false(); - self.compile_stmt(node.body(), use_expr, configurable_globals); + self.compile_stmt(node.body(), use_expr); match node.else_node() { None => { @@ -15,7 +15,7 @@ impl ByteCompiler<'_, '_> { Some(else_body) => { let exit = self.jump(); self.patch_jump(jelse); - self.compile_stmt(else_body, use_expr, configurable_globals); + self.compile_stmt(else_body, use_expr); self.patch_jump(exit); } } diff --git a/boa_engine/src/bytecompiler/statement/labelled.rs b/boa_engine/src/bytecompiler/statement/labelled.rs index 42f4f8a3903..a7c3565542b 100644 --- a/boa_engine/src/bytecompiler/statement/labelled.rs +++ b/boa_engine/src/bytecompiler/statement/labelled.rs @@ -9,12 +9,7 @@ use boa_ast::{ impl ByteCompiler<'_, '_> { /// Compile a [`Labelled`] `boa_ast` node - pub(crate) fn compile_labelled( - &mut self, - labelled: &Labelled, - use_expr: bool, - configurable_globals: bool, - ) { + pub(crate) fn compile_labelled(&mut self, labelled: &Labelled, use_expr: bool) { let labelled_loc = self.next_opcode_location(); let end_label = self.emit_opcode_with_operand(Opcode::LabelledStart); self.push_labelled_control_info(labelled.label(), labelled_loc); @@ -22,37 +17,21 @@ impl ByteCompiler<'_, '_> { match labelled.item() { LabelledItem::Statement(stmt) => match stmt { Statement::ForLoop(for_loop) => { - self.compile_for_loop(for_loop, Some(labelled.label()), configurable_globals); + self.compile_for_loop(for_loop, Some(labelled.label())); } Statement::ForInLoop(for_in_loop) => { - self.compile_for_in_loop( - for_in_loop, - Some(labelled.label()), - configurable_globals, - ); + self.compile_for_in_loop(for_in_loop, Some(labelled.label())); } Statement::ForOfLoop(for_of_loop) => { - self.compile_for_of_loop( - for_of_loop, - Some(labelled.label()), - configurable_globals, - ); + self.compile_for_of_loop(for_of_loop, Some(labelled.label())); } Statement::WhileLoop(while_loop) => { - self.compile_while_loop( - while_loop, - Some(labelled.label()), - configurable_globals, - ); + self.compile_while_loop(while_loop, Some(labelled.label())); } Statement::DoWhileLoop(do_while_loop) => { - self.compile_do_while_loop( - do_while_loop, - Some(labelled.label()), - configurable_globals, - ); + self.compile_do_while_loop(do_while_loop, Some(labelled.label())); } - stmt => self.compile_stmt(stmt, use_expr, configurable_globals), + stmt => self.compile_stmt(stmt, use_expr), }, LabelledItem::Function(f) => { self.function(f.into(), NodeKind::Declaration, false); diff --git a/boa_engine/src/bytecompiler/statement/loop.rs b/boa_engine/src/bytecompiler/statement/loop.rs index ce05315ef0e..358b4d7db16 100644 --- a/boa_engine/src/bytecompiler/statement/loop.rs +++ b/boa_engine/src/bytecompiler/statement/loop.rs @@ -1,5 +1,5 @@ use boa_ast::{ - declaration::Binding, + declaration::{Binding, LexicalDeclaration}, operations::bound_names, statement::{ iteration::{ForLoopInitializer, IterableLoopInitializer}, @@ -14,12 +14,7 @@ use crate::{ }; impl ByteCompiler<'_, '_> { - pub(crate) fn compile_for_loop( - &mut self, - for_loop: &ForLoop, - label: Option, - configurable_globals: bool, - ) { + pub(crate) fn compile_for_loop(&mut self, for_loop: &ForLoop, label: Option) { self.push_compile_environment(false); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); self.push_empty_loop_jump_control(); @@ -28,11 +23,21 @@ impl ByteCompiler<'_, '_> { match init { ForLoopInitializer::Expression(expr) => self.compile_expr(expr, false), ForLoopInitializer::Var(decl) => { - self.create_decls_from_var_decl(decl, configurable_globals); self.compile_var_decl(decl); } ForLoopInitializer::Lexical(decl) => { - self.create_decls_from_lexical_decl(decl); + match decl { + LexicalDeclaration::Const(decl) => { + for name in bound_names(decl) { + self.create_immutable_binding(name, true); + } + } + LexicalDeclaration::Let(decl) => { + for name in bound_names(decl) { + self.create_mutable_binding(name, false); + } + } + } self.compile_lexical_decl(decl); } } @@ -67,7 +72,7 @@ impl ByteCompiler<'_, '_> { } let exit = self.jump_if_false(); - self.compile_stmt(for_loop.body(), false, configurable_globals); + self.compile_stmt(for_loop.body(), false); self.emit(Opcode::Jump, &[start_address]); @@ -83,18 +88,12 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::PopEnvironment); } - pub(crate) fn compile_for_in_loop( - &mut self, - for_in_loop: &ForInLoop, - label: Option, - configurable_globals: bool, - ) { + pub(crate) fn compile_for_in_loop(&mut self, for_in_loop: &ForInLoop, label: Option) { // Handle https://tc39.es/ecma262/#prod-annexB-ForInOfStatement if let IterableLoopInitializer::Var(var) = for_in_loop.initializer() { if let Binding::Identifier(ident) = var.binding() { if let Some(init) = var.init() { self.compile_expr(init, true); - self.create_mutable_binding(*ident, true, true); self.emit_binding(BindingOpcode::InitVar, *ident); } } @@ -111,7 +110,7 @@ impl ByteCompiler<'_, '_> { let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); for name in &initializer_bound_names { - self.create_mutable_binding(*name, false, false); + self.create_mutable_binding(*name, false); } self.compile_expr(for_in_loop.target(), true); @@ -146,7 +145,6 @@ impl ByteCompiler<'_, '_> { match for_in_loop.initializer() { IterableLoopInitializer::Identifier(ident) => { - self.create_mutable_binding(*ident, true, true); self.emit_binding(BindingOpcode::InitVar, *ident); } IterableLoopInitializer::Access(access) => { @@ -158,24 +156,20 @@ impl ByteCompiler<'_, '_> { } IterableLoopInitializer::Var(declaration) => match declaration.binding() { Binding::Identifier(ident) => { - self.create_mutable_binding(*ident, true, configurable_globals); self.emit_binding(BindingOpcode::InitVar, *ident); } Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - self.create_mutable_binding(ident, true, false); - } self.compile_declaration_pattern(pattern, BindingOpcode::InitVar); } }, IterableLoopInitializer::Let(declaration) => match declaration { Binding::Identifier(ident) => { - self.create_mutable_binding(*ident, false, false); + self.create_mutable_binding(*ident, false); self.emit_binding(BindingOpcode::InitLet, *ident); } Binding::Pattern(pattern) => { for ident in bound_names(pattern) { - self.create_mutable_binding(ident, false, false); + self.create_mutable_binding(ident, false); } self.compile_declaration_pattern(pattern, BindingOpcode::InitLet); } @@ -197,7 +191,7 @@ impl ByteCompiler<'_, '_> { } } - self.compile_stmt(for_in_loop.body(), false, configurable_globals); + self.compile_stmt(for_in_loop.body(), false); if let Some(iteration_environment) = iteration_environment { let env_info = self.pop_compile_environment(); @@ -220,12 +214,7 @@ impl ByteCompiler<'_, '_> { self.patch_jump(early_exit); } - pub(crate) fn compile_for_of_loop( - &mut self, - for_of_loop: &ForOfLoop, - label: Option, - configurable_globals: bool, - ) { + pub(crate) fn compile_for_of_loop(&mut self, for_of_loop: &ForOfLoop, label: Option) { let initializer_bound_names = match for_of_loop.initializer() { IterableLoopInitializer::Let(declaration) | IterableLoopInitializer::Const(declaration) => bound_names(declaration), @@ -238,7 +227,7 @@ impl ByteCompiler<'_, '_> { let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); for name in &initializer_bound_names { - self.create_mutable_binding(*name, false, false); + self.create_mutable_binding(*name, false); } self.compile_expr(for_of_loop.iterable(), true); @@ -280,7 +269,6 @@ impl ByteCompiler<'_, '_> { match for_of_loop.initializer() { IterableLoopInitializer::Identifier(ref ident) => { - self.create_mutable_binding(*ident, true, true); let binding = self.set_mutable_binding(*ident); let index = self.get_or_insert_binding(binding); self.emit(Opcode::DefInitVar, &[index]); @@ -297,25 +285,21 @@ impl ByteCompiler<'_, '_> { assert!(declaration.init().is_none()); match declaration.binding() { Binding::Identifier(ident) => { - self.create_mutable_binding(*ident, true, false); self.emit_binding(BindingOpcode::InitVar, *ident); } Binding::Pattern(pattern) => { - for ident in bound_names(pattern) { - self.create_mutable_binding(ident, true, false); - } self.compile_declaration_pattern(pattern, BindingOpcode::InitVar); } } } IterableLoopInitializer::Let(declaration) => match declaration { Binding::Identifier(ident) => { - self.create_mutable_binding(*ident, false, false); + self.create_mutable_binding(*ident, false); self.emit_binding(BindingOpcode::InitLet, *ident); } Binding::Pattern(pattern) => { for ident in bound_names(pattern) { - self.create_mutable_binding(ident, false, false); + self.create_mutable_binding(ident, false); } self.compile_declaration_pattern(pattern, BindingOpcode::InitLet); } @@ -337,7 +321,7 @@ impl ByteCompiler<'_, '_> { } } - self.compile_stmt(for_of_loop.body(), false, configurable_globals); + self.compile_stmt(for_of_loop.body(), false); if let Some(iteration_environment) = iteration_environment { let env_info = self.pop_compile_environment(); @@ -356,12 +340,7 @@ impl ByteCompiler<'_, '_> { self.iterator_close(for_of_loop.r#await()); } - pub(crate) fn compile_while_loop( - &mut self, - while_loop: &WhileLoop, - label: Option, - configurable_globals: bool, - ) { + pub(crate) fn compile_while_loop(&mut self, while_loop: &WhileLoop, label: Option) { let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart); let start_address = self.next_opcode_location(); let (continue_start, continue_exit) = @@ -372,7 +351,7 @@ impl ByteCompiler<'_, '_> { self.compile_expr(while_loop.condition(), true); let exit = self.jump_if_false(); - self.compile_stmt(while_loop.body(), false, configurable_globals); + self.compile_stmt(while_loop.body(), false); self.emit(Opcode::Jump, &[start_address]); self.patch_jump(exit); @@ -386,7 +365,6 @@ impl ByteCompiler<'_, '_> { &mut self, do_while_loop: &DoWhileLoop, label: Option, - configurable_globals: bool, ) { let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart); let initial_label = self.jump(); @@ -404,7 +382,7 @@ impl ByteCompiler<'_, '_> { self.patch_jump(initial_label); - self.compile_stmt(do_while_loop.body(), false, configurable_globals); + self.compile_stmt(do_while_loop.body(), false); self.emit(Opcode::Jump, &[condition_label_address]); self.patch_jump(exit); self.patch_jump(loop_exit); diff --git a/boa_engine/src/bytecompiler/statement/mod.rs b/boa_engine/src/bytecompiler/statement/mod.rs index bb32585a7ba..75f5c3692d5 100644 --- a/boa_engine/src/bytecompiler/statement/mod.rs +++ b/boa_engine/src/bytecompiler/statement/mod.rs @@ -14,30 +14,30 @@ mod with; impl ByteCompiler<'_, '_> { /// Compiles a [`Statement`] `boa_ast` node. - pub fn compile_stmt(&mut self, node: &Statement, use_expr: bool, configurable_globals: bool) { + pub fn compile_stmt(&mut self, node: &Statement, use_expr: bool) { match node { Statement::Var(var) => self.compile_var_decl(var), - Statement::If(node) => self.compile_if(node, use_expr, configurable_globals), + Statement::If(node) => self.compile_if(node, use_expr), Statement::ForLoop(for_loop) => { - self.compile_for_loop(for_loop, None, configurable_globals); + self.compile_for_loop(for_loop, None); } Statement::ForInLoop(for_in_loop) => { - self.compile_for_in_loop(for_in_loop, None, configurable_globals); + self.compile_for_in_loop(for_in_loop, None); } Statement::ForOfLoop(for_of_loop) => { - self.compile_for_of_loop(for_of_loop, None, configurable_globals); + self.compile_for_of_loop(for_of_loop, None); } Statement::WhileLoop(while_loop) => { - self.compile_while_loop(while_loop, None, configurable_globals); + self.compile_while_loop(while_loop, None); } Statement::DoWhileLoop(do_while_loop) => { - self.compile_do_while_loop(do_while_loop, None, configurable_globals); + self.compile_do_while_loop(do_while_loop, None); } Statement::Block(block) => { - self.compile_block(block, use_expr, configurable_globals); + self.compile_block(block, use_expr); } Statement::Labelled(labelled) => { - self.compile_labelled(labelled, use_expr, configurable_globals); + self.compile_labelled(labelled, use_expr); } Statement::Continue(node) => self.compile_continue(*node), Statement::Break(node) => self.compile_break(*node), @@ -46,7 +46,7 @@ impl ByteCompiler<'_, '_> { self.emit(Opcode::Throw, &[]); } Statement::Switch(switch) => { - self.compile_switch(switch, configurable_globals); + self.compile_switch(switch); } Statement::Return(ret) => { if let Some(expr) = ret.target() { @@ -56,9 +56,9 @@ impl ByteCompiler<'_, '_> { } self.emit(Opcode::Return, &[]); } - Statement::Try(t) => self.compile_try(t, use_expr, configurable_globals), + Statement::Try(t) => self.compile_try(t, use_expr), Statement::Expression(expr) => self.compile_expr(expr, use_expr), - Statement::With(with) => self.compile_with(with, configurable_globals), + Statement::With(with) => self.compile_with(with), Statement::Empty => {} } } diff --git a/boa_engine/src/bytecompiler/statement/switch.rs b/boa_engine/src/bytecompiler/statement/switch.rs index 82cc559ca17..93b16f7f069 100644 --- a/boa_engine/src/bytecompiler/statement/switch.rs +++ b/boa_engine/src/bytecompiler/statement/switch.rs @@ -3,17 +3,14 @@ use boa_ast::statement::Switch; impl ByteCompiler<'_, '_> { /// Compile a [`Switch`] `boa_ast` node - pub(crate) fn compile_switch(&mut self, switch: &Switch, configurable_globals: bool) { + pub(crate) fn compile_switch(&mut self, switch: &Switch) { self.compile_expr(switch.val(), true); self.push_compile_environment(false); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); - for case in switch.cases() { - self.create_declarations(case.body(), configurable_globals); - } - if let Some(body) = switch.default() { - self.create_declarations(body, configurable_globals); - } + + self.block_declaration_instantiation(switch); + let (start_label, end_label) = self.emit_opcode_with_two_operands(Opcode::LoopStart); let start_address = self.next_opcode_location(); @@ -30,16 +27,12 @@ impl ByteCompiler<'_, '_> { for (label, case) in labels.into_iter().zip(switch.cases()) { self.patch_jump(label); - for item in case.body().statements() { - self.compile_stmt_list_item(item, false, configurable_globals); - } + self.compile_statement_list(case.body(), false); } self.patch_jump(exit); if let Some(body) = switch.default() { - for item in body.statements() { - self.compile_stmt_list_item(item, false, configurable_globals); - } + self.compile_statement_list(body, false); } self.pop_switch_control_info(); diff --git a/boa_engine/src/bytecompiler/statement/try.rs b/boa_engine/src/bytecompiler/statement/try.rs index f10e7514284..b05d2f3c709 100644 --- a/boa_engine/src/bytecompiler/statement/try.rs +++ b/boa_engine/src/bytecompiler/statement/try.rs @@ -9,7 +9,7 @@ use boa_ast::{ }; impl ByteCompiler<'_, '_> { - pub(crate) fn compile_try(&mut self, t: &Try, use_expr: bool, configurable_globals: bool) { + pub(crate) fn compile_try(&mut self, t: &Try, use_expr: bool) { let try_start = self.next_opcode_location(); let (catch_start, finally_loc) = self.emit_opcode_with_two_operands(Opcode::TryStart); self.patch_jump_with_target(finally_loc, u32::MAX); @@ -20,22 +20,15 @@ impl ByteCompiler<'_, '_> { } self.push_try_control_info(t.finally().is_some(), try_start); - self.push_compile_environment(false); - let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); + self.compile_block(t.block(), use_expr); - self.compile_statement_list(t.block().statement_list(), use_expr, configurable_globals); - - let env_info = self.pop_compile_environment(); - self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32); - self.patch_jump_with_target(push_env.1, env_info.index as u32); - self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::TryEnd); let finally = self.jump(); self.patch_jump(catch_start); if t.catch().is_some() { - self.compile_catch_stmt(t, use_expr, configurable_globals); + self.compile_catch_stmt(t, use_expr); } self.patch_jump(finally); @@ -49,19 +42,14 @@ impl ByteCompiler<'_, '_> { self.set_jump_control_start_address(finally_start); self.patch_jump_with_target(finally_loc, finally_start); // Compile finally statement body - self.compile_finally_stmt(finally, finally_end, configurable_globals); + self.compile_finally_stmt(finally, finally_end); } else { let try_end = self.next_opcode_location(); self.pop_try_control_info(try_end); } } - pub(crate) fn compile_catch_stmt( - &mut self, - parent_try: &Try, - use_expr: bool, - configurable_globals: bool, - ) { + pub(crate) fn compile_catch_stmt(&mut self, parent_try: &Try, use_expr: bool) { let catch = parent_try .catch() .expect("Catch must exist for compile_catch_stmt to have been invoked"); @@ -74,12 +62,12 @@ impl ByteCompiler<'_, '_> { if let Some(binding) = catch.parameter() { match binding { Binding::Identifier(ident) => { - self.create_mutable_binding(*ident, false, false); + self.create_mutable_binding(*ident, false); self.emit_binding(BindingOpcode::InitLet, *ident); } Binding::Pattern(pattern) => { for ident in bound_names(pattern) { - self.create_mutable_binding(ident, false, false); + self.create_mutable_binding(ident, false); } self.compile_declaration_pattern(pattern, BindingOpcode::InitLet); } @@ -88,11 +76,7 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::Pop); } - self.compile_statement_list( - catch.block().statement_list(), - use_expr, - configurable_globals, - ); + self.compile_block(catch.block(), use_expr); let env_info = self.pop_compile_environment(); self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32); @@ -108,26 +92,9 @@ impl ByteCompiler<'_, '_> { self.set_jump_control_in_finally(false); } - pub(crate) fn compile_finally_stmt( - &mut self, - finally: &Finally, - finally_end_label: Label, - configurable_globals: bool, - ) { - self.push_compile_environment(false); - let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); - - self.compile_statement_list( - finally.block().statement_list(), - false, - configurable_globals, - ); + pub(crate) fn compile_finally_stmt(&mut self, finally: &Finally, finally_end_label: Label) { + self.compile_block(finally.block(), false); - let env_info = self.pop_compile_environment(); - self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32); - self.patch_jump_with_target(push_env.1, env_info.index as u32); - - self.emit_opcode(Opcode::PopEnvironment); self.pop_finally_control_info(); self.patch_jump(finally_end_label); self.emit_opcode(Opcode::FinallyEnd); diff --git a/boa_engine/src/bytecompiler/statement/with.rs b/boa_engine/src/bytecompiler/statement/with.rs index f6b9547ae1d..f48d851a36b 100644 --- a/boa_engine/src/bytecompiler/statement/with.rs +++ b/boa_engine/src/bytecompiler/statement/with.rs @@ -3,11 +3,11 @@ use boa_ast::statement::With; impl ByteCompiler<'_, '_> { /// Compile a [`With`] `boa_ast` node - pub(crate) fn compile_with(&mut self, with: &With, configurable_globals: bool) { + pub(crate) fn compile_with(&mut self, with: &With) { self.compile_expr(with.expression(), true); self.push_compile_environment(false); self.emit_opcode(Opcode::PushObjectEnvironment); - self.compile_stmt(with.statement(), false, configurable_globals); + self.compile_stmt(with.statement(), false); self.pop_compile_environment(); self.emit_opcode(Opcode::PopEnvironment); } diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index e551c6317fd..2d3e1ebf64d 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -18,7 +18,7 @@ use std::{io::Read, rc::Rc}; use crate::{ builtins, - bytecompiler::ByteCompiler, + bytecompiler::{ByteCompiler, NodeKind}, class::{Class, ClassBuilder}, job::{JobQueue, NativeJob, SimpleJobQueue}, native_function::NativeFunction, @@ -26,10 +26,15 @@ use crate::{ optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, - vm::{CallFrame, CodeBlock, Vm}, + vm::{CallFrame, CodeBlock, Opcode, Vm}, JsResult, JsValue, Source, }; -use boa_ast::{ModuleItemList, StatementList}; +use boa_ast::{ + declaration::LexicalDeclaration, + expression::Identifier, + operations::{bound_names, lexically_scoped_declarations, var_scoped_declarations}, + Declaration, ModuleItemList, StatementList, +}; use boa_gc::Gc; use boa_interner::{Interner, Sym}; use boa_parser::{Error as ParseError, Parser}; @@ -246,6 +251,7 @@ impl<'host> Context<'host> { /// Compile the script AST into a `CodeBlock` ready to be executed by the VM. pub fn compile_script(&mut self, statement_list: &StatementList) -> JsResult> { let _timer = Profiler::global().start_event("Script compilation", "Main"); + let mut compiler = ByteCompiler::new( Sym::MAIN, statement_list.strict(), @@ -253,7 +259,8 @@ impl<'host> Context<'host> { self.realm.environment().compile_env(), self, ); - compiler.compile_statement_list(statement_list, true, false); + compiler.global_declaration_instantiation(statement_list)?; + compiler.compile_statement_list(statement_list, true); Ok(Gc::new(compiler.finish())) } @@ -268,8 +275,67 @@ impl<'host> Context<'host> { self.realm.environment().compile_env(), self, ); - compiler.create_module_decls(statement_list, false); - compiler.compile_module_item_list(statement_list, false); + let var_declarations = var_scoped_declarations(statement_list); + let mut declared_var_names = Vec::new(); + for var in var_declarations { + for name in var.bound_names() { + if !declared_var_names.contains(&name) { + compiler.create_mutable_binding(name, false); + let binding = compiler.initialize_mutable_binding(name, false); + let index = compiler.get_or_insert_binding(binding); + compiler.emit_opcode(Opcode::PushUndefined); + compiler.emit(Opcode::DefInitVar, &[index]); + declared_var_names.push(name); + } + } + } + + let lex_declarations = lexically_scoped_declarations(statement_list); + for declaration in lex_declarations { + match &declaration { + Declaration::Lexical(LexicalDeclaration::Const(declaration)) => { + for name in bound_names(declaration) { + compiler.create_immutable_binding(name, true); + } + } + Declaration::Lexical(LexicalDeclaration::Let(declaration)) => { + for name in bound_names(declaration) { + compiler.create_mutable_binding(name, false); + } + } + Declaration::Function(function) => { + for name in bound_names(function) { + compiler.create_mutable_binding(name, false); + } + compiler.function(function.into(), NodeKind::Declaration, false); + } + Declaration::Generator(function) => { + for name in bound_names(function) { + compiler.create_mutable_binding(name, false); + } + compiler.function(function.into(), NodeKind::Declaration, false); + } + Declaration::AsyncFunction(function) => { + for name in bound_names(function) { + compiler.create_mutable_binding(name, false); + } + compiler.function(function.into(), NodeKind::Declaration, false); + } + Declaration::AsyncGenerator(function) => { + for name in bound_names(function) { + compiler.create_mutable_binding(name, false); + } + compiler.function(function.into(), NodeKind::Declaration, false); + } + Declaration::Class(class) => { + for name in bound_names(class) { + compiler.create_mutable_binding(name, false); + } + } + } + } + + compiler.compile_module_item_list(statement_list); Ok(Gc::new(compiler.finish())) } @@ -558,6 +624,163 @@ impl Context<'_> { pub(crate) fn swap_realm(&mut self, realm: &mut Realm) { std::mem::swap(&mut self.realm, realm); } + + /// `CanDeclareGlobalFunction ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction + pub(crate) fn can_declare_global_function(&mut self, name: Identifier) -> JsResult { + // 1. Let ObjRec be envRec.[[ObjectRecord]]. + // 2. Let globalObject be ObjRec.[[BindingObject]]. + let global_object = self.realm().global_object().clone(); + + // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). + let name = self.interner().resolve_expect(name.sym()).utf16().into(); + let existing_prop = global_object.__get_own_property__(&name, self)?; + + // 4. If existingProp is undefined, return ? IsExtensible(globalObject). + let Some(existing_prop) = existing_prop else { + return global_object.is_extensible(self); + }; + + // 5. If existingProp.[[Configurable]] is true, return true. + if existing_prop.configurable() == Some(true) { + return Ok(true); + } + + // 6. If IsDataDescriptor(existingProp) is true and existingProp has attribute values { [[Writable]]: true, [[Enumerable]]: true }, return true. + if existing_prop.is_data_descriptor() + && existing_prop.writable() == Some(true) + && existing_prop.enumerable() == Some(true) + { + return Ok(true); + } + + // 7. Return false. + Ok(false) + } + + /// `CanDeclareGlobalVar ( N )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar + pub(crate) fn can_declare_global_var(&mut self, name: Identifier) -> JsResult { + // 1. Let ObjRec be envRec.[[ObjectRecord]]. + // 2. Let globalObject be ObjRec.[[BindingObject]]. + let global_object = self.realm().global_object().clone(); + + // 3. Let hasProperty be ? HasOwnProperty(globalObject, N). + let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16()); + let has_property = global_object.has_own_property(name, self)?; + + // 4. If hasProperty is true, return true. + if has_property { + return Ok(true); + } + + // 5. Return ? IsExtensible(globalObject). + global_object.is_extensible(self) + } + + /// `CreateGlobalVarBinding ( N, D )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding + pub(crate) fn create_global_var_binding( + &mut self, + name: Identifier, + configurable: bool, + ) -> JsResult<()> { + // 1. Let ObjRec be envRec.[[ObjectRecord]]. + // 2. Let globalObject be ObjRec.[[BindingObject]]. + let global_object = self.realm().global_object().clone(); + + // 3. Let hasProperty be ? HasOwnProperty(globalObject, N). + let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16()); + let has_property = global_object.has_own_property(name.clone(), self)?; + + // 4. Let extensible be ? IsExtensible(globalObject). + let extensible = global_object.is_extensible(self)?; + + // 5. If hasProperty is false and extensible is true, then + if !has_property && extensible { + // a. Perform ? ObjRec.CreateMutableBinding(N, D). + // b. Perform ? ObjRec.InitializeBinding(N, undefined). + global_object.define_property_or_throw( + name, + PropertyDescriptor::builder() + .value(JsValue::undefined()) + .writable(true) + .enumerable(true) + .configurable(configurable) + .build(), + self, + )?; + } + + // 6. If envRec.[[VarNames]] does not contain N, then + // a. Append N to envRec.[[VarNames]]. + // 7. Return unused. + Ok(()) + } + + /// `CreateGlobalFunctionBinding ( N, V, D )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding + pub(crate) fn create_global_function_binding( + &mut self, + name: Identifier, + function: JsObject, + configurable: bool, + ) -> JsResult<()> { + // 1. Let ObjRec be envRec.[[ObjectRecord]]. + // 2. Let globalObject be ObjRec.[[BindingObject]]. + let global_object = self.realm().global_object().clone(); + + // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). + let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16()); + let existing_prop = global_object.__get_own_property__(&name, self)?; + + // 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then + let desc = if existing_prop.is_none() + || existing_prop.and_then(|p| p.configurable()) == Some(true) + { + // a. Let desc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }. + PropertyDescriptor::builder() + .value(function.clone()) + .writable(true) + .enumerable(true) + .configurable(configurable) + .build() + } + // 5. Else, + else { + // a. Let desc be the PropertyDescriptor { [[Value]]: V }. + PropertyDescriptor::builder() + .value(function.clone()) + .build() + }; + + // 6. Perform ? DefinePropertyOrThrow(globalObject, N, desc). + global_object.define_property_or_throw(name.clone(), desc, self)?; + + // 7. Perform ? Set(globalObject, N, V, false). + global_object.set(name, function, false, self)?; + + // 8. If envRec.[[VarNames]] does not contain N, then + // a. Append N to envRec.[[VarNames]]. + // 9. Return unused. + Ok(()) + } } impl<'host> Context<'host> { diff --git a/boa_engine/src/environments/compile.rs b/boa_engine/src/environments/compile.rs index b95bcfc43af..e85ff628218 100644 --- a/boa_engine/src/environments/compile.rs +++ b/boa_engine/src/environments/compile.rs @@ -106,6 +106,23 @@ impl CompileTimeEnvironment { } } + /// Check if a binding name exists in a environment. + /// If strict is `false` check until a function scope is reached. + pub(crate) fn has_binding_eval(&self, name: Identifier, strict: bool) -> bool { + let exists = self.bindings.contains_key(&name); + if exists || strict { + return exists; + } + if self.function_scope { + return false; + } + if let Some(outer) = &self.outer { + outer.borrow().has_binding_eval(name, false) + } else { + false + } + } + /// Create a mutable binding. /// /// If the binding is a function scope binding and this is a declarative environment, try the outer environment. @@ -228,18 +245,4 @@ impl CompileTimeEnvironment { pub(crate) const fn environment_index(&self) -> usize { self.environment_index } - - /// Gets the indices of all `var` bindings in this environment. - pub(crate) fn var_binding_indices(&self) -> Vec { - self.bindings - .iter() - .filter_map(|(_, binding)| { - if binding.lex { - None - } else { - Some(binding.index) - } - }) - .collect() - } } diff --git a/boa_engine/src/environments/runtime.rs b/boa_engine/src/environments/runtime.rs index 0521ff172e6..b4a567bb087 100644 --- a/boa_engine/src/environments/runtime.rs +++ b/boa_engine/src/environments/runtime.rs @@ -336,6 +336,22 @@ impl DeclarativeEnvironmentStack { None } + /// Check if the next outer function environment is the global environment. + pub(crate) fn is_next_outer_function_environment_global(&self) -> bool { + for env in self + .stack + .iter() + .rev() + .filter_map(Environment::as_declarative) + { + let compile = env.compile.borrow(); + if compile.is_function() { + return compile.outer().is_none(); + } + } + true + } + /// Pop all current environments except the global environment. pub(crate) fn pop_to_global(&mut self) -> Vec { self.stack.split_off(1) @@ -481,14 +497,9 @@ impl DeclarativeEnvironmentStack { let this = this.unwrap_or(JsValue::Null); - let mut bindings = vec![None; num_bindings]; - for index in compile_environment.borrow().var_binding_indices() { - bindings[index] = Some(JsValue::Undefined); - } - self.stack .push(Environment::Declarative(Gc::new(DeclarativeEnvironment { - bindings: GcRefCell::new(bindings), + bindings: GcRefCell::new(vec![None; num_bindings]), compile: compile_environment, poisoned: Cell::new(poisoned), with: Cell::new(with), @@ -539,14 +550,9 @@ impl DeclarativeEnvironmentStack { ) }; - let mut bindings = vec![None; num_bindings]; - for index in compile_environment.borrow().var_binding_indices() { - bindings[index] = Some(JsValue::Undefined); - } - self.stack .push(Environment::Declarative(Gc::new(DeclarativeEnvironment { - bindings: GcRefCell::new(bindings), + bindings: GcRefCell::new(vec![None; num_bindings]), compile: compile_environment, poisoned: Cell::new(poisoned), with: Cell::new(with), diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 5df7f5ce48b..310d87d0909 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -33,7 +33,9 @@ pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode}; pub(crate) use { call_frame::GeneratorResumeKind, - code_block::{create_function_object, create_generator_function_object}, + code_block::{ + create_function_object, create_function_object_fast, create_generator_function_object, + }, completion_record::CompletionRecord, opcode::BindingOpcode, }; diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index a646ef02515..62e9612347c 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -1636,7 +1636,6 @@ pub(crate) enum BindingOpcode { Let, InitVar, InitLet, - InitArg, InitConst, SetName, } diff --git a/boa_parser/src/parser/statement/block/tests.rs b/boa_parser/src/parser/statement/block/tests.rs index 743415dc80a..55eae0e2034 100644 --- a/boa_parser/src/parser/statement/block/tests.rs +++ b/boa_parser/src/parser/statement/block/tests.rs @@ -119,15 +119,6 @@ fn hoisting() { function hello() { return 10 } }", vec![ - Declaration::Function(Function::new( - Some(hello.into()), - FormalParameterList::default(), - vec![StatementListItem::Statement(Statement::Return( - Return::new(Some(Literal::from(10).into())), - ))] - .into(), - )) - .into(), Statement::Var(VarDeclaration( vec![Variable::from_identifier( a.into(), @@ -142,6 +133,15 @@ fn hoisting() { UpdateTarget::Identifier(Identifier::new(a)), ))) .into(), + Declaration::Function(Function::new( + Some(hello.into()), + FormalParameterList::default(), + vec![StatementListItem::Statement(Statement::Return( + Return::new(Some(Literal::from(10).into())), + ))] + .into(), + )) + .into(), ], interner, ); diff --git a/boa_parser/src/parser/statement/mod.rs b/boa_parser/src/parser/statement/mod.rs index 7116877d0e3..0e753143241 100644 --- a/boa_parser/src/parser/statement/mod.rs +++ b/boa_parser/src/parser/statement/mod.rs @@ -366,8 +366,6 @@ where items.push(item); } - items.sort_by(ast::StatementListItem::hoistable_order); - cursor.set_strict(global_strict); Ok(ast::StatementList::new(items, strict)) diff --git a/boa_parser/src/parser/tests/mod.rs b/boa_parser/src/parser/tests/mod.rs index 183e07d03d5..28fda835bf8 100644 --- a/boa_parser/src/parser/tests/mod.rs +++ b/boa_parser/src/parser/tests/mod.rs @@ -100,9 +100,7 @@ fn assign_operator_precedence() { #[test] fn hoisting() { let interner = &mut Interner::default(); - let hello = interner - .get_or_intern_static("hello", utf16!("hello")) - .into(); + let hello = interner.get_or_intern_static("hello", utf16!("hello")); let a = interner.get_or_intern_static("a", utf16!("a")); check_script_parser( r" @@ -111,16 +109,10 @@ fn hoisting() { function hello() { return 10 }", vec![ - Declaration::Function(Function::new( - Some(hello), - FormalParameterList::default(), - vec![Statement::Return(Return::new(Some(Literal::from(10).into()))).into()].into(), - )) - .into(), Statement::Var(VarDeclaration( vec![Variable::from_identifier( a.into(), - Some(Call::new(hello.into(), Box::default()).into()), + Some(Call::new(Identifier::new(hello).into(), Box::default()).into()), )] .try_into() .unwrap(), @@ -131,6 +123,12 @@ fn hoisting() { UpdateTarget::Identifier(Identifier::new(a)), ))) .into(), + Declaration::Function(Function::new( + Some(hello.into()), + FormalParameterList::default(), + vec![Statement::Return(Return::new(Some(Literal::from(10).into()))).into()].into(), + )) + .into(), ], interner, );