diff --git a/boa/src/builtins/array/array_iterator.rs b/boa/src/builtins/array/array_iterator.rs index 695595fcaea..5e0c0d7286b 100644 --- a/boa/src/builtins/array/array_iterator.rs +++ b/boa/src/builtins/array/array_iterator.rs @@ -1,7 +1,7 @@ use crate::{ - builtins::{iterable::create_iter_result_object, Array, JsValue}, + builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue}, gc::{Finalize, Trace}, - object::{function::make_builtin_fn, JsObject, ObjectData}, + object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, @@ -44,13 +44,11 @@ impl ArrayIterator { kind: PropertyNameKind, context: &Context, ) -> JsValue { - let array_iterator = JsValue::new_object(context); - array_iterator.set_data(ObjectData::array_iterator(Self::new(array, kind))); - array_iterator - .as_object() - .expect("array iterator object") - .set_prototype_instance(context.iterator_prototypes().array_iterator().into()); - array_iterator + let array_iterator = JsObject::from_proto_and_data( + Some(context.iterator_prototypes().array_iterator()), + ObjectData::array_iterator(Self::new(array, kind)), + ); + array_iterator.into() } /// %ArrayIteratorPrototype%.next( ) @@ -114,13 +112,16 @@ impl ArrayIterator { /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object - pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject { + pub(crate) fn create_prototype( + iterator_prototype: JsObject, + context: &mut Context, + ) -> JsObject { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); // Create prototype - let array_iterator = context.construct_object(); + let array_iterator = + JsObject::from_proto_and_data(Some(iterator_prototype), ObjectData::ordinary()); make_builtin_fn(Self::next, "next", &array_iterator, 0, context); - array_iterator.set_prototype_instance(iterator_prototype); let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag_property = PropertyDescriptor::builder() diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 0b8f1a4a633..3652ac10fd2 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -223,12 +223,7 @@ impl Array { Some(prototype) => prototype, None => context.standard_objects().array_object().prototype(), }; - let array = context.construct_object(); - - array.set_prototype_instance(prototype.into()); - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - array.borrow_mut().data = ObjectData::array(); + let array = JsObject::from_proto_and_data(Some(prototype), ObjectData::array()); // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). crate::object::internal_methods::ordinary_define_own_property( @@ -275,22 +270,9 @@ impl Array { /// Creates a new `Array` instance. pub(crate) fn new_array(context: &mut Context) -> JsValue { - let array = JsValue::new_object(context); - array.set_data(ObjectData::array()); - array - .as_object() - .expect("'array' should be an object") - .set_prototype_instance(context.standard_objects().array_object().prototype().into()); - array.set_property( - "length", - PropertyDescriptor::builder() - .value(0) - .writable(true) - .enumerable(false) - .configurable(false) - .build(), - ); - array + Self::array_create(0, None, context) + .expect("creating an empty array with the default prototype must not fail") + .into() } /// Utility function for concatenating array objects. diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index acd9d6ab79a..279439f61f8 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -15,7 +15,9 @@ mod tests; use crate::{ builtins::BuiltIn, context::StandardObjects, - object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, + }, property::Attribute, BoaProfiler, Context, JsResult, JsValue, }; @@ -69,15 +71,9 @@ impl Boolean { } let prototype = get_prototype_from_constructor(new_target, StandardObjects::boolean_object, context)?; - let boolean = JsValue::new_object(context); + let boolean = JsObject::from_proto_and_data(Some(prototype), ObjectData::boolean(data)); - boolean - .as_object() - .expect("this should be an object") - .set_prototype_instance(prototype.into()); - boolean.set_data(ObjectData::boolean(data)); - - Ok(boolean) + Ok(boolean.into()) } /// An Utility function used to get the internal `[[BooleanData]]`. diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index 8a23ee1f5cd..ed63dfbfa7e 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -5,7 +5,9 @@ use crate::{ builtins::BuiltIn, context::StandardObjects, gc::{empty_trace, Finalize, Trace}, - object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, + }, property::Attribute, symbol::WellKnownSymbols, value::{JsValue, PreferredType}, @@ -346,16 +348,14 @@ impl Date { StandardObjects::object_object, context, )?; - let obj = context.construct_object(); - obj.set_prototype_instance(prototype.into()); - let this = obj.into(); - if args.is_empty() { - Ok(Self::make_date_now(&this)) + Ok(if args.is_empty() { + Self::make_date_now(prototype) } else if args.len() == 1 { - Self::make_date_single(&this, args, context) + Self::make_date_single(prototype, args, context)? } else { - Self::make_date_multiple(&this, args, context) + Self::make_date_multiple(prototype, args, context)? } + .into()) } } @@ -383,10 +383,8 @@ impl Date { /// /// [spec]: https://tc39.es/ecma262/#sec-date-constructor /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date - pub(crate) fn make_date_now(this: &JsValue) -> JsValue { - let date = Date::default(); - this.set_data(ObjectData::date(date)); - this.clone() + pub(crate) fn make_date_now(prototype: JsObject) -> JsObject { + JsObject::from_proto_and_data(Some(prototype), ObjectData::date(Date::default())) } /// `Date(value)` @@ -400,10 +398,10 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date-constructor /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date pub(crate) fn make_date_single( - this: &JsValue, + prototype: JsObject, args: &[JsValue], context: &mut Context, - ) -> JsResult { + ) -> JsResult { let value = &args[0]; let tv = match this_time_value(value, context) { Ok(dt) => dt.0, @@ -426,9 +424,10 @@ impl Date { }; let tv = tv.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some()); - let date = Date(tv); - this.set_data(ObjectData::date(date)); - Ok(this.clone()) + Ok(JsObject::from_proto_and_data( + Some(prototype), + ObjectData::date(Date(tv)), + )) } /// `Date(year, month [ , date [ , hours [ , minutes [ , seconds [ , ms ] ] ] ] ])` @@ -442,10 +441,10 @@ impl Date { /// [spec]: https://tc39.es/ecma262/#sec-date-constructor /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date pub(crate) fn make_date_multiple( - this: &JsValue, + prototype: JsObject, args: &[JsValue], context: &mut Context, - ) -> JsResult { + ) -> JsResult { let mut year = args[0].to_number(context)?; let month = args[1].to_number(context)?; let day = args @@ -466,9 +465,10 @@ impl Date { // If any of the args are infinity or NaN, return an invalid date. if !check_normal_opt!(year, month, day, hour, min, sec, milli) { - let date = Date(None); - this.set_data(ObjectData::date(date)); - return Ok(this.clone()); + return Ok(JsObject::from_proto_and_data( + Some(prototype), + ObjectData::date(Date(None)), + )); } if (0.0..=99.0).contains(&year) { @@ -493,9 +493,10 @@ impl Date { Some(milli), ); - this.set_data(ObjectData::date(date)); - - Ok(this.clone()) + Ok(JsObject::from_proto_and_data( + Some(prototype), + ObjectData::date(date), + )) } /// `Date.prototype[@@toPrimitive]` diff --git a/boa/src/builtins/error/eval.rs b/boa/src/builtins/error/eval.rs index 21fb99ca9ae..9b76ea0d9a0 100644 --- a/boa/src/builtins/error/eval.rs +++ b/boa/src/builtins/error/eval.rs @@ -13,6 +13,7 @@ use crate::context::StandardObjects; use crate::object::internal_methods::get_prototype_from_constructor; +use crate::object::JsObject; use crate::{ builtins::BuiltIn, @@ -66,18 +67,12 @@ impl EvalError { ) -> JsResult { let prototype = get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = context.construct_object(); - obj.set_prototype_instance(prototype.into()); - let this = JsValue::new(obj); + let obj = JsObject::from_proto_and_data(Some(prototype), ObjectData::error()); if let Some(message) = args.get(0) { if !message.is_undefined() { - this.set_field("message", message.to_string(context)?, false, context)?; + obj.set("message", message.to_string(context)?, false, context)?; } } - - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_data(ObjectData::error()); - Ok(this) + Ok(obj.into()) } } diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index 89791f7c2c2..7899b9d27e5 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -13,7 +13,9 @@ use crate::{ builtins::BuiltIn, context::StandardObjects, - object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, + }, profiler::BoaProfiler, property::Attribute, Context, JsResult, JsValue, @@ -81,19 +83,13 @@ impl Error { ) -> JsResult { let prototype = get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = context.construct_object(); - obj.set_prototype_instance(prototype.into()); - let this = JsValue::new(obj); + let obj = JsObject::from_proto_and_data(Some(prototype), ObjectData::error()); if let Some(message) = args.get(0) { if !message.is_undefined() { - this.set_field("message", message.to_string(context)?, false, context)?; + obj.set("message", message.to_string(context)?, false, context)?; } } - - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_data(ObjectData::error()); - Ok(this) + Ok(obj.into()) } /// `Error.prototype.toString()` diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index b58f7e5ae7d..88b97a8a051 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -12,7 +12,9 @@ use crate::{ builtins::BuiltIn, context::StandardObjects, - object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, + }, profiler::BoaProfiler, property::Attribute, Context, JsResult, JsValue, @@ -62,18 +64,12 @@ impl RangeError { ) -> JsResult { let prototype = get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = context.construct_object(); - obj.set_prototype_instance(prototype.into()); - let this = JsValue::new(obj); + let obj = JsObject::from_proto_and_data(Some(prototype), ObjectData::error()); if let Some(message) = args.get(0) { if !message.is_undefined() { - this.set_field("message", message.to_string(context)?, false, context)?; + obj.set("message", message.to_string(context)?, false, context)?; } } - - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_data(ObjectData::error()); - Ok(this) + Ok(obj.into()) } } diff --git a/boa/src/builtins/error/reference.rs b/boa/src/builtins/error/reference.rs index 8384068ca29..ea1a236b91e 100644 --- a/boa/src/builtins/error/reference.rs +++ b/boa/src/builtins/error/reference.rs @@ -12,7 +12,9 @@ use crate::{ builtins::BuiltIn, context::StandardObjects, - object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, + }, profiler::BoaProfiler, property::Attribute, Context, JsResult, JsValue, @@ -61,18 +63,12 @@ impl ReferenceError { ) -> JsResult { let prototype = get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = context.construct_object(); - obj.set_prototype_instance(prototype.into()); - let this = JsValue::new(obj); + let obj = JsObject::from_proto_and_data(Some(prototype), ObjectData::error()); if let Some(message) = args.get(0) { if !message.is_undefined() { - this.set_field("message", message.to_string(context)?, false, context)?; + obj.set("message", message.to_string(context)?, false, context)?; } } - - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_data(ObjectData::error()); - Ok(this) + Ok(obj.into()) } } diff --git a/boa/src/builtins/error/syntax.rs b/boa/src/builtins/error/syntax.rs index d8d8a0404f1..5353313d881 100644 --- a/boa/src/builtins/error/syntax.rs +++ b/boa/src/builtins/error/syntax.rs @@ -14,7 +14,9 @@ use crate::{ builtins::BuiltIn, context::StandardObjects, - object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, + }, profiler::BoaProfiler, property::Attribute, Context, JsResult, JsValue, @@ -64,18 +66,12 @@ impl SyntaxError { ) -> JsResult { let prototype = get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = context.construct_object(); - obj.set_prototype_instance(prototype.into()); - let this = JsValue::new(obj); + let obj = JsObject::from_proto_and_data(Some(prototype), ObjectData::error()); if let Some(message) = args.get(0) { if !message.is_undefined() { - this.set_field("message", message.to_string(context)?, false, context)?; + obj.set("message", message.to_string(context)?, false, context)?; } } - - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_data(ObjectData::error()); - Ok(this) + Ok(obj.into()) } } diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index c0a7455f797..6eec1096ab1 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -18,7 +18,9 @@ use crate::{ builtins::BuiltIn, context::StandardObjects, - object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, + }, property::Attribute, BoaProfiler, Context, JsResult, JsValue, }; @@ -67,18 +69,12 @@ impl TypeError { ) -> JsResult { let prototype = get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = context.construct_object(); - obj.set_prototype_instance(prototype.into()); - let this = JsValue::new(obj); + let obj = JsObject::from_proto_and_data(Some(prototype), ObjectData::error()); if let Some(message) = args.get(0) { if !message.is_undefined() { - this.set_field("message", message.to_string(context)?, false, context)?; + obj.set("message", message.to_string(context)?, false, context)?; } } - - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_data(ObjectData::error()); - Ok(this) + Ok(obj.into()) } } diff --git a/boa/src/builtins/error/uri.rs b/boa/src/builtins/error/uri.rs index d5ef0b0c5dd..dbdaa6fb38b 100644 --- a/boa/src/builtins/error/uri.rs +++ b/boa/src/builtins/error/uri.rs @@ -13,7 +13,9 @@ use crate::{ builtins::BuiltIn, context::StandardObjects, - object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData, + }, profiler::BoaProfiler, property::Attribute, Context, JsResult, JsValue, @@ -63,18 +65,12 @@ impl UriError { ) -> JsResult { let prototype = get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; - let obj = context.construct_object(); - obj.set_prototype_instance(prototype.into()); - let this = JsValue::new(obj); + let obj = JsObject::from_proto_and_data(Some(prototype), ObjectData::error()); if let Some(message) = args.get(0) { if !message.is_undefined() { - this.set_field("message", message.to_string(context)?, false, context)?; + obj.set("message", message.to_string(context)?, false, context)?; } } - - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_data(ObjectData::error()); - Ok(this) + Ok(obj.into()) } } diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 819169b7d38..8eb8c42df45 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -11,14 +11,26 @@ //! [spec]: https://tc39.es/ecma262/#sec-function-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function +use std::{ + fmt, + ops::{Deref, DerefMut}, +}; + +use dyn_clone::DynClone; + use crate::{ builtins::BuiltIn, context::StandardObjects, + environment::lexical_environment::Environment, + gc::{Finalize, Trace}, + object::JsObject, object::{ - function::Function, internal_methods::get_prototype_from_constructor, ConstructorBuilder, - FunctionBuilder, ObjectData, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, + NativeObject, ObjectData, }, property::Attribute, + property::PropertyDescriptor, + syntax::ast::node::{FormalParameter, RcStatementList}, BoaProfiler, Context, JsResult, JsValue, }; @@ -27,6 +39,349 @@ use super::JsArgs; #[cfg(test)] mod tests; +/// Type representing a native built-in function a.k.a. function pointer. +/// +/// Native functions need to have this signature in order to +/// be callable from Javascript. +pub type NativeFunctionSignature = fn(&JsValue, &[JsValue], &mut Context) -> JsResult; + +// Allows restricting closures to only `Copy` ones. +// Used the sealed pattern to disallow external implementations +// of `DynCopy`. +mod sealed { + pub trait Sealed {} + impl Sealed for T {} +} +pub trait DynCopy: sealed::Sealed {} +impl DynCopy for T {} + +/// Trait representing a native built-in closure. +/// +/// Closures need to have this signature in order to +/// be callable from Javascript, but most of the time the compiler +/// is smart enough to correctly infer the types. +pub trait ClosureFunctionSignature: + Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + DynCopy + DynClone + 'static +{ +} + +// The `Copy` bound automatically infers `DynCopy` and `DynClone` +impl ClosureFunctionSignature for T where + T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + Copy + 'static +{ +} + +// Allows cloning Box +dyn_clone::clone_trait_object!(ClosureFunctionSignature); + +#[derive(Debug, Trace, Finalize, PartialEq, Clone)] +pub enum ThisMode { + Lexical, + Strict, + Global, +} + +impl ThisMode { + /// Returns `true` if the this mode is `Lexical`. + pub fn is_lexical(&self) -> bool { + matches!(self, Self::Lexical) + } + + /// Returns `true` if the this mode is `Strict`. + pub fn is_strict(&self) -> bool { + matches!(self, Self::Strict) + } + + /// Returns `true` if the this mode is `Global`. + pub fn is_global(&self) -> bool { + matches!(self, Self::Global) + } +} + +#[derive(Debug, Trace, Finalize, PartialEq, Clone)] +pub enum ConstructorKind { + Base, + Derived, +} + +impl ConstructorKind { + /// Returns `true` if the constructor kind is `Base`. + pub fn is_base(&self) -> bool { + matches!(self, Self::Base) + } + + /// Returns `true` if the constructor kind is `Derived`. + pub fn is_derived(&self) -> bool { + matches!(self, Self::Derived) + } +} +// We don't use a standalone `NativeObject` for `Captures` because it doesn't +// guarantee that the internal type implements `Clone`. +// This private trait guarantees that the internal type passed to `Captures` +// implements `Clone`, and `DynClone` allows us to implement `Clone` for +// `Box`. +trait CapturesObject: NativeObject + DynClone {} +impl CapturesObject for T {} +dyn_clone::clone_trait_object!(CapturesObject); + +/// Wrapper for `Box` that allows passing additional +/// captures through a `Copy` closure. +/// +/// Any type implementing `Trace + Any + Debug + Clone` +/// can be used as a capture context, so you can pass e.g. a String, +/// a tuple or even a full struct. +/// +/// You can downcast to any type and handle the fail case as you like +/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref` +/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast +/// fails. +#[derive(Debug, Clone, Trace, Finalize)] +pub struct Captures(Box); + +impl Captures { + /// Creates a new capture context. + pub(crate) fn new(captures: T) -> Self + where + T: NativeObject + Clone, + { + Self(Box::new(captures)) + } + + /// Downcasts `Captures` to the specified type, returning a reference to the + /// downcasted type if successful or `None` otherwise. + pub fn downcast_ref(&self) -> Option<&T> + where + T: NativeObject + Clone, + { + self.0.deref().as_any().downcast_ref::() + } + + /// Mutably downcasts `Captures` to the specified type, returning a + /// mutable reference to the downcasted type if successful or `None` otherwise. + pub fn downcast_mut(&mut self) -> Option<&mut T> + where + T: NativeObject + Clone, + { + self.0.deref_mut().as_mut_any().downcast_mut::() + } + + /// Downcasts `Captures` to the specified type, returning a reference to the + /// downcasted type if successful or a `TypeError` otherwise. + pub fn try_downcast_ref(&self, context: &mut Context) -> JsResult<&T> + where + T: NativeObject + Clone, + { + self.0 + .deref() + .as_any() + .downcast_ref::() + .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) + } + + /// Downcasts `Captures` to the specified type, returning a reference to the + /// downcasted type if successful or a `TypeError` otherwise. + pub fn try_downcast_mut(&mut self, context: &mut Context) -> JsResult<&mut T> + where + T: NativeObject + Clone, + { + self.0 + .deref_mut() + .as_mut_any() + .downcast_mut::() + .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) + } +} + +/// Boa representation of a Function Object. +/// +/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) +/// +/// +#[derive(Clone, Trace, Finalize)] +pub enum Function { + Native { + #[unsafe_ignore_trace] + function: NativeFunctionSignature, + constructable: bool, + }, + Closure { + #[unsafe_ignore_trace] + function: Box, + constructable: bool, + captures: Captures, + }, + Ordinary { + constructable: bool, + this_mode: ThisMode, + body: RcStatementList, + params: Box<[FormalParameter]>, + environment: Environment, + }, + #[cfg(feature = "vm")] + VmOrdinary { + code: gc::Gc, + environment: Environment, + }, +} + +impl fmt::Debug for Function { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Function {{ ... }}") + } +} + +impl Function { + // Adds the final rest parameters to the Environment as an array + #[cfg(not(feature = "vm"))] + pub(crate) fn add_rest_param( + param: &FormalParameter, + index: usize, + args_list: &[JsValue], + context: &mut Context, + local_env: &Environment, + ) { + use crate::builtins::Array; + // Create array of values + let array = Array::new_array(context); + Array::add_to_array_object(&array, args_list.get(index..).unwrap_or_default(), context) + .unwrap(); + + // Create binding + local_env + // Function parameters can share names in JavaScript... + .create_mutable_binding(param.name(), false, true, context) + .expect("Failed to create binding for rest param"); + + // Set Binding to value + local_env + .initialize_binding(param.name(), array, context) + .expect("Failed to initialize rest param"); + } + + // Adds an argument to the environment + pub(crate) fn add_arguments_to_environment( + param: &FormalParameter, + value: JsValue, + local_env: &Environment, + context: &mut Context, + ) { + // Create binding + local_env + .create_mutable_binding(param.name(), false, true, context) + .expect("Failed to create binding"); + + // Set Binding to value + local_env + .initialize_binding(param.name(), value, context) + .expect("Failed to intialize binding"); + } + + /// Returns true if the function object is constructable. + pub fn is_constructable(&self) -> bool { + match self { + Self::Native { constructable, .. } => *constructable, + Self::Closure { constructable, .. } => *constructable, + Self::Ordinary { constructable, .. } => *constructable, + #[cfg(feature = "vm")] + Self::VmOrdinary { code, .. } => code.constructable, + } + } +} + +/// Arguments. +/// +/// +pub fn create_unmapped_arguments_object( + arguments_list: &[JsValue], + context: &mut Context, +) -> JsResult { + let len = arguments_list.len(); + let obj = JsObject::empty(); + // Set length + let length = PropertyDescriptor::builder() + .value(len) + .writable(true) + .enumerable(false) + .configurable(true) + .build(); + // Define length as a property + crate::object::internal_methods::ordinary_define_own_property( + &obj, + "length".into(), + length, + context, + )?; + let mut index: usize = 0; + while index < len { + let val = arguments_list.get(index).expect("Could not get argument"); + let prop = PropertyDescriptor::builder() + .value(val.clone()) + .writable(true) + .enumerable(true) + .configurable(true); + + obj.insert(index, prop); + index += 1; + } + + Ok(JsValue::new(obj)) +} + +/// Creates a new member function of a `Object` or `prototype`. +/// +/// A function registered using this macro can then be called from Javascript using: +/// +/// parent.name() +/// +/// See the javascript 'Number.toString()' as an example. +/// +/// # Arguments +/// function: The function to register as a built in function. +/// name: The name of the function (how it will be called but without the ()). +/// parent: The object to register the function on, if the global object is used then the function is instead called as name() +/// without requiring the parent, see parseInt() as an example. +/// length: As described at , The value of the "length" property is an integer that +/// indicates the typical number of arguments expected by the function. However, the language permits the function to be invoked with +/// some other number of arguments. +/// +/// If no length is provided, the length will be set to 0. +// TODO: deprecate/remove this. +pub(crate) fn make_builtin_fn( + function: NativeFunctionSignature, + name: N, + parent: &JsObject, + length: usize, + interpreter: &Context, +) where + N: Into, +{ + let name = name.into(); + let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init"); + + let function = JsObject::from_proto_and_data( + Some(interpreter.standard_objects().function_object().prototype()), + ObjectData::function(Function::Native { + function, + constructable: false, + }), + ); + let attribute = PropertyDescriptor::builder() + .writable(false) + .enumerable(false) + .configurable(true); + function.insert_property("length", attribute.clone().value(length)); + function.insert_property("name", attribute.value(name.as_str())); + + parent.clone().insert_property( + name, + PropertyDescriptor::builder() + .value(function) + .writable(true) + .enumerable(false) + .configurable(true), + ); +} + #[derive(Debug, Clone, Copy)] pub struct BuiltInFunctionObject; @@ -40,17 +395,16 @@ impl BuiltInFunctionObject { ) -> JsResult { let prototype = get_prototype_from_constructor(new_target, StandardObjects::function_object, context)?; - let this = JsValue::new_object(context); - this.as_object() - .expect("this should be an object") - .set_prototype_instance(prototype.into()); + let this = JsObject::from_proto_and_data( + Some(prototype), + ObjectData::function(Function::Native { + function: |_, _, _| Ok(JsValue::undefined()), + constructable: true, + }), + ); - this.set_data(ObjectData::function(Function::Native { - function: |_, _, _| Ok(JsValue::undefined()), - constructable: true, - })); - Ok(this) + Ok(this.into()) } fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { diff --git a/boa/src/builtins/iterable/mod.rs b/boa/src/builtins/iterable/mod.rs index a6d852d3a19..d06c7b7593f 100644 --- a/boa/src/builtins/iterable/mod.rs +++ b/boa/src/builtins/iterable/mod.rs @@ -24,24 +24,15 @@ impl IteratorPrototypes { pub(crate) fn init(context: &mut Context) -> Self { let iterator_prototype = create_iterator_prototype(context); Self { - array_iterator: ArrayIterator::create_prototype( - iterator_prototype.clone().into(), - context, - ), - set_iterator: SetIterator::create_prototype(iterator_prototype.clone().into(), context), - string_iterator: StringIterator::create_prototype( - iterator_prototype.clone().into(), - context, - ), + array_iterator: ArrayIterator::create_prototype(iterator_prototype.clone(), context), + set_iterator: SetIterator::create_prototype(iterator_prototype.clone(), context), + string_iterator: StringIterator::create_prototype(iterator_prototype.clone(), context), regexp_string_iterator: RegExpStringIterator::create_prototype( - iterator_prototype.clone().into(), - context, - ), - map_iterator: MapIterator::create_prototype(iterator_prototype.clone().into(), context), - for_in_iterator: ForInIterator::create_prototype( - iterator_prototype.clone().into(), + iterator_prototype.clone(), context, ), + map_iterator: MapIterator::create_prototype(iterator_prototype.clone(), context), + for_in_iterator: ForInIterator::create_prototype(iterator_prototype.clone(), context), iterator_prototype, } } diff --git a/boa/src/builtins/map/map_iterator.rs b/boa/src/builtins/map/map_iterator.rs index 8b005b24aa5..f25844893e9 100644 --- a/boa/src/builtins/map/map_iterator.rs +++ b/boa/src/builtins/map/map_iterator.rs @@ -1,6 +1,6 @@ use crate::{ - builtins::{iterable::create_iter_result_object, Array, JsValue}, - object::{function::make_builtin_fn, JsObject, ObjectData}, + builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue}, + object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, @@ -47,13 +47,11 @@ impl MapIterator { map_iteration_kind: kind, lock, }; - let map_iterator = JsValue::new_object(context); - map_iterator.set_data(ObjectData::map_iterator(iter)); - map_iterator - .as_object() - .expect("map iterator object") - .set_prototype_instance(context.iterator_prototypes().map_iterator().into()); - return Ok(map_iterator); + let map_iterator = JsObject::from_proto_and_data( + Some(context.iterator_prototypes().map_iterator()), + ObjectData::map_iterator(iter), + ); + return Ok(map_iterator.into()); } } context.throw_type_error("`this` is not a Map") @@ -126,13 +124,16 @@ impl MapIterator { /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%-object - pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject { + pub(crate) fn create_prototype( + iterator_prototype: JsObject, + context: &mut Context, + ) -> JsObject { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); // Create prototype - let map_iterator = context.construct_object(); + let map_iterator = + JsObject::from_proto_and_data(Some(iterator_prototype), ObjectData::ordinary()); make_builtin_fn(Self::next, "next", &map_iterator, 0, context); - map_iterator.set_prototype_instance(iterator_prototype); let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag_property = PropertyDescriptor::builder() diff --git a/boa/src/builtins/map/mod.rs b/boa/src/builtins/map/mod.rs index 227415f6999..7c8ec0c9dc0 100644 --- a/boa/src/builtins/map/mod.rs +++ b/boa/src/builtins/map/mod.rs @@ -136,13 +136,11 @@ impl Map { } // 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%Map.prototype%", « [[MapData]] »). + // 3. Set map.[[MapData]] to a new empty List. let prototype = get_prototype_from_constructor(new_target, StandardObjects::map_object, context)?; - let map = context.construct_object(); - map.set_prototype_instance(prototype.into()); - - // 3. Set map.[[MapData]] to a new empty List. - map.borrow_mut().data = ObjectData::map(OrderedMap::new()); + let map = + JsObject::from_proto_and_data(Some(prototype), ObjectData::map(OrderedMap::new())); // 4. If iterable is either undefined or null, return map. let iterable = match args.get_or_undefined(0) { diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 47135798527..92fbcb38060 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -16,12 +16,10 @@ use super::string::is_trimmable_whitespace; use super::JsArgs; use crate::context::StandardObjects; +use crate::object::JsObject; use crate::{ - builtins::BuiltIn, - object::{ - function::make_builtin_fn, internal_methods::get_prototype_from_constructor, - ConstructorBuilder, ObjectData, - }, + builtins::{function::make_builtin_fn, BuiltIn}, + object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, property::Attribute, value::{AbstractRelation, IntegerOrInfinity, JsValue}, BoaProfiler, Context, JsResult, @@ -172,13 +170,8 @@ impl Number { } let prototype = get_prototype_from_constructor(new_target, StandardObjects::number_object, context)?; - let this = JsValue::new_object(context); - this.as_object() - .expect("this should be an object") - .set_prototype_instance(prototype.into()); - this.set_data(ObjectData::number(data)); - - Ok(this) + let this = JsObject::from_proto_and_data(Some(prototype), ObjectData::number(data)); + Ok(this.into()) } /// This function returns a `JsResult` of the number `Value`. diff --git a/boa/src/builtins/object/for_in_iterator.rs b/boa/src/builtins/object/for_in_iterator.rs index bd2b02c0cd1..70ed65607ca 100644 --- a/boa/src/builtins/object/for_in_iterator.rs +++ b/boa/src/builtins/object/for_in_iterator.rs @@ -1,7 +1,7 @@ use crate::{ - builtins::iterable::create_iter_result_object, + builtins::{function::make_builtin_fn, iterable::create_iter_result_object}, gc::{Finalize, Trace}, - object::{function::make_builtin_fn, JsObject, ObjectData}, + object::{JsObject, ObjectData}, property::PropertyDescriptor, property::PropertyKey, symbol::WellKnownSymbols, @@ -46,13 +46,11 @@ impl ForInIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-createforiniterator pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context) -> JsValue { - let for_in_iterator = JsValue::new_object(context); - for_in_iterator.set_data(ObjectData::for_in_iterator(Self::new(object))); - for_in_iterator - .as_object() - .expect("for in iterator object") - .set_prototype_instance(context.iterator_prototypes().for_in_iterator().into()); - for_in_iterator + let for_in_iterator = JsObject::from_proto_and_data( + Some(context.iterator_prototypes().for_in_iterator()), + ObjectData::for_in_iterator(Self::new(object)), + ); + for_in_iterator.into() } /// %ForInIteratorPrototype%.next( ) @@ -129,13 +127,16 @@ impl ForInIterator { /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%-object - pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject { + pub(crate) fn create_prototype( + iterator_prototype: JsObject, + context: &mut Context, + ) -> JsObject { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); // Create prototype - let for_in_iterator = context.construct_object(); + let for_in_iterator = + JsObject::from_proto_and_data(Some(iterator_prototype), ObjectData::ordinary()); make_builtin_fn(Self::next, "next", &for_in_iterator, 0, context); - for_in_iterator.set_prototype_instance(iterator_prototype); let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag_property = PropertyDescriptor::builder() diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 6773253db41..853e3904b2c 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -18,7 +18,7 @@ use crate::{ context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, IntegrityLevel, - JsObject, Object as BuiltinObject, ObjectData, ObjectInitializer, ObjectKind, + JsObject, ObjectData, ObjectInitializer, ObjectKind, }, property::{Attribute, DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, @@ -107,20 +107,15 @@ impl Object { StandardObjects::object_object, context, )?; - let object = JsValue::new_object(context); - - object - .as_object() - .expect("this should be an object") - .set_prototype_instance(prototype.into()); - return Ok(object); + let object = JsObject::from_proto_and_data(Some(prototype), ObjectData::ordinary()); + return Ok(object.into()); } if let Some(arg) = args.get(0) { if !arg.is_null_or_undefined() { return Ok(arg.to_object(context)?.into()); } } - Ok(JsValue::new_object(context)) + Ok(context.construct_object().into()) } /// `Object.create( proto, [propertiesObject] )` @@ -138,10 +133,9 @@ impl Object { let properties = args.get_or_undefined(1); let obj = match prototype { - JsValue::Object(_) | JsValue::Null => JsObject::new(BuiltinObject::with_prototype( - prototype.clone(), - ObjectData::ordinary(), - )), + JsValue::Object(_) | JsValue::Null => { + JsObject::from_proto_and_data(prototype.as_object(), ObjectData::ordinary()) + } _ => { return context.throw_type_error(format!( "Object prototype may only be an Object or null: {}", diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 51388dcac30..9e26943d473 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -17,7 +17,7 @@ use crate::{ gc::{empty_trace, Finalize, Trace}, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - JsObject, Object, ObjectData, + JsObject, ObjectData, }, property::Attribute, symbol::WellKnownSymbols, @@ -263,7 +263,7 @@ impl RegExp { fn alloc(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let proto = get_prototype_from_constructor(this, StandardObjects::regexp_object, context)?; - Ok(JsObject::new(Object::create(proto.into())).into()) + Ok(JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary()).into()) } /// `22.2.3.2.2 RegExpInitialize ( obj, pattern, flags )` @@ -983,7 +983,7 @@ impl RegExp { let named_groups = match_value.named_groups(); let groups = if named_groups.clone().count() > 0 { // a. Let groups be ! OrdinaryObjectCreate(null). - let groups = JsValue::new_object(context); + let groups = JsValue::from(JsObject::empty()); // Perform 27.f here // f. If the ith capture of R was defined with a GroupName, then diff --git a/boa/src/builtins/regexp/regexp_string_iterator.rs b/boa/src/builtins/regexp/regexp_string_iterator.rs index 8e703ae65e0..4d96822583d 100644 --- a/boa/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa/src/builtins/regexp/regexp_string_iterator.rs @@ -12,9 +12,9 @@ use regexp::{advance_string_index, RegExp}; use crate::{ - builtins::{iterable::create_iter_result_object, regexp}, + builtins::{function::make_builtin_fn, iterable::create_iter_result_object, regexp}, gc::{Finalize, Trace}, - object::{function::make_builtin_fn, JsObject, ObjectData}, + object::{JsObject, ObjectData}, property::PropertyDescriptor, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, JsString, JsValue, @@ -66,24 +66,18 @@ impl RegExpStringIterator { // and fullUnicode and performs the following steps when called: // 5. Return ! CreateIteratorFromClosure(closure, "%RegExpStringIteratorPrototype%", %RegExpStringIteratorPrototype%). - let regexp_string_iterator = JsValue::new_object(context); - regexp_string_iterator.set_data(ObjectData::reg_exp_string_iterator(Self::new( - matcher.clone(), - string, - global, - unicode, - ))); - regexp_string_iterator - .as_object() - .expect("regexp string iterator object") - .set_prototype_instance( - context - .iterator_prototypes() - .regexp_string_iterator() - .into(), - ); - - Ok(regexp_string_iterator) + + let regexp_string_iterator = JsObject::from_proto_and_data( + Some(context.iterator_prototypes().regexp_string_iterator()), + ObjectData::reg_exp_string_iterator(Self::new( + matcher.clone(), + string, + global, + unicode, + )), + ); + + Ok(regexp_string_iterator.into()) } pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { @@ -161,13 +155,16 @@ impl RegExpStringIterator { /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object - pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject { + pub(crate) fn create_prototype( + iterator_prototype: JsObject, + context: &mut Context, + ) -> JsObject { let _timer = BoaProfiler::global().start_event("RegExp String Iterator", "init"); // Create prototype - let result = context.construct_object(); + let result = + JsObject::from_proto_and_data(Some(iterator_prototype), ObjectData::ordinary()); make_builtin_fn(Self::next, "next", &result, 0, context); - result.set_prototype_instance(iterator_prototype); let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag_property = PropertyDescriptor::builder() diff --git a/boa/src/builtins/set/mod.rs b/boa/src/builtins/set/mod.rs index f1021b7b477..f178b99c278 100644 --- a/boa/src/builtins/set/mod.rs +++ b/boa/src/builtins/set/mod.rs @@ -15,7 +15,7 @@ use crate::{ context::StandardObjects, object::{ internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, - ObjectData, + JsObject, ObjectData, }, property::{Attribute, PropertyNameKind}, symbol::WellKnownSymbols, @@ -130,21 +130,17 @@ impl Set { let prototype = get_prototype_from_constructor(new_target, StandardObjects::set_object, context)?; - let obj = context.construct_object(); - obj.set_prototype_instance(prototype.into()); - - let set = JsValue::new(obj); - // 3 - set.set_data(ObjectData::set(OrderedSet::default())); + let obj = + JsObject::from_proto_and_data(Some(prototype), ObjectData::set(OrderedSet::default())); let iterable = args.get_or_undefined(0); // 4 if iterable.is_null_or_undefined() { - return Ok(set); + return Ok(obj.into()); } // 5 - let adder = set.get_field("add", context)?; + let adder = obj.get("add", context)?; // 6 if !adder.is_function() { @@ -163,7 +159,7 @@ impl Set { let next_value = next.value; // d, e - if let Err(status) = context.call(&adder, &set, &[next_value]) { + if let Err(status) = context.call(&adder, &obj.clone().into(), &[next_value]) { return iterator_record.close(Err(status), context); } @@ -171,7 +167,7 @@ impl Set { } // 8.b - Ok(set) + Ok(obj.into()) } /// `get Set [ @@species ]` diff --git a/boa/src/builtins/set/set_iterator.rs b/boa/src/builtins/set/set_iterator.rs index baf90f6b6a8..039d141fd1c 100644 --- a/boa/src/builtins/set/set_iterator.rs +++ b/boa/src/builtins/set/set_iterator.rs @@ -1,8 +1,8 @@ use crate::{ - builtins::iterable::create_iter_result_object, builtins::Array, builtins::JsValue, - object::{function::make_builtin_fn, JsObject, ObjectData}, + builtins::{function::make_builtin_fn, iterable::create_iter_result_object}, + object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, @@ -47,13 +47,11 @@ impl SetIterator { kind: PropertyNameKind, context: &Context, ) -> JsValue { - let set_iterator = JsValue::new_object(context); - set_iterator.set_data(ObjectData::set_iterator(Self::new(set, kind))); - set_iterator - .as_object() - .expect("set iterator object") - .set_prototype_instance(context.iterator_prototypes().set_iterator().into()); - set_iterator + let set_iterator = JsObject::from_proto_and_data( + Some(context.iterator_prototypes().set_iterator()), + ObjectData::set_iterator(Self::new(set, kind)), + ); + set_iterator.into() } /// %SetIteratorPrototype%.next( ) @@ -140,13 +138,16 @@ impl SetIterator { /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%-object - pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject { + pub(crate) fn create_prototype( + iterator_prototype: JsObject, + context: &mut Context, + ) -> JsObject { let _timer = BoaProfiler::global().start_event(Self::NAME, "init"); // Create prototype - let set_iterator = context.construct_object(); + let set_iterator = + JsObject::from_proto_and_data(Some(iterator_prototype), ObjectData::ordinary()); make_builtin_fn(Self::next, "next", &set_iterator, 0, context); - set_iterator.set_prototype_instance(iterator_prototype); let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag_property = PropertyDescriptor::builder() diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index 7ee8e0584df..60361a0da6c 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -205,9 +205,7 @@ impl String { // 4. Set S.[[GetOwnProperty]] as specified in 10.4.3.1. // 5. Set S.[[DefineOwnProperty]] as specified in 10.4.3.2. // 6. Set S.[[OwnPropertyKeys]] as specified in 10.4.3.3. - let s = context.construct_object(); - s.set_prototype_instance(prototype.into()); - s.borrow_mut().data = ObjectData::string(value); + let s = JsObject::from_proto_and_data(Some(prototype), ObjectData::string(value)); // 8. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor { [[Value]]: 𝔽(length), // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }). diff --git a/boa/src/builtins/string/string_iterator.rs b/boa/src/builtins/string/string_iterator.rs index 2028d23786d..3ced382c9bd 100644 --- a/boa/src/builtins/string/string_iterator.rs +++ b/boa/src/builtins/string/string_iterator.rs @@ -1,7 +1,9 @@ use crate::{ - builtins::{iterable::create_iter_result_object, string::code_point_at}, + builtins::{ + function::make_builtin_fn, iterable::create_iter_result_object, string::code_point_at, + }, gc::{Finalize, Trace}, - object::{function::make_builtin_fn, JsObject, ObjectData}, + object::{JsObject, ObjectData}, property::PropertyDescriptor, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, JsValue, @@ -22,13 +24,11 @@ impl StringIterator { } pub fn create_string_iterator(string: JsValue, context: &mut Context) -> JsResult { - let string_iterator = JsValue::new_object(context); - string_iterator.set_data(ObjectData::string_iterator(Self::new(string))); - string_iterator - .as_object() - .expect("array iterator object") - .set_prototype_instance(context.iterator_prototypes().string_iterator().into()); - Ok(string_iterator) + let string_iterator = JsObject::from_proto_and_data( + Some(context.iterator_prototypes().string_iterator()), + ObjectData::string_iterator(Self::new(string)), + ); + Ok(string_iterator.into()) } pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { @@ -76,13 +76,16 @@ impl StringIterator { /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object - pub(crate) fn create_prototype(iterator_prototype: JsValue, context: &mut Context) -> JsObject { + pub(crate) fn create_prototype( + iterator_prototype: JsObject, + context: &mut Context, + ) -> JsObject { let _timer = BoaProfiler::global().start_event("String Iterator", "init"); // Create prototype - let array_iterator = context.construct_object(); + let array_iterator = + JsObject::from_proto_and_data(Some(iterator_prototype), ObjectData::ordinary()); make_builtin_fn(Self::next, "next", &array_iterator, 0, context); - array_iterator.set_prototype_instance(iterator_prototype); let to_string_tag = WellKnownSymbols::to_string_tag(); let to_string_tag_property = PropertyDescriptor::builder() diff --git a/boa/src/bytecompiler.rs b/boa/src/bytecompiler.rs index 1978a2c5115..1e3dffb8c72 100644 --- a/boa/src/bytecompiler.rs +++ b/boa/src/bytecompiler.rs @@ -1,7 +1,7 @@ use gc::Gc; use crate::{ - object::function::ThisMode, + builtins::function::ThisMode, syntax::ast::{ node::{Declaration, GetConstField, GetField, StatementList}, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, diff --git a/boa/src/class.rs b/boa/src/class.rs index 1b510206143..4eb1551f562 100644 --- a/boa/src/class.rs +++ b/boa/src/class.rs @@ -62,10 +62,8 @@ //! [class-trait]: ./trait.Class.html use crate::{ - object::{ - function::NativeFunctionSignature, ConstructorBuilder, JsObject, NativeObject, ObjectData, - PROTOTYPE, - }, + builtins::function::NativeFunctionSignature, + object::{ConstructorBuilder, JsObject, NativeObject, ObjectData, PROTOTYPE}, property::{Attribute, PropertyDescriptor, PropertyKey}, Context, JsResult, JsValue, }; @@ -142,9 +140,10 @@ impl ClassConstructor for T { .unwrap_or(class_prototype); let native_instance = Self::constructor(this, args, context)?; - let object_instance = context.construct_object(); - object_instance.set_prototype_instance(prototype.into()); - object_instance.borrow_mut().data = ObjectData::native_object(Box::new(native_instance)); + let object_instance = JsObject::from_proto_and_data( + Some(prototype), + ObjectData::native_object(Box::new(native_instance)), + ); Ok(object_instance.into()) } } diff --git a/boa/src/context.rs b/boa/src/context.rs index 4cbc59c9207..e4be9c6adbc 100644 --- a/boa/src/context.rs +++ b/boa/src/context.rs @@ -1,13 +1,15 @@ //! Javascript context. use crate::{ - builtins::{self, iterable::IteratorPrototypes}, - class::{Class, ClassBuilder}, - exec::Interpreter, - object::{ + builtins::{ + self, function::{Function, NativeFunctionSignature, ThisMode}, - FunctionBuilder, JsObject, Object, PROTOTYPE, + iterable::IteratorPrototypes, }, + class::{Class, ClassBuilder}, + exec::Interpreter, + object::ObjectData, + object::{FunctionBuilder, JsObject, PROTOTYPE}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, syntax::{ @@ -39,18 +41,18 @@ pub struct StandardConstructor { impl Default for StandardConstructor { fn default() -> Self { Self { - constructor: JsObject::new(Object::default()), - prototype: JsObject::new(Object::default()), + constructor: JsObject::empty(), + prototype: JsObject::empty(), } } } impl StandardConstructor { /// Build a constructor with a defined prototype. - fn with_prototype(prototype: Object) -> Self { + fn with_prototype(prototype: JsObject) -> Self { Self { - constructor: JsObject::new(Object::default()), - prototype: JsObject::new(prototype), + constructor: JsObject::empty(), + prototype, } } @@ -101,9 +103,18 @@ impl Default for StandardObjects { function: StandardConstructor::default(), array: StandardConstructor::default(), bigint: StandardConstructor::default(), - number: StandardConstructor::with_prototype(Object::number(0.0)), - boolean: StandardConstructor::with_prototype(Object::boolean(false)), - string: StandardConstructor::with_prototype(Object::string("")), + number: StandardConstructor::with_prototype(JsObject::from_proto_and_data( + None, + ObjectData::number(0.0), + )), + boolean: StandardConstructor::with_prototype(JsObject::from_proto_and_data( + None, + ObjectData::boolean(false), + )), + string: StandardConstructor::with_prototype(JsObject::from_proto_and_data( + None, + ObjectData::string("".into()), + )), regexp: StandardConstructor::default(), symbol: StandardConstructor::default(), error: StandardConstructor::default(), @@ -378,11 +389,11 @@ impl Context { builtins::init(self); } - /// Construct an empty object. + /// Construct a `JsObject` with the `Object` prototype #[inline] pub fn construct_object(&self) -> JsObject { - let object_prototype: JsValue = self.standard_objects().object_object().prototype().into(); - JsObject::new(Object::create(object_prototype)) + let object_prototype = self.standard_objects().object_object().prototype(); + JsObject::from_proto_and_data(Some(object_prototype), ObjectData::ordinary()) } /// @@ -579,8 +590,7 @@ impl Context { P: Into>, { let name = name.into(); - let function_prototype: JsValue = - self.standard_objects().function_object().prototype().into(); + let function_prototype = self.standard_objects().function_object().prototype(); // Every new function has a prototype property pre-made let prototype = self.construct_object(); @@ -600,7 +610,8 @@ impl Context { environment: self.get_current_environment().clone(), }; - let function = JsObject::new(Object::function(func, function_prototype)); + let function = + JsObject::from_proto_and_data(Some(function_prototype), ObjectData::function(func)); // Set constructor field to the newly created Value (function object) let constructor = PropertyDescriptor::builder() diff --git a/boa/src/object/function.rs b/boa/src/object/function.rs deleted file mode 100644 index 104ef90af73..00000000000 --- a/boa/src/object/function.rs +++ /dev/null @@ -1,376 +0,0 @@ -//! This module implements the global `Function` object as well as creates Native Functions. -//! -//! Objects wrap `Function`s and expose them via call/construct slots. -//! -//! `The `Function` object is used for matching text with a pattern. -//! -//! More information: -//! - [ECMAScript reference][spec] -//! - [MDN documentation][mdn] -//! -//! [spec]: https://tc39.es/ecma262/#sec-function-objects -//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function - -use crate::{ - environment::lexical_environment::Environment, - gc::{Finalize, Trace}, - object::{JsObject, Object}, - property::PropertyDescriptor, - syntax::ast::node::{FormalParameter, RcStatementList}, - BoaProfiler, Context, JsResult, JsValue, -}; - -use dyn_clone::DynClone; -use std::{ - fmt, - ops::{Deref, DerefMut}, -}; - -use super::NativeObject; - -/// Type representing a native built-in function a.k.a. function pointer. -/// -/// Native functions need to have this signature in order to -/// be callable from Javascript. -pub type NativeFunctionSignature = fn(&JsValue, &[JsValue], &mut Context) -> JsResult; - -// Allows restricting closures to only `Copy` ones. -// Used the sealed pattern to disallow external implementations -// of `DynCopy`. -mod sealed { - pub trait Sealed {} - impl Sealed for T {} -} -pub trait DynCopy: sealed::Sealed {} -impl DynCopy for T {} - -/// Trait representing a native built-in closure. -/// -/// Closures need to have this signature in order to -/// be callable from Javascript, but most of the time the compiler -/// is smart enough to correctly infer the types. -pub trait ClosureFunctionSignature: - Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + DynCopy + DynClone + 'static -{ -} - -// The `Copy` bound automatically infers `DynCopy` and `DynClone` -impl ClosureFunctionSignature for T where - T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult + Copy + 'static -{ -} - -// Allows cloning Box -dyn_clone::clone_trait_object!(ClosureFunctionSignature); - -#[derive(Debug, Trace, Finalize, PartialEq, Clone)] -pub enum ThisMode { - Lexical, - Strict, - Global, -} - -impl ThisMode { - /// Returns `true` if the this mode is `Lexical`. - pub fn is_lexical(&self) -> bool { - matches!(self, Self::Lexical) - } - - /// Returns `true` if the this mode is `Strict`. - pub fn is_strict(&self) -> bool { - matches!(self, Self::Strict) - } - - /// Returns `true` if the this mode is `Global`. - pub fn is_global(&self) -> bool { - matches!(self, Self::Global) - } -} - -#[derive(Debug, Trace, Finalize, PartialEq, Clone)] -pub enum ConstructorKind { - Base, - Derived, -} - -impl ConstructorKind { - /// Returns `true` if the constructor kind is `Base`. - pub fn is_base(&self) -> bool { - matches!(self, Self::Base) - } - - /// Returns `true` if the constructor kind is `Derived`. - pub fn is_derived(&self) -> bool { - matches!(self, Self::Derived) - } -} -// We don't use a standalone `NativeObject` for `Captures` because it doesn't -// guarantee that the internal type implements `Clone`. -// This private trait guarantees that the internal type passed to `Captures` -// implements `Clone`, and `DynClone` allows us to implement `Clone` for -// `Box`. -trait CapturesObject: NativeObject + DynClone {} -impl CapturesObject for T {} -dyn_clone::clone_trait_object!(CapturesObject); - -/// Wrapper for `Box` that allows passing additional -/// captures through a `Copy` closure. -/// -/// Any type implementing `Trace + Any + Debug + Clone` -/// can be used as a capture context, so you can pass e.g. a String, -/// a tuple or even a full struct. -/// -/// You can downcast to any type and handle the fail case as you like -/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref` -/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast -/// fails. -#[derive(Debug, Clone, Trace, Finalize)] -pub struct Captures(Box); - -impl Captures { - /// Creates a new capture context. - pub(crate) fn new(captures: T) -> Self - where - T: NativeObject + Clone, - { - Self(Box::new(captures)) - } - - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or `None` otherwise. - pub fn downcast_ref(&self) -> Option<&T> - where - T: NativeObject + Clone, - { - self.0.deref().as_any().downcast_ref::() - } - - /// Mutably downcasts `Captures` to the specified type, returning a - /// mutable reference to the downcasted type if successful or `None` otherwise. - pub fn downcast_mut(&mut self) -> Option<&mut T> - where - T: NativeObject + Clone, - { - self.0.deref_mut().as_mut_any().downcast_mut::() - } - - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or a `TypeError` otherwise. - pub fn try_downcast_ref(&self, context: &mut Context) -> JsResult<&T> - where - T: NativeObject + Clone, - { - self.0 - .deref() - .as_any() - .downcast_ref::() - .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) - } - - /// Downcasts `Captures` to the specified type, returning a reference to the - /// downcasted type if successful or a `TypeError` otherwise. - pub fn try_downcast_mut(&mut self, context: &mut Context) -> JsResult<&mut T> - where - T: NativeObject + Clone, - { - self.0 - .deref_mut() - .as_mut_any() - .downcast_mut::() - .ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type")) - } -} - -/// Boa representation of a Function Object. -/// -/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node) -/// -/// -#[derive(Clone, Trace, Finalize)] -pub enum Function { - Native { - #[unsafe_ignore_trace] - function: NativeFunctionSignature, - constructable: bool, - }, - Closure { - #[unsafe_ignore_trace] - function: Box, - constructable: bool, - captures: Captures, - }, - Ordinary { - constructable: bool, - this_mode: ThisMode, - body: RcStatementList, - params: Box<[FormalParameter]>, - environment: Environment, - }, - #[cfg(feature = "vm")] - VmOrdinary { - code: gc::Gc, - environment: Environment, - }, -} - -impl fmt::Debug for Function { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Function {{ ... }}") - } -} - -impl Function { - // Adds the final rest parameters to the Environment as an array - #[cfg(not(feature = "vm"))] - pub(crate) fn add_rest_param( - param: &FormalParameter, - index: usize, - args_list: &[JsValue], - context: &mut Context, - local_env: &Environment, - ) { - use crate::builtins::Array; - // Create array of values - let array = Array::new_array(context); - Array::add_to_array_object(&array, args_list.get(index..).unwrap_or_default(), context) - .unwrap(); - - // Create binding - local_env - // Function parameters can share names in JavaScript... - .create_mutable_binding(param.name(), false, true, context) - .expect("Failed to create binding for rest param"); - - // Set Binding to value - local_env - .initialize_binding(param.name(), array, context) - .expect("Failed to initialize rest param"); - } - - // Adds an argument to the environment - pub(crate) fn add_arguments_to_environment( - param: &FormalParameter, - value: JsValue, - local_env: &Environment, - context: &mut Context, - ) { - // Create binding - local_env - .create_mutable_binding(param.name(), false, true, context) - .expect("Failed to create binding"); - - // Set Binding to value - local_env - .initialize_binding(param.name(), value, context) - .expect("Failed to intialize binding"); - } - - /// Returns true if the function object is constructable. - pub fn is_constructable(&self) -> bool { - match self { - Self::Native { constructable, .. } => *constructable, - Self::Closure { constructable, .. } => *constructable, - Self::Ordinary { constructable, .. } => *constructable, - #[cfg(feature = "vm")] - Self::VmOrdinary { code, .. } => code.constructable, - } - } -} - -/// Arguments. -/// -/// -pub fn create_unmapped_arguments_object( - arguments_list: &[JsValue], - context: &mut Context, -) -> JsResult { - let len = arguments_list.len(); - let obj = JsObject::new(Object::default()); - // Set length - let length = PropertyDescriptor::builder() - .value(len) - .writable(true) - .enumerable(false) - .configurable(true) - .build(); - // Define length as a property - crate::object::internal_methods::ordinary_define_own_property( - &obj, - "length".into(), - length, - context, - )?; - let mut index: usize = 0; - while index < len { - let val = arguments_list.get(index).expect("Could not get argument"); - let prop = PropertyDescriptor::builder() - .value(val.clone()) - .writable(true) - .enumerable(true) - .configurable(true); - - obj.insert(index, prop); - index += 1; - } - - Ok(JsValue::new(obj)) -} - -/// Creates a new member function of a `Object` or `prototype`. -/// -/// A function registered using this macro can then be called from Javascript using: -/// -/// parent.name() -/// -/// See the javascript 'Number.toString()' as an example. -/// -/// # Arguments -/// function: The function to register as a built in function. -/// name: The name of the function (how it will be called but without the ()). -/// parent: The object to register the function on, if the global object is used then the function is instead called as name() -/// without requiring the parent, see parseInt() as an example. -/// length: As described at , The value of the "length" property is an integer that -/// indicates the typical number of arguments expected by the function. However, the language permits the function to be invoked with -/// some other number of arguments. -/// -/// If no length is provided, the length will be set to 0. -// TODO: deprecate/remove this. -pub(crate) fn make_builtin_fn( - function: NativeFunctionSignature, - name: N, - parent: &JsObject, - length: usize, - interpreter: &Context, -) where - N: Into, -{ - let name = name.into(); - let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init"); - - let mut function = Object::function( - Function::Native { - function, - constructable: false, - }, - interpreter - .standard_objects() - .function_object() - .prototype() - .into(), - ); - let attribute = PropertyDescriptor::builder() - .writable(false) - .enumerable(false) - .configurable(true); - function.insert_property("length", attribute.clone().value(length)); - function.insert_property("name", attribute.value(name.as_str())); - - parent.clone().insert_property( - name, - PropertyDescriptor::builder() - .value(function) - .writable(true) - .enumerable(false) - .configurable(true), - ); -} diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index 9624a1709a9..f11c187b87a 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -20,19 +20,16 @@ use std::{ #[cfg(not(feature = "vm"))] use crate::{ + builtins::function::{ + create_unmapped_arguments_object, Captures, ClosureFunctionSignature, Function, + NativeFunctionSignature, + }, environment::{ environment_record_trait::EnvironmentRecordTrait, function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, lexical_environment::Environment, }, exec::InterpreterState, - object::{ - function::{ - create_unmapped_arguments_object, Captures, ClosureFunctionSignature, Function, - NativeFunctionSignature, - }, - PROTOTYPE, - }, syntax::ast::node::RcStatementList, Executable, }; @@ -63,12 +60,32 @@ enum FunctionBody { } impl JsObject { - /// Create a new `GcObject` from a `Object`. + /// Create a new `JsObject` from an internal `Object`. #[inline] - pub fn new(object: Object) -> Self { + fn from_object(object: Object) -> Self { Self(Gc::new(GcCell::new(object))) } + /// Create a new empty `JsObject`, with `prototype` set to `JsValue::Null` + /// and `data` set to `ObjectData::ordinary` + pub fn empty() -> Self { + Self::from_object(Object::default()) + } + + /// The more general form of `OrdinaryObjectCreate` and `MakeBasicObject`. + /// + /// Create a `JsObject` and automatically set its internal methods and + /// internal slots from the `data` provided. + #[inline] + pub fn from_proto_and_data(prototype: Option, data: ObjectData) -> Self { + Self::from_object(Object { + data, + prototype: prototype.map_or(JsValue::Null, JsValue::new), + extensible: true, + properties: Default::default(), + }) + } + /// Immutably borrows the `Object`. /// /// The borrow lasts until the returned `Ref` exits scope. @@ -143,6 +160,10 @@ impl JsObject { context: &mut Context, construct: bool, ) -> JsResult { + use crate::context::StandardObjects; + + use super::internal_methods::get_prototype_from_constructor; + let this_function_object = self.clone(); let mut has_parameter_expressions = false; @@ -180,21 +201,13 @@ impl JsObject { // prototype as prototype for the new object // see // see - let proto = this_target.as_object().unwrap().__get__( - &PROTOTYPE.into(), - this_target.clone(), + let proto = get_prototype_from_constructor( + this_target, + StandardObjects::object_object, context, )?; - let proto = if proto.is_object() { - proto - } else { - context - .standard_objects() - .object_object() - .prototype() - .into() - }; - JsValue::new(Object::create(proto)) + JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary()) + .into() } else { this_target.clone() }; diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 9f751675bd8..8a1c841e3e7 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -2,16 +2,20 @@ use crate::{ builtins::{ - array::array_iterator::ArrayIterator, map::map_iterator::MapIterator, - map::ordered_map::OrderedMap, regexp::regexp_string_iterator::RegExpStringIterator, - set::ordered_set::OrderedSet, set::set_iterator::SetIterator, - string::string_iterator::StringIterator, Date, RegExp, + array::array_iterator::ArrayIterator, + function::{Captures, Function, NativeFunctionSignature}, + map::map_iterator::MapIterator, + map::ordered_map::OrderedMap, + regexp::regexp_string_iterator::RegExpStringIterator, + set::ordered_set::OrderedSet, + set::set_iterator::SetIterator, + string::string_iterator::StringIterator, + Date, RegExp, }, context::StandardConstructor, gc::{Finalize, Trace}, - object::function::{Captures, Function, NativeFunctionSignature}, property::{Attribute, PropertyDescriptor, PropertyKey}, - BoaProfiler, Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, + Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, }; use std::{ any::Any, @@ -22,7 +26,6 @@ use std::{ #[cfg(test)] mod tests; -pub mod function; mod gcobject; pub(crate) mod internal_methods; mod operations; @@ -335,99 +338,6 @@ impl Default for Object { } impl Object { - #[inline] - pub fn new() -> Self { - Default::default() - } - - /// Return a new ObjectData struct, with `kind` set to Ordinary - #[inline] - pub fn function(function: Function, prototype: JsValue) -> Self { - let _timer = BoaProfiler::global().start_event("Object::Function", "object"); - - Self { - data: ObjectData::function(function), - properties: PropertyMap::default(), - prototype, - extensible: true, - } - } - - /// ObjectCreate is used to specify the runtime creation of new ordinary objects. - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-objectcreate - // TODO: proto should be a &Value here - #[inline] - pub fn create(proto: JsValue) -> Self { - let mut obj = Self::new(); - obj.prototype = proto; - obj - } - - /// Return a new Boolean object whose `[[BooleanData]]` internal slot is set to argument. - #[inline] - pub fn boolean(value: bool) -> Self { - Self { - data: ObjectData::boolean(value), - properties: PropertyMap::default(), - prototype: JsValue::null(), - extensible: true, - } - } - - /// Return a new `Number` object whose `[[NumberData]]` internal slot is set to argument. - #[inline] - pub fn number(value: f64) -> Self { - Self { - data: ObjectData::number(value), - properties: PropertyMap::default(), - prototype: JsValue::null(), - extensible: true, - } - } - - /// Return a new `String` object whose `[[StringData]]` internal slot is set to argument. - #[inline] - pub fn string(value: S) -> Self - where - S: Into, - { - Self { - data: ObjectData::string(value.into()), - properties: PropertyMap::default(), - prototype: JsValue::null(), - extensible: true, - } - } - - /// Return a new `BigInt` object whose `[[BigIntData]]` internal slot is set to argument. - #[inline] - pub fn bigint(value: JsBigInt) -> Self { - Self { - data: ObjectData::big_int(value), - properties: PropertyMap::default(), - prototype: JsValue::null(), - extensible: true, - } - } - - /// Create a new native object of type `T`. - #[inline] - pub fn native_object(value: T) -> Self - where - T: NativeObject, - { - Self { - data: ObjectData::native_object(Box::new(value)), - properties: PropertyMap::default(), - prototype: JsValue::null(), - extensible: true, - } - } - #[inline] pub fn kind(&self) -> &ObjectKind { &self.data.kind @@ -918,15 +828,6 @@ impl Object { } } - /// Similar to `Value::new_object`, but you can pass a prototype to create from, plus a kind - #[inline] - pub fn with_prototype(proto: JsValue, data: ObjectData) -> Object { - let mut object = Object::new(); - object.data = data; - object.set_prototype_instance(proto); - object - } - /// Returns `true` if it holds an Rust type that implements `NativeObject`. #[inline] pub fn is_native_object(&self) -> bool { @@ -1211,13 +1112,14 @@ impl<'context> FunctionBuilder<'context> { /// Build the function object. #[inline] pub fn build(&mut self) -> JsObject { - let mut function = Object::function( - self.function.take().unwrap(), - self.context - .standard_objects() - .function_object() - .prototype() - .into(), + let function = JsObject::from_proto_and_data( + Some( + self.context + .standard_objects() + .function_object() + .prototype(), + ), + ObjectData::function(self.function.take().unwrap()), ); let property = PropertyDescriptor::builder() .writable(false) @@ -1226,7 +1128,7 @@ impl<'context> FunctionBuilder<'context> { function.insert_property("name", property.clone().value(self.name.clone())); function.insert_property("length", property.value(self.length)); - JsObject::new(function) + function } /// Initializes the `Function.prototype` function object. @@ -1380,8 +1282,8 @@ impl<'context> ConstructorBuilder<'context> { Self { context, constructor_function: constructor, - constructor_object: JsObject::new(Object::default()), - prototype: JsObject::new(Object::default()), + constructor_object: JsObject::empty(), + prototype: JsObject::empty(), length: 0, name: JsString::default(), callable: true, diff --git a/boa/src/realm.rs b/boa/src/realm.rs index aa997b66ab7..303a93364d7 100644 --- a/boa/src/realm.rs +++ b/boa/src/realm.rs @@ -8,7 +8,7 @@ use crate::{ environment::{ global_environment_record::GlobalEnvironmentRecord, lexical_environment::LexicalEnvironment, }, - object::{JsObject, Object, ObjectData}, + object::{JsObject, ObjectData}, BoaProfiler, }; use gc::Gc; @@ -29,12 +29,8 @@ impl Realm { let _timer = BoaProfiler::global().start_event("Realm::create", "realm"); // Create brand new global object // Global has no prototype to pass None to new_obj - let mut global = Object::default(); - // Allow identification of the global object easily - global.data = ObjectData::global(); - - let gc_global = JsObject::new(global); + let gc_global = JsObject::from_proto_and_data(None, ObjectData::global()); // We need to clone the global here because its referenced from separate places (only pointer is cloned) let global_env = GlobalEnvironmentRecord::new(gc_global.clone(), gc_global.clone()); diff --git a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs index 2cebe53c4e3..11fcccca2c3 100644 --- a/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs @@ -1,7 +1,7 @@ use crate::{ + builtins::function::ThisMode, exec::Executable, gc::{Finalize, Trace}, - object::function::ThisMode, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, Context, JsResult, JsValue, }; diff --git a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs index 25731595a73..f5198f55d7b 100644 --- a/boa/src/syntax/ast/node/declaration/function_decl/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_decl/mod.rs @@ -1,8 +1,8 @@ use crate::{ + builtins::function::ThisMode, environment::lexical_environment::VariableScope, exec::Executable, gc::{Finalize, Trace}, - object::function::ThisMode, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, BoaProfiler, Context, JsResult, JsValue, }; diff --git a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs index b859997c18e..e93a06f1668 100644 --- a/boa/src/syntax/ast/node/declaration/function_expr/mod.rs +++ b/boa/src/syntax/ast/node/declaration/function_expr/mod.rs @@ -1,7 +1,7 @@ use crate::{ + builtins::function::ThisMode, exec::Executable, gc::{Finalize, Trace}, - object::function::ThisMode, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, Context, JsResult, JsValue, }; diff --git a/boa/src/syntax/ast/node/object/mod.rs b/boa/src/syntax/ast/node/object/mod.rs index b47e6ad078d..1205d0fbb3b 100644 --- a/boa/src/syntax/ast/node/object/mod.rs +++ b/boa/src/syntax/ast/node/object/mod.rs @@ -89,7 +89,7 @@ impl Object { impl Executable for Object { fn run(&self, context: &mut Context) -> JsResult { let _timer = BoaProfiler::global().start_event("object", "exec"); - let obj = JsValue::new_object(context); + let mut obj = context.construct_object(); // TODO: Implement the rest of the property types. for property in self.properties().iter() { @@ -101,14 +101,16 @@ impl Executable for Object { node.run(context)?.to_property_key(context)? } }; - obj.set_property( + obj.__define_own_property__( name, PropertyDescriptor::builder() .value(value.run(context)?) .writable(true) .enumerable(true) - .configurable(true), - ); + .configurable(true) + .build(), + context, + )?; } PropertyDefinition::MethodDefinition(kind, name, func) => { let name = match name { @@ -119,44 +121,50 @@ impl Executable for Object { }; match kind { MethodDefinitionKind::Ordinary => { - obj.set_property( + obj.__define_own_property__( name, PropertyDescriptor::builder() .value(func.run(context)?) .writable(true) .enumerable(true) - .configurable(true), - ); + .configurable(true) + .build(), + context, + )?; } MethodDefinitionKind::Get => { let set = obj - .get_property(name.clone()) + .__get_own_property__(&name, context)? .as_ref() .and_then(|a| a.set()) .cloned(); - obj.set_property( + obj.__define_own_property__( name, PropertyDescriptor::builder() .maybe_get(func.run(context)?.as_object()) .maybe_set(set) .enumerable(true) - .configurable(true), - ) + .configurable(true) + .build(), + context, + )?; } MethodDefinitionKind::Set => { let get = obj - .get_property(name.clone()) + .__get_own_property__(&name, context)? .as_ref() - .and_then(|a| a.get()) + .and_then(|a| a.set()) .cloned(); - obj.set_property( + obj.__define_own_property__( name, PropertyDescriptor::builder() .maybe_get(get) .maybe_set(func.run(context)?.as_object()) .enumerable(true) - .configurable(true), - ) + .configurable(true) + .build(), + context, + )?; } } } @@ -168,17 +176,13 @@ impl Executable for Object { continue; } - obj.as_object().unwrap().copy_data_properties::( - &val, - vec![], - context, - )?; + obj.copy_data_properties::(&val, vec![], context)?; } _ => {} // unimplemented!("{:?} type of property", i), } } - Ok(obj) + Ok(obj.into()) } } diff --git a/boa/src/value/conversions.rs b/boa/src/value/conversions.rs index be1c97e44e2..b0d1417fce4 100644 --- a/boa/src/value/conversions.rs +++ b/boa/src/value/conversions.rs @@ -120,54 +120,6 @@ impl From for JsValue { } } -impl From<&[T]> for JsValue -where - T: Clone + Into, -{ - fn from(value: &[T]) -> Self { - let mut array = Object::default(); - for (i, item) in value.iter().enumerate() { - array.insert( - i, - PropertyDescriptor::builder() - .value(item.clone()) - .writable(true) - .enumerable(true) - .configurable(true), - ); - } - Self::from(array) - } -} - -impl From> for JsValue -where - T: Into, -{ - fn from(value: Vec) -> Self { - let mut array = Object::default(); - for (i, item) in value.into_iter().enumerate() { - array.insert( - i, - PropertyDescriptor::builder() - .value(item) - .writable(true) - .enumerable(true) - .configurable(true), - ); - } - JsValue::new(array) - } -} - -impl From for JsValue { - #[inline] - fn from(object: Object) -> Self { - let _timer = BoaProfiler::global().start_event("From", "value"); - JsValue::Object(JsObject::new(object)) - } -} - impl From for JsValue { #[inline] fn from(object: JsObject) -> Self { diff --git a/boa/src/value/mod.rs b/boa/src/value/mod.rs index 5e7ab67ec0c..8a4743a1c92 100644 --- a/boa/src/value/mod.rs +++ b/boa/src/value/mod.rs @@ -10,7 +10,7 @@ use crate::{ number::{f64_to_int32, f64_to_uint32}, Number, }, - object::{JsObject, Object, ObjectData}, + object::{JsObject, ObjectData}, property::{PropertyDescriptor, PropertyKey}, symbol::{JsSymbol, WellKnownSymbols}, BoaProfiler, Context, JsBigInt, JsResult, JsString, @@ -98,22 +98,16 @@ impl JsValue { /// Creates a new number with `Infinity` value. #[inline] - pub fn positive_inifnity() -> Self { + pub fn positive_infinity() -> Self { Self::Rational(f64::INFINITY) } /// Creates a new number with `-Infinity` value. #[inline] - pub fn negative_inifnity() -> Self { + pub fn negative_infinity() -> Self { Self::Rational(f64::NEG_INFINITY) } - /// Returns a new empty object - pub(crate) fn new_object(context: &Context) -> Self { - let _timer = BoaProfiler::global().start_event("new_object", "value"); - context.construct_object().into() - } - /// Returns true if the value is an object #[inline] pub fn is_object(&self) -> bool { @@ -502,32 +496,32 @@ impl JsValue { } JsValue::Boolean(boolean) => { let prototype = context.standard_objects().boolean_object().prototype(); - Ok(JsObject::new(Object::with_prototype( - prototype.into(), + Ok(JsObject::from_proto_and_data( + Some(prototype), ObjectData::boolean(*boolean), - ))) + )) } JsValue::Integer(integer) => { let prototype = context.standard_objects().number_object().prototype(); - Ok(JsObject::new(Object::with_prototype( - prototype.into(), + Ok(JsObject::from_proto_and_data( + Some(prototype), ObjectData::number(f64::from(*integer)), - ))) + )) } JsValue::Rational(rational) => { let prototype = context.standard_objects().number_object().prototype(); - Ok(JsObject::new(Object::with_prototype( - prototype.into(), + Ok(JsObject::from_proto_and_data( + Some(prototype), ObjectData::number(*rational), - ))) + )) } JsValue::String(ref string) => { let prototype = context.standard_objects().string_object().prototype(); - let object = JsObject::new(Object::with_prototype( - prototype.into(), + let object = JsObject::from_proto_and_data( + Some(prototype), ObjectData::string(string.clone()), - )); + ); // Make sure the correct length is set on our new string object object.insert_property( "length", @@ -541,17 +535,17 @@ impl JsValue { } JsValue::Symbol(ref symbol) => { let prototype = context.standard_objects().symbol_object().prototype(); - Ok(JsObject::new(Object::with_prototype( - prototype.into(), + Ok(JsObject::from_proto_and_data( + Some(prototype), ObjectData::symbol(symbol.clone()), - ))) + )) } JsValue::BigInt(ref bigint) => { let prototype = context.standard_objects().bigint_object().prototype(); - Ok(JsObject::new(Object::with_prototype( - prototype.into(), + Ok(JsObject::from_proto_and_data( + Some(prototype), ObjectData::big_int(bigint.clone()), - ))) + )) } JsValue::Object(jsobject) => Ok(jsobject.clone()), } diff --git a/boa/src/value/tests.rs b/boa/src/value/tests.rs index c0ac943f6c2..7fa2a991f8f 100644 --- a/boa/src/value/tests.rs +++ b/boa/src/value/tests.rs @@ -6,13 +6,6 @@ use crate::{check_output, forward, forward_val, Context, TestAction}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; -#[test] -fn is_object() { - let context = Context::new(); - let val = JsValue::new_object(&context); - assert!(val.is_object()); -} - #[test] fn string_to_value() { let s = String::from("Hello"); @@ -31,15 +24,12 @@ fn undefined() { #[test] fn get_set_field() { let mut context = Context::new(); - let obj = JsValue::new_object(&context); + let obj = &context.construct_object(); // Create string and convert it to a Value let s = JsValue::new("bar"); - obj.set_field("foo", s, false, &mut context).unwrap(); + obj.set("foo", s, false, &mut context).unwrap(); assert_eq!( - obj.get_field("foo", &mut context) - .unwrap() - .display() - .to_string(), + obj.get("foo", &mut context).unwrap().display().to_string(), "\"bar\"" ); } @@ -142,11 +132,11 @@ fn hash_rational() { #[test] #[allow(clippy::eq_op)] fn hash_object() { - let object1 = JsValue::new(Object::default()); + let object1 = JsValue::new(JsObject::empty()); assert_eq!(object1, object1); assert_eq!(object1, object1.clone()); - let object2 = JsValue::new(Object::default()); + let object2 = JsValue::new(JsObject::empty()); assert_ne!(object1, object2); assert_eq!(hash_value(&object1), hash_value(&object1.clone())); diff --git a/boa/src/vm/code_block.rs b/boa/src/vm/code_block.rs index a47ff8eb709..427d2f60649 100644 --- a/boa/src/vm/code_block.rs +++ b/boa/src/vm/code_block.rs @@ -1,14 +1,15 @@ use crate::{ + builtins::function::{ + Captures, ClosureFunctionSignature, Function, NativeFunctionSignature, ThisMode, + }, + context::StandardObjects, environment::{ function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, lexical_environment::Environment, }, gc::{Finalize, Trace}, object::{ - function::{ - Captures, ClosureFunctionSignature, Function, NativeFunctionSignature, ThisMode, - }, - JsObject, Object, PROTOTYPE, + internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, PROTOTYPE, }, property::PropertyDescriptor, syntax::ast::node::FormalParameter, @@ -311,7 +312,8 @@ impl JsVmFunction { let function = Function::VmOrdinary { code, environment }; - let constructor = JsObject::new(Object::function(function, function_prototype.into())); + let constructor = + JsObject::from_proto_and_data(Some(function_prototype), ObjectData::function(function)); let constructor_property = PropertyDescriptor::builder() .value(constructor.clone()) @@ -515,26 +517,17 @@ impl JsObject { (function)(this_target, args, captures, context) } FunctionBody::Ordinary { code, environment } => { - let this = { + let this: JsValue = { // If the prototype of the constructor is not an object, then use the default object // prototype as prototype for the new object // see // see - let proto = this_target.as_object().unwrap().__get__( - &PROTOTYPE.into(), - this_target.clone(), + let proto = get_prototype_from_constructor( + this_target, + StandardObjects::object_object, context, )?; - let proto = if proto.is_object() { - proto - } else { - context - .standard_objects() - .object_object() - .prototype() - .into() - }; - JsValue::from(Object::create(proto)) + JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary()).into() }; let lexical_this_mode = code.this_mode == ThisMode::Lexical; diff --git a/boa/src/vm/mod.rs b/boa/src/vm/mod.rs index 4251b28b261..599450dd2c7 100644 --- a/boa/src/vm/mod.rs +++ b/boa/src/vm/mod.rs @@ -155,22 +155,21 @@ impl Context { self.vm.push(value); } Opcode::PushNaN => self.vm.push(JsValue::nan()), - Opcode::PushPositiveInfinity => self.vm.push(JsValue::positive_inifnity()), - Opcode::PushNegativeInfinity => self.vm.push(JsValue::negative_inifnity()), + Opcode::PushPositiveInfinity => self.vm.push(JsValue::positive_infinity()), + Opcode::PushNegativeInfinity => self.vm.push(JsValue::negative_infinity()), Opcode::PushLiteral => { let index = self.vm.read::() as usize; let value = self.vm.frame().code.literals[index].clone(); self.vm.push(value) } - Opcode::PushEmptyObject => self.vm.push(JsValue::new_object(self)), + Opcode::PushEmptyObject => self.vm.push(self.construct_object()), Opcode::PushNewArray => { let count = self.vm.read::(); let mut elements = Vec::with_capacity(count as usize); for _ in 0..count { elements.push(self.vm.pop()); } - let array = Array::new_array(self); - Array::add_to_array_object(&array, &elements, self)?; + let array = Array::create_array_from_list(elements, self); self.vm.push(array); } Opcode::Add => bin_op!(add),