diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index b77ce70544..ec4b0e0a51 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -18,9 +18,8 @@ use crate::{ value::{RcString, Value}, Array, }, - environment::function_environment_record::BindingStatus, - environment::lexical_environment::{new_function_environment, Environment}, - exec::{Executable, Interpreter}, + environment::lexical_environment::Environment, + exec::Interpreter, syntax::ast::node::{FormalParameter, StatementList}, BoaProfiler, Result, }; @@ -29,73 +28,33 @@ use gc::{unsafe_empty_trace, Finalize, Trace}; use std::fmt::{self, Debug}; /// _fn(this, arguments, ctx) -> Result_ - The signature of a built-in function -pub type NativeFunctionData = fn(&Value, &[Value], &mut Interpreter) -> Result; +pub type NativeFunction = fn(&Value, &[Value], &mut Interpreter) -> Result; -/// Sets the ConstructorKind -#[derive(Debug, Copy, Clone)] -pub enum ConstructorKind { - Base, - Derived, -} - -/// Defines how this references are interpreted within the formal parameters and code body of the function. -/// -/// Arrow functions don't define a `this` and thus are lexical, `function`s do define a this and thus are NonLexical +#[derive(Clone, Copy, Finalize)] +pub struct BuiltInFunction(pub(crate) NativeFunction); -#[derive(Debug, Copy, Finalize, Clone, PartialEq, PartialOrd, Hash)] -pub enum ThisMode { - Lexical, - NonLexical, -} - -unsafe impl Trace for ThisMode { +unsafe impl Trace for BuiltInFunction { unsafe_empty_trace!(); } -/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) -#[derive(Clone, Finalize)] -pub enum FunctionBody { - BuiltIn(NativeFunctionData), - Ordinary(StatementList), -} - -impl Debug for FunctionBody { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::BuiltIn(_) => write!(f, "[native]"), - Self::Ordinary(statements) => write!(f, "{:?}", statements), - } +impl From for BuiltInFunction { + fn from(function: NativeFunction) -> Self { + Self(function) } } -impl PartialEq for FunctionBody { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::BuiltIn(a), Self::BuiltIn(b)) => std::ptr::eq(a, b), - (Self::Ordinary(a), Self::Ordinary(b)) => a == b, - (_, _) => false, - } +impl Debug for BuiltInFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("[native]") } } -impl Eq for FunctionBody {} - -/// `Trace` implementation for `FunctionBody`. -/// -/// This is indeed safe, but we need to mark this as an empty trace because neither -// `NativeFunctionData` nor Node hold any GC'd objects, but Gc doesn't know that. So we need to -/// signal it manually. `rust-gc` does not have a `Trace` implementation for `fn(_, _, _)`. -/// -/// -unsafe impl Trace for FunctionBody { - unsafe_empty_trace!(); -} - bitflags! { #[derive(Finalize, Default)] - struct FunctionFlags: u8 { + pub struct FunctionFlags: u8 { const CALLABLE = 0b0000_0001; const CONSTRUCTABLE = 0b0000_0010; + const LEXICAL_THIS_MODE = 0b0000_0100; } } @@ -114,14 +73,19 @@ impl FunctionFlags { } #[inline] - fn is_callable(&self) -> bool { + pub(crate) fn is_callable(&self) -> bool { self.contains(Self::CALLABLE) } #[inline] - fn is_constructable(&self) -> bool { + pub(crate) fn is_constructable(&self) -> bool { self.contains(Self::CONSTRUCTABLE) } + + #[inline] + pub(crate) fn is_lexical_this_mode(&self) -> bool { + self.contains(Self::LEXICAL_THIS_MODE) + } } unsafe impl Trace for FunctionFlags { @@ -130,220 +94,23 @@ unsafe impl Trace for FunctionFlags { /// Boa representation of a Function Object. /// +/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) +/// /// -#[derive(Trace, Finalize, Clone)] -pub struct Function { - /// Call/Construct Function body - pub body: FunctionBody, - /// Formal Paramaters - pub params: Box<[FormalParameter]>, - /// This Mode - pub this_mode: ThisMode, - // Environment, built-in functions don't need Environments - pub environment: Option, - /// Is it constructable or - flags: FunctionFlags, +#[derive(Debug, Clone, Finalize, Trace)] +pub enum Function { + BuiltIn(BuiltInFunction, FunctionFlags), + Ordinary { + flags: FunctionFlags, + body: StatementList, + params: Box<[FormalParameter]>, + environment: Environment, + }, } impl Function { - pub fn new

( - parameter_list: P, - scope: Option, - body: FunctionBody, - this_mode: ThisMode, - constructable: bool, - callable: bool, - ) -> Self - where - P: Into>, - { - Self { - body, - environment: scope, - params: parameter_list.into(), - this_mode, - flags: FunctionFlags::from_parameters(callable, constructable), - } - } - - /// This will create an ordinary function object - /// - /// - pub fn ordinary

( - parameter_list: P, - scope: Environment, - body: StatementList, - this_mode: ThisMode, - ) -> Self - where - P: Into>, - { - Self::new( - parameter_list.into(), - Some(scope), - FunctionBody::Ordinary(body), - this_mode, - true, - true, - ) - } - - /// This will create a built-in function object - /// - /// - pub fn builtin

(parameter_list: P, body: NativeFunctionData) -> Self - where - P: Into>, - { - let _timer = BoaProfiler::global().start_event("function::builtin", "function"); - Self::new( - parameter_list.into(), - None, - FunctionBody::BuiltIn(body), - ThisMode::NonLexical, - false, - true, - ) - } - - /// This will handle calls for both ordinary and built-in functions - /// - /// - /// - pub fn call( - &self, - function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) - this: &Value, - args_list: &[Value], - interpreter: &mut Interpreter, - ) -> Result { - let _timer = BoaProfiler::global().start_event("function::call", "function"); - if self.flags.is_callable() { - match self.body { - FunctionBody::BuiltIn(func) => func(this, args_list, interpreter), - FunctionBody::Ordinary(ref body) => { - // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) - // - let local_env = new_function_environment( - function, - if let ThisMode::Lexical = self.this_mode { - None - } else { - Some(this.clone()) - }, - self.environment.as_ref().cloned(), - // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records - if let ThisMode::Lexical = self.this_mode { - BindingStatus::Lexical - } else { - BindingStatus::Uninitialized - }, - ); - - // Add argument bindings to the function environment - for (i, param) in self.params.iter().enumerate() { - // Rest Parameters - if param.is_rest_param() { - self.add_rest_param(param, i, args_list, interpreter, &local_env); - break; - } - - let value = args_list.get(i).cloned().unwrap_or_else(Value::undefined); - self.add_arguments_to_environment(param, value, &local_env); - } - - // Add arguments object - let arguments_obj = create_unmapped_arguments_object(args_list); - local_env - .borrow_mut() - .create_mutable_binding("arguments".to_string(), false); - local_env - .borrow_mut() - .initialize_binding("arguments", arguments_obj); - - interpreter.realm.environment.push(local_env); - - // Call body should be set before reaching here - let result = body.run(interpreter); - - // local_env gets dropped here, its no longer needed - interpreter.realm.environment.pop(); - result - } - } - } else { - panic!("TypeError: class constructors must be invoked with 'new'"); - } - } - - /// - pub fn construct( - &self, - function: Value, // represents a pointer to this function object wrapped in a GC (not a `this` JS object) - this: &Value, - args_list: &[Value], - interpreter: &mut Interpreter, - ) -> Result { - if self.flags.is_constructable() { - match self.body { - FunctionBody::BuiltIn(func) => { - func(this, args_list, interpreter)?; - Ok(this.clone()) - } - FunctionBody::Ordinary(ref body) => { - // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) - // - let local_env = new_function_environment( - function, - Some(this.clone()), - self.environment.as_ref().cloned(), - // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records - if let ThisMode::Lexical = self.this_mode { - BindingStatus::Lexical - } else { - BindingStatus::Uninitialized - }, - ); - - // Add argument bindings to the function environment - for (i, param) in self.params.iter().enumerate() { - // Rest Parameters - if param.is_rest_param() { - self.add_rest_param(param, i, args_list, interpreter, &local_env); - break; - } - - let value = args_list.get(i).cloned().unwrap_or_else(Value::undefined); - self.add_arguments_to_environment(param, value, &local_env); - } - - // Add arguments object - let arguments_obj = create_unmapped_arguments_object(args_list); - local_env - .borrow_mut() - .create_mutable_binding("arguments".to_string(), false); - local_env - .borrow_mut() - .initialize_binding("arguments", arguments_obj); - - interpreter.realm.environment.push(local_env); - - // Call body should be set before reaching here - let _ = body.run(interpreter); - - // local_env gets dropped here, its no longer needed - let binding = interpreter.realm.environment.get_this_binding(); - Ok(binding) - } - } - } else { - let name = this.get_field("name").display().to_string(); - panic!("TypeError: {} is not a constructor", name); - } - } - // Adds the final rest parameters to the Environment as an array - fn add_rest_param( + pub(crate) fn add_rest_param( &self, param: &FormalParameter, index: usize, @@ -367,7 +134,7 @@ impl Function { } // Adds an argument to the environment - fn add_arguments_to_environment( + pub(crate) fn add_arguments_to_environment( &self, param: &FormalParameter, value: Value, @@ -386,20 +153,18 @@ impl Function { /// Returns true if the function object is callable. pub fn is_callable(&self) -> bool { - self.flags.is_callable() + match self { + Self::BuiltIn(_, flags) => flags.is_callable(), + Self::Ordinary { flags, .. } => flags.is_callable(), + } } /// Returns true if the function object is constructable. pub fn is_constructable(&self) -> bool { - self.flags.is_constructable() - } -} - -impl Debug for Function { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{{")?; - write!(f, "[Not implemented]")?; - write!(f, "}}") + match self { + Self::BuiltIn(_, flags) => flags.is_constructable(), + Self::Ordinary { flags, .. } => flags.is_constructable(), + } } } @@ -436,9 +201,9 @@ pub fn create_unmapped_arguments_object(arguments_list: &[Value]) -> Value { /// // This gets called when a new Function() is created. pub fn make_function(this: &Value, _: &[Value], _: &mut Interpreter) -> Result { - this.set_data(ObjectData::Function(Function::builtin( - Vec::new(), - |_, _, _| Ok(Value::undefined()), + this.set_data(ObjectData::Function(Function::BuiltIn( + BuiltInFunction(|_, _, _| Ok(Value::undefined())), + FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, ))); Ok(this.clone()) } @@ -450,7 +215,7 @@ pub fn make_function(this: &Value, _: &[Value], _: &mut Interpreter) -> Result( - function: NativeFunctionData, + function: NativeFunction, name: N, parent: &Value, length: usize, @@ -526,13 +293,12 @@ pub fn make_builtin_fn( let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init"); let mut function = Object::function( - Function::builtin(Vec::new(), function), + Function::BuiltIn(function.into(), FunctionFlags::CALLABLE), interpreter .global() .get_field("Function") .get_field("prototype"), ); - function.insert_field("length", Value::from(length)); parent diff --git a/boa/src/builtins/map/tests.rs b/boa/src/builtins/map/tests.rs index e3e9302787..a0d7b62294 100644 --- a/boa/src/builtins/map/tests.rs +++ b/boa/src/builtins/map/tests.rs @@ -228,10 +228,18 @@ fn recursive_display() { } #[test] -#[should_panic] fn not_a_function() { let realm = Realm::create(); let mut engine = Interpreter::new(realm); - let init = "let map = Map()"; - forward(&mut engine, init); + let init = r" + try { + let map = Map() + } catch(e) { + e.toString() + } + "; + assert_eq!( + forward(&mut engine, init), + "\"TypeError: function object is not callable\"" + ); } diff --git a/boa/src/builtins/object/gcobject.rs b/boa/src/builtins/object/gcobject.rs index 6db9b76904..2dd0cbeb13 100644 --- a/boa/src/builtins/object/gcobject.rs +++ b/boa/src/builtins/object/gcobject.rs @@ -3,7 +3,18 @@ //! The `GcObject` is a garbage collected Object. use super::Object; +use crate::{ + builtins::{ + function::{create_unmapped_arguments_object, BuiltInFunction, Function}, + Value, + }, + environment::{ + function_environment_record::BindingStatus, lexical_environment::new_function_environment, + }, + Executable, Interpreter, Result, +}; use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace}; +use std::result::Result as StdResult; use std::{ cell::RefCell, collections::HashSet, @@ -31,12 +42,12 @@ impl GcObject { } #[inline] - pub fn try_borrow(&self) -> Result, BorrowError> { + pub fn try_borrow(&self) -> StdResult, BorrowError> { self.0.try_borrow().map_err(|_| BorrowError) } #[inline] - pub fn try_borrow_mut(&self) -> Result, BorrowMutError> { + pub fn try_borrow_mut(&self) -> StdResult, BorrowMutError> { self.0.try_borrow_mut().map_err(|_| BorrowMutError) } @@ -45,6 +56,151 @@ impl GcObject { pub fn equals(lhs: &Self, rhs: &Self) -> bool { std::ptr::eq(lhs.as_ref(), rhs.as_ref()) } + + /// This will handle calls for both ordinary and built-in functions + /// + /// + /// + pub fn call(&self, this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result { + let this_function_object = self.clone(); + let object = self.borrow(); + if let Some(function) = object.as_function() { + if function.is_callable() { + match function { + Function::BuiltIn(BuiltInFunction(function), _) => function(this, args, ctx), + Function::Ordinary { + body, + params, + environment, + flags, + } => { + // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) + // + let local_env = new_function_environment( + this_function_object, + if flags.is_lexical_this_mode() { + None + } else { + Some(this.clone()) + }, + Some(environment.clone()), + // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records + if flags.is_lexical_this_mode() { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + ); + + // Add argument bindings to the function environment + for (i, param) in params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param() { + function.add_rest_param(param, i, args, ctx, &local_env); + break; + } + + let value = args.get(i).cloned().unwrap_or_else(Value::undefined); + function.add_arguments_to_environment(param, value, &local_env); + } + + // Add arguments object + let arguments_obj = create_unmapped_arguments_object(args); + local_env + .borrow_mut() + .create_mutable_binding("arguments".to_string(), false); + local_env + .borrow_mut() + .initialize_binding("arguments", arguments_obj); + + ctx.realm.environment.push(local_env); + + // Call body should be set before reaching here + let result = body.run(ctx); + + // local_env gets dropped here, its no longer needed + ctx.realm.environment.pop(); + result + } + } + } else { + ctx.throw_type_error("function object is not callable") + } + } else { + ctx.throw_type_error("not a function") + } + } + + /// + pub fn construct(&self, this: &Value, args: &[Value], ctx: &mut Interpreter) -> Result { + let this_function_object = self.clone(); + let object = self.borrow(); + if let Some(function) = object.as_function() { + if function.is_constructable() { + match function { + Function::BuiltIn(BuiltInFunction(function), _) => { + function(this, args, ctx)?; + Ok(this.clone()) + } + Function::Ordinary { + body, + params, + environment, + flags, + } => { + // Create a new Function environment who's parent is set to the scope of the function declaration (self.environment) + // + let local_env = new_function_environment( + this_function_object, + Some(this.clone()), + Some(environment.clone()), + // Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records + if flags.is_lexical_this_mode() { + BindingStatus::Lexical + } else { + BindingStatus::Uninitialized + }, + ); + + // Add argument bindings to the function environment + for (i, param) in params.iter().enumerate() { + // Rest Parameters + if param.is_rest_param() { + function.add_rest_param(param, i, args, ctx, &local_env); + break; + } + + let value = args.get(i).cloned().unwrap_or_else(Value::undefined); + function.add_arguments_to_environment(param, value, &local_env); + } + + // Add arguments object + let arguments_obj = create_unmapped_arguments_object(args); + local_env + .borrow_mut() + .create_mutable_binding("arguments".to_string(), false); + local_env + .borrow_mut() + .initialize_binding("arguments", arguments_obj); + + ctx.realm.environment.push(local_env); + + // Call body should be set before reaching here + let _ = body.run(ctx); + + // local_env gets dropped here, its no longer needed + let binding = ctx.realm.environment.get_this_binding(); + Ok(binding) + } + } + } else { + let name = this.get_field("name").display().to_string(); + ctx.throw_type_error(format!("{} is not a constructor", name)) + } + } else { + ctx.throw_type_error("not a function") + } + } } impl AsRef> for GcObject { diff --git a/boa/src/builtins/object/internal_state.rs b/boa/src/builtins/object/internal_state.rs deleted file mode 100644 index 06955258cc..0000000000 --- a/boa/src/builtins/object/internal_state.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! Implementations for storing normal rust structs inside any object as internal state. - -use std::{ - any::Any, - fmt::{self, Debug}, - ops::{Deref, DerefMut}, - rc::Rc, -}; - -use gc::{unsafe_empty_trace, Finalize, Trace}; - -/// Wrapper around `Rc` to implement `Trace` and `Finalize`. -#[derive(Clone)] -pub struct InternalStateCell { - /// The internal state. - state: Rc, -} - -impl Finalize for InternalStateCell {} - -unsafe impl Trace for InternalStateCell { - unsafe_empty_trace!(); -} - -impl Deref for InternalStateCell { - type Target = dyn Any; - fn deref(&self) -> &Self::Target { - Deref::deref(&self.state) - } -} - -impl DerefMut for InternalStateCell { - fn deref_mut(&mut self) -> &mut Self::Target { - Rc::get_mut(&mut self.state).expect("failed to get mutable") - } -} - -/// The derived version would print 'InternalStateCell { state: ... }', this custom implementation -/// only prints the actual internal state. -impl Debug for InternalStateCell { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - Debug::fmt(&self.state, f) - } -} - -impl InternalStateCell { - /// Create new `InternalStateCell` from a value. - pub fn new(value: T) -> Self { - Self { - state: Rc::new(value), - } - } - /// Get a reference to the stored value and cast it to `T`. - pub fn downcast_ref(&self) -> Option<&T> { - self.deref().downcast_ref::() - } - /// Get a mutable reference to the stored value and cast it to `T`. - pub fn downcast_mut(&mut self) -> Option<&mut T> { - self.deref_mut().downcast_mut::() - } -} - -/// This trait must be implemented by all structs used for internal state. -pub trait InternalState: Debug {} diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index de05955d57..f1572ac809 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -31,13 +31,12 @@ use std::result::Result as StdResult; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::builtins::value::same_value; -pub use internal_state::{InternalState, InternalStateCell}; -pub mod gcobject; -pub mod internal_methods; -mod internal_state; +mod gcobject; +mod internal_methods; pub use gcobject::GcObject; +pub use internal_methods::*; #[cfg(test)] mod tests; @@ -45,9 +44,6 @@ mod tests; /// Static `prototype`, usually set on constructors as a key to point to their respective prototype object. pub static PROTOTYPE: &str = "prototype"; -// /// Static `__proto__`, usually set on Object instances as a key to point to their respective prototype object. -// pub static INSTANCE_PROTOTYPE: &str = "__proto__"; - /// The internal representation of an JavaScript object. #[derive(Debug, Trace, Finalize, Clone)] pub struct Object { @@ -59,8 +55,6 @@ pub struct Object { symbol_properties: FxHashMap, /// Instance prototype `__proto__`. prototype: Value, - /// Some rust object that stores internal state - state: Option, /// Whether it can have new properties added to it. extensible: bool, } @@ -70,7 +64,7 @@ pub struct Object { pub enum ObjectData { Array, Map(OrderedMap), - RegExp(RegExp), + RegExp(Box), BigInt(RcBigInt), Boolean(bool), Function(Function), @@ -116,7 +110,6 @@ impl Default for Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), - state: None, extensible: true, } } @@ -137,7 +130,6 @@ impl Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype, - state: None, extensible: true, } } @@ -162,7 +154,6 @@ impl Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), - state: None, extensible: true, } } @@ -174,7 +165,6 @@ impl Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), - state: None, extensible: true, } } @@ -189,7 +179,6 @@ impl Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), - state: None, extensible: true, } } @@ -201,7 +190,6 @@ impl Object { properties: FxHashMap::default(), symbol_properties: FxHashMap::default(), prototype: Value::null(), - state: None, extensible: true, } } @@ -420,16 +408,6 @@ impl Object { &mut self.symbol_properties } - #[inline] - pub fn state(&self) -> &Option { - &self.state - } - - #[inline] - pub fn state_mut(&mut self) -> &mut Option { - &mut self.state - } - pub fn prototype(&self) -> &Value { &self.prototype } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 855cec9097..214b2807ca 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -14,7 +14,7 @@ use regex::Regex; use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ - object::{InternalState, ObjectData}, + object::ObjectData, property::Property, value::{RcString, Value}, }, @@ -64,8 +64,6 @@ unsafe impl Trace for RegExp { unsafe_empty_trace!(); } -impl InternalState for RegExp {} - impl RegExp { /// The name of the object. pub(crate) const NAME: &'static str = "RegExp"; @@ -156,7 +154,7 @@ impl RegExp { original_flags: regex_flags, }; - this.set_data(ObjectData::RegExp(regexp)); + this.set_data(ObjectData::RegExp(Box::new(regexp))); Ok(this.clone()) } diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 2e99a370c6..7ccf46469b 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -7,8 +7,7 @@ mod tests; use super::number::{f64_to_int32, f64_to_uint32}; use crate::builtins::{ - function::Function, - object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE}, + object::{GcObject, Object, ObjectData, PROTOTYPE}, property::{Attribute, Property, PropertyKey}, BigInt, Number, Symbol, }; @@ -17,7 +16,6 @@ use crate::{BoaProfiler, Result}; use gc::{Finalize, GcCellRef, GcCellRefMut, Trace}; use serde_json::{map::Map, Number as JSONNumber, Value as JSONValue}; use std::{ - any::Any, collections::HashSet, convert::TryFrom, f64::NAN, @@ -530,66 +528,6 @@ impl Value { } } - /// Check whether an object has an internal state set. - #[inline] - pub fn has_internal_state(&self) -> bool { - matches!(self.as_object(), Some(object) if object.state().is_some()) - } - - /// Get the internal state of an object. - pub fn get_internal_state(&self) -> Option { - self.as_object() - .and_then(|object| object.state().as_ref().cloned()) - } - - /// Run a function with a reference to the internal state. - /// - /// # Panics - /// - /// This will panic if this value doesn't have an internal state or if the internal state doesn't - /// have the concrete type `S`. - pub fn with_internal_state_ref(&self, f: F) -> R - where - S: Any + InternalState, - F: FnOnce(&S) -> R, - { - if let Some(object) = self.as_object() { - let state = object - .state() - .as_ref() - .expect("no state") - .downcast_ref() - .expect("wrong state type"); - f(state) - } else { - panic!("not an object"); - } - } - - /// Run a function with a mutable reference to the internal state. - /// - /// # Panics - /// - /// This will panic if this value doesn't have an internal state or if the internal state doesn't - /// have the concrete type `S`. - pub fn with_internal_state_mut(&self, f: F) -> R - where - S: Any + InternalState, - F: FnOnce(&mut S) -> R, - { - if let Some(mut object) = self.as_object_mut() { - let state = object - .state_mut() - .as_mut() - .expect("no state") - .downcast_mut() - .expect("wrong state type"); - f(state) - } else { - panic!("not an object"); - } - } - /// Check to see if the Value has the field, mainly used by environment records. #[inline] pub fn has_field(&self, field: &str) -> bool { @@ -646,27 +584,6 @@ impl Value { property } - /// Set internal state of an Object. Discards the previous state if it was set. - pub fn set_internal_state(&self, state: T) { - if let Some(mut object) = self.as_object_mut() { - object.state_mut().replace(InternalStateCell::new(state)); - } - } - - /// Consume the function and return a Value - pub fn from_func(function: Function) -> Value { - // Get Length - let length = function.params.len(); - // Object with Kind set to function - // TODO: FIXME: Add function prototype - let new_func = Object::function(function, Value::null()); - // Wrap Object in GC'd Value - let new_func_val = Value::from(new_func); - // Set length to parameters - new_func_val.set_field("length", Value::from(length)); - new_func_val - } - /// The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType. /// /// diff --git a/boa/src/environment/function_environment_record.rs b/boa/src/environment/function_environment_record.rs index 8a8c4b0b80..c6a5a866a6 100644 --- a/boa/src/environment/function_environment_record.rs +++ b/boa/src/environment/function_environment_record.rs @@ -9,7 +9,7 @@ //! More info: use crate::{ - builtins::value::Value, + builtins::{object::GcObject, value::Value}, environment::{ declarative_environment_record::DeclarativeEnvironmentRecordBinding, environment_record_trait::EnvironmentRecordTrait, @@ -44,7 +44,7 @@ pub struct FunctionEnvironmentRecord { /// If the value is "lexical", this is an ArrowFunction and does not have a local this value. pub this_binding_status: BindingStatus, /// The function object whose invocation caused this Environment Record to be created. - pub function: Value, + pub function: GcObject, /// If the associated function has super property accesses and is not an ArrowFunction, /// [[HomeObject]] is the object that the function is bound to as a method. /// The default value for [[HomeObject]] is undefined. diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index 66bf668d77..2a1ecd448d 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -6,7 +6,7 @@ //! This is the entrypoint to lexical environments. use crate::{ - builtins::value::Value, + builtins::{object::GcObject, value::Value}, environment::{ declarative_environment_record::DeclarativeEnvironmentRecord, environment_record_trait::EnvironmentRecordTrait, @@ -162,11 +162,7 @@ impl LexicalEnvironment { }) .expect("No function or global environment"); - #[allow(clippy::let_and_return)] - // FIXME need to assign result to a variable to avoid borrow checker error - // (borrowed value `env` does not live long enough) - let b = env.borrow_mut().create_immutable_binding(name, deletion); - b + env.borrow_mut().create_immutable_binding(name, deletion) } } } @@ -230,7 +226,7 @@ pub fn new_declarative_environment(env: Option) -> Environment { } pub fn new_function_environment( - f: Value, + f: GcObject, this: Option, outer: Option, binding_status: BindingStatus, diff --git a/boa/src/exec/declaration/mod.rs b/boa/src/exec/declaration/mod.rs index dd1215700a..5b5678de10 100644 --- a/boa/src/exec/declaration/mod.rs +++ b/boa/src/exec/declaration/mod.rs @@ -2,7 +2,7 @@ use super::{Executable, Interpreter}; use crate::{ - builtins::{function::ThisMode, value::Value}, + builtins::{function::FunctionFlags, Value}, environment::lexical_environment::VariableScope, syntax::ast::node::{ ArrowFunctionDecl, ConstDeclList, FunctionDecl, FunctionExpr, LetDeclList, VarDeclList, @@ -16,9 +16,7 @@ impl Executable for FunctionDecl { let val = interpreter.create_function( self.parameters().to_vec(), self.body().to_vec(), - ThisMode::NonLexical, - true, - true, + FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, ); // Set the name and assign it in the current environment @@ -43,9 +41,7 @@ impl Executable for FunctionExpr { let val = interpreter.create_function( self.parameters().to_vec(), self.body().to_vec(), - ThisMode::NonLexical, - true, - true, + FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, ); if let Some(name) = self.name() { @@ -127,9 +123,9 @@ impl Executable for ArrowFunctionDecl { Ok(interpreter.create_function( self.params().to_vec(), self.body().to_vec(), - ThisMode::Lexical, - false, - true, + FunctionFlags::CALLABLE + | FunctionFlags::CONSTRUCTABLE + | FunctionFlags::LEXICAL_THIS_MODE, )) } } diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 5e42f41409..882ed51741 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -25,8 +25,8 @@ mod try_node; use crate::{ builtins, builtins::{ - function::{Function as FunctionObject, FunctionBody, ThisMode}, - object::{Object, ObjectData, PROTOTYPE}, + function::{Function, FunctionFlags, NativeFunction}, + object::{GcObject, Object, ObjectData, PROTOTYPE}, property::PropertyKey, value::{PreferredType, Type, Value}, Console, @@ -125,40 +125,25 @@ impl Interpreter { &mut self, params: P, body: B, - this_mode: ThisMode, - constructable: bool, - callable: bool, + flags: FunctionFlags, ) -> Value where P: Into>, B: Into, { - let function_prototype = self - .realm - .environment - .get_global_object() - .expect("Could not get the global object") - .get_field("Function") - .get_field(PROTOTYPE); + let function_prototype = self.global().get_field("Function").get_field(PROTOTYPE); // Every new function has a prototype property pre-made - let global_val = &self - .realm - .environment - .get_global_object() - .expect("Could not get the global object"); - let proto = Value::new_object(Some(global_val)); + let proto = Value::new_object(Some(self.global())); let params = params.into(); let params_len = params.len(); - let func = FunctionObject::new( + let func = Function::Ordinary { + flags, + body: body.into(), params, - Some(self.realm.environment.get_current_environment().clone()), - FunctionBody::Ordinary(body.into()), - this_mode, - constructable, - callable, - ); + environment: self.realm.environment.get_current_environment().clone(), + }; let new_func = Object::function(func, function_prototype); @@ -169,21 +154,43 @@ impl Interpreter { val } - /// - pub(crate) fn call( + /// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions + pub fn create_builtin_function( &mut self, - f: &Value, - this: &Value, - arguments_list: &[Value], - ) -> Result { + name: &str, + length: usize, + body: NativeFunction, + ) -> Result { + let function_prototype = self.global().get_field("Function").get_field(PROTOTYPE); + + // Every new function has a prototype property pre-made + let proto = Value::new_object(Some(self.global())); + let mut function = Object::function( + Function::BuiltIn(body.into(), FunctionFlags::CALLABLE), + function_prototype, + ); + function.set(&PROTOTYPE.into(), proto); + function.set(&"length".into(), length.into()); + function.set(&"name".into(), name.into()); + + Ok(GcObject::new(function)) + } + + pub fn register_global_function( + &mut self, + name: &str, + length: usize, + body: NativeFunction, + ) -> Result<()> { + let function = self.create_builtin_function(name, length, body)?; + self.global().set_field(name, function); + Ok(()) + } + + /// + pub(crate) fn call(&mut self, f: &Value, this: &Value, args: &[Value]) -> Result { match *f { - Value::Object(ref obj) => { - let obj = obj.borrow(); - if let ObjectData::Function(ref func) = obj.data { - return func.call(f.clone(), this, arguments_list, self); - } - self.throw_type_error("not a function") - } + Value::Object(ref object) => object.call(this, args, self), _ => self.throw_type_error("not a function"), } } diff --git a/boa/src/exec/new/mod.rs b/boa/src/exec/new/mod.rs index 419c35b3e8..5bb6bec9e3 100644 --- a/boa/src/exec/new/mod.rs +++ b/boa/src/exec/new/mod.rs @@ -1,9 +1,6 @@ use super::{Executable, Interpreter}; use crate::{ - builtins::{ - object::{ObjectData, PROTOTYPE}, - value::Value, - }, + builtins::{object::PROTOTYPE, Value}, syntax::ast::node::New, BoaProfiler, Result, }; @@ -11,10 +8,6 @@ use crate::{ impl Executable for New { fn run(&self, interpreter: &mut Interpreter) -> Result { let _timer = BoaProfiler::global().start_event("New", "exec"); - // let (callee, args) = match call.as_ref() { - // Node::Call(callee, args) => (callee, args), - // _ => unreachable!("Node::New(ref call): 'call' must only be Node::Call type."), - // }; let func_object = self.expr().run(interpreter)?; let mut v_args = Vec::with_capacity(self.args().len()); @@ -28,13 +21,7 @@ impl Executable for New { .set_prototype(func_object.get_field(PROTOTYPE)); match func_object { - Value::Object(ref obj) => { - let obj = obj.borrow(); - if let ObjectData::Function(ref func) = obj.data { - return func.construct(func_object.clone(), &this, &v_args, interpreter); - } - interpreter.throw_type_error("not a constructor") - } + Value::Object(ref object) => object.construct(&this, &v_args, interpreter), _ => Ok(Value::undefined()), } } diff --git a/boa/src/realm.rs b/boa/src/realm.rs index f4f1f1839c..feaad72aa9 100644 --- a/boa/src/realm.rs +++ b/boa/src/realm.rs @@ -5,10 +5,7 @@ //! A realm is represented in this implementation as a Realm struct with the fields specified from the spec. use crate::{ - builtins::{ - function::{Function, NativeFunctionData}, - value::Value, - }, + builtins::value::Value, environment::{ declarative_environment_record::DeclarativeEnvironmentRecord, global_environment_record::GlobalEnvironmentRecord, @@ -49,14 +46,6 @@ impl Realm { environment: LexicalEnvironment::new(global), } } - - /// Utility to add a function to the global object - pub fn register_global_func(self, func_name: &str, func: NativeFunctionData) -> Self { - let func = Function::builtin(Vec::new(), func); - self.global_obj.set_field(func_name, Value::from_func(func)); - - self - } } // Similar to new_global_environment in lexical_environment, except we need to return a GlobalEnvirionment