diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 63936e3171..32b734ff07 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -17,7 +17,7 @@ use crate::{ builtins::array::array_iterator::{ArrayIterationKind, ArrayIterator}, builtins::BuiltIn, gc::GcObject, - object::{ConstructorBuilder, FunctionBuilder, ObjectData}, + object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE}, property::{Attribute, DataDescriptor}, value::{same_value_zero, IntegerOrInfinity, Value}, BoaProfiler, Context, Result, @@ -108,12 +108,21 @@ impl BuiltIn for Array { impl Array { const LENGTH: usize = 1; - fn constructor(this: &Value, args: &[Value], context: &mut Context) -> Result { + fn constructor(new_target: &Value, args: &[Value], context: &mut Context) -> Result { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().array_object().prototype()); // Delegate to the appropriate constructor based on the number of arguments match args.len() { - 0 => Array::construct_array_empty(this, context), - 1 => Array::construct_array_length(this, &args[0], context), - _ => Array::construct_array_values(this, args, context), + 0 => Array::construct_array_empty(prototype, context), + 1 => Array::construct_array_length(prototype, &args[0], context), + _ => Array::construct_array_values(prototype, args, context), } } @@ -123,10 +132,8 @@ impl Array { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-array-constructor-array - fn construct_array_empty(this: &Value, context: &mut Context) -> Result { - let prototype = context.standard_objects().array_object().prototype(); - - Array::array_create(this, 0, Some(prototype), context) + fn construct_array_empty(proto: GcObject, context: &mut Context) -> Result { + Array::array_create(0, Some(proto), context) } /// By length constructor for `Array`. @@ -136,12 +143,11 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array-len fn construct_array_length( - this: &Value, + prototype: GcObject, length: &Value, context: &mut Context, ) -> Result { - let prototype = context.standard_objects().array_object().prototype(); - let array = Array::array_create(this, 0, Some(prototype), context)?; + let array = Array::array_create(0, Some(prototype), context)?; if !length.is_number() { array.set_property(0, DataDescriptor::new(length, Attribute::all())); @@ -163,13 +169,12 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-array-items fn construct_array_values( - this: &Value, + prototype: GcObject, items: &[Value], context: &mut Context, ) -> Result { - let prototype = context.standard_objects().array_object().prototype(); let items_len = items.len().try_into().map_err(interror_to_value)?; - let array = Array::array_create(this, items_len, Some(prototype), context)?; + let array = Array::array_create(items_len, Some(prototype), context)?; for (k, item) in items.iter().enumerate() { array.set_property(k, DataDescriptor::new(item.clone(), Attribute::all())); @@ -185,7 +190,6 @@ impl Array { /// /// [spec]: https://tc39.es/ecma262/#sec-arraycreate fn array_create( - this: &Value, length: u32, prototype: Option, context: &mut Context, @@ -194,21 +198,23 @@ impl Array { Some(prototype) => prototype, None => context.standard_objects().array_object().prototype(), }; + let array = Value::new_object(context); - this.as_object() - .expect("this should be an array object") + array + .as_object() + .expect("'array' should be an object") .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) - this.set_data(ObjectData::Array); + array.set_data(ObjectData::Array); let length = DataDescriptor::new( length, Attribute::WRITABLE | Attribute::NON_ENUMERABLE | Attribute::PERMANENT, ); - this.set_property("length", length); + array.set_property("length", length); - Ok(this.clone()) + Ok(array) } /// Creates a new `Array` instance. @@ -217,7 +223,7 @@ impl Array { array.set_data(ObjectData::Array); array .as_object() - .expect("array object") + .expect("'array' should be an object") .set_prototype_instance(context.standard_objects().array_object().prototype().into()); let length = DataDescriptor::new( Value::from(0), diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 43c959d304..4c8f1b67c6 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -14,7 +14,7 @@ mod tests; use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData}, + object::{ConstructorBuilder, ObjectData, PROTOTYPE}, property::Attribute, BoaProfiler, Context, Result, Value, }; @@ -56,12 +56,34 @@ impl Boolean { /// `[[Construct]]` Create a new boolean object /// /// `[[Call]]` Creates a new boolean primitive - pub(crate) fn constructor(this: &Value, args: &[Value], _: &mut Context) -> Result { + pub(crate) fn constructor( + new_target: &Value, + args: &[Value], + context: &mut Context, + ) -> Result { // Get the argument, if any let data = args.get(0).map(|x| x.to_boolean()).unwrap_or(false); - this.set_data(ObjectData::Boolean(data)); + if new_target.is_undefined() { + return Ok(Value::from(data)); + } + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().object_object().prototype()); + let boolean = Value::new_object(context); + + boolean + .as_object() + .expect("this should be an object") + .set_prototype_instance(prototype.into()); + boolean.set_data(ObjectData::Boolean(data)); - Ok(Value::from(data)) + Ok(boolean) } /// 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 46a437ea52..f19a2791ec 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -4,7 +4,7 @@ mod tests; use crate::{ builtins::BuiltIn, gc::{empty_trace, Finalize, Trace}, - object::{ConstructorBuilder, ObjectData}, + object::{ConstructorBuilder, ObjectData, PROTOTYPE}, property::Attribute, value::{PreferredType, Value}, BoaProfiler, Context, Result, @@ -324,18 +324,32 @@ 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 constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { - if this.is_global() { + if new_target.is_undefined() { Self::make_date_string() - } else if args.is_empty() { - Self::make_date_now(this) - } else if args.len() == 1 { - Self::make_date_single(this, args, context) } else { - Self::make_date_multiple(this, args, context) + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().object_object().prototype()); + let mut obj = context.construct_object(); + obj.set_prototype_instance(prototype.into()); + let this = obj.into(); + if args.is_empty() { + Self::make_date_now(&this) + } else if args.len() == 1 { + Self::make_date_single(&this, args, context) + } else { + Self::make_date_multiple(&this, args, context) + } } } diff --git a/boa/src/builtins/error/eval.rs b/boa/src/builtins/error/eval.rs index e1ca438670..0e1ccad1a3 100644 --- a/boa/src/builtins/error/eval.rs +++ b/boa/src/builtins/error/eval.rs @@ -11,6 +11,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-native-error-types-used-in-this-standard-evalerror //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/EvalError +use crate::object::PROTOTYPE; use crate::{ builtins::BuiltIn, object::{ConstructorBuilder, ObjectData}, @@ -57,17 +58,31 @@ impl EvalError { /// Create a new error object. pub(crate) fn constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().error_object().prototype()); + let mut obj = context.construct_object(); + obj.set_prototype_instance(prototype.into()); + let this = Value::from(obj); if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?, context)?; + if !message.is_undefined() { + this.set_field("message", message.to_string(context)?, 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.clone()) + Ok(this) } } diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index 505d51c9d0..60622cfd1b 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -12,7 +12,7 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData}, + object::{ConstructorBuilder, ObjectData, PROTOTYPE}, profiler::BoaProfiler, property::Attribute, Context, Result, Value, @@ -74,18 +74,32 @@ impl Error { /// /// Create a new error object. pub(crate) fn constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().error_object().prototype()); + let mut obj = context.construct_object(); + obj.set_prototype_instance(prototype.into()); + let this = Value::from(obj); if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?, context)?; + if !message.is_undefined() { + this.set_field("message", message.to_string(context)?, 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.clone()) + Ok(this) } /// `Error.prototype.toString()` diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 0a9211cddf..ed02089765 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -11,13 +11,13 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData}, + object::{ConstructorBuilder, ObjectData, PROTOTYPE}, profiler::BoaProfiler, property::Attribute, Context, Result, Value, }; -/// JavaScript `RangeError` impleentation. +/// JavaScript `RangeError` implementation. #[derive(Debug, Clone, Copy)] pub(crate) struct RangeError; @@ -55,17 +55,31 @@ impl RangeError { /// Create a new error object. pub(crate) fn constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().error_object().prototype()); + let mut obj = context.construct_object(); + obj.set_prototype_instance(prototype.into()); + let this = Value::from(obj); if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?, context)?; + if !message.is_undefined() { + this.set_field("message", message.to_string(context)?, 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.clone()) + Ok(this) } } diff --git a/boa/src/builtins/error/reference.rs b/boa/src/builtins/error/reference.rs index c606d71e69..a1e7153ffb 100644 --- a/boa/src/builtins/error/reference.rs +++ b/boa/src/builtins/error/reference.rs @@ -11,7 +11,7 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData}, + object::{ConstructorBuilder, ObjectData, PROTOTYPE}, profiler::BoaProfiler, property::Attribute, Context, Result, Value, @@ -54,17 +54,31 @@ impl ReferenceError { /// Create a new error object. pub(crate) fn constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().error_object().prototype()); + let mut obj = context.construct_object(); + obj.set_prototype_instance(prototype.into()); + let this = Value::from(obj); if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?, context)?; + if !message.is_undefined() { + this.set_field("message", message.to_string(context)?, 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.clone()) + Ok(this) } } diff --git a/boa/src/builtins/error/syntax.rs b/boa/src/builtins/error/syntax.rs index b61f9fde3a..b5764d219a 100644 --- a/boa/src/builtins/error/syntax.rs +++ b/boa/src/builtins/error/syntax.rs @@ -13,7 +13,7 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData}, + object::{ConstructorBuilder, ObjectData, PROTOTYPE}, profiler::BoaProfiler, property::Attribute, Context, Result, Value, @@ -57,17 +57,31 @@ impl SyntaxError { /// Create a new error object. pub(crate) fn constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().error_object().prototype()); + let mut obj = context.construct_object(); + obj.set_prototype_instance(prototype.into()); + let this = Value::from(obj); if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?, context)?; + if !message.is_undefined() { + this.set_field("message", message.to_string(context)?, 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.clone()) + Ok(this) } } diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index 497dc89154..c348d36aeb 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -17,7 +17,7 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData}, + object::{ConstructorBuilder, ObjectData, PROTOTYPE}, property::Attribute, BoaProfiler, Context, Result, Value, }; @@ -60,17 +60,31 @@ impl TypeError { /// Create a new error object. pub(crate) fn constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().error_object().prototype()); + let mut obj = context.construct_object(); + obj.set_prototype_instance(prototype.into()); + let this = Value::from(obj); if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?, context)?; + if !message.is_undefined() { + this.set_field("message", message.to_string(context)?, 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.clone()) + Ok(this) } } diff --git a/boa/src/builtins/error/uri.rs b/boa/src/builtins/error/uri.rs index 2d8a944b11..928627c953 100644 --- a/boa/src/builtins/error/uri.rs +++ b/boa/src/builtins/error/uri.rs @@ -12,7 +12,7 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData}, + object::{ConstructorBuilder, ObjectData, PROTOTYPE}, profiler::BoaProfiler, property::Attribute, Context, Result, Value, @@ -56,17 +56,31 @@ impl UriError { /// Create a new error object. pub(crate) fn constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().error_object().prototype()); + let mut obj = context.construct_object(); + obj.set_prototype_instance(prototype.into()); + let this = Value::from(obj); if let Some(message) = args.get(0) { - this.set_field("message", message.to_string(context)?, context)?; + if !message.is_undefined() { + this.set_field("message", message.to_string(context)?, 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.clone()) + Ok(this) } } diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 59be8e2b38..ae46d8068d 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -11,6 +11,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-function-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function +use crate::object::PROTOTYPE; use crate::{ builtins::{Array, BuiltIn}, environment::lexical_environment::Environment, @@ -255,12 +256,27 @@ pub struct BuiltInFunctionObject; impl BuiltInFunctionObject { pub const LENGTH: usize = 1; - fn constructor(this: &Value, _: &[Value], _: &mut Context) -> Result { + fn constructor(new_target: &Value, _: &[Value], context: &mut Context) -> Result { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().object_object().prototype()); + let this = Value::new_object(context); + + this.as_object() + .expect("this should be an object") + .set_prototype_instance(prototype.into()); + this.set_data(ObjectData::Function(Function::BuiltIn( BuiltInFunction(|_, _, _| Ok(Value::undefined())), FunctionFlags::CALLABLE | FunctionFlags::CONSTRUCTABLE, ))); - Ok(this.clone()) + Ok(this) } fn prototype(_: &Value, _: &[Value], _: &mut Context) -> Result { diff --git a/boa/src/builtins/map/mod.rs b/boa/src/builtins/map/mod.rs index b572e4384b..7a48f49368 100644 --- a/boa/src/builtins/map/mod.rs +++ b/boa/src/builtins/map/mod.rs @@ -70,22 +70,33 @@ impl Map { /// Create a new map pub(crate) fn constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { - // Set Prototype - let prototype = context + if new_target.is_undefined() { + return context.throw_type_error("Map requires new"); + } + let map_prototype = context .global_object() .clone() .get_field("Map", context)? - .get_field(PROTOTYPE, context)?; - - this.as_object() - .expect("this is map object") - .set_prototype_instance(prototype); - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) + .get_field(PROTOTYPE, context)? + .as_object() + .expect("'Map' global property should be an object"); + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or(map_prototype); + + let mut obj = context.construct_object(); + obj.set_prototype_instance(prototype.into()); + let this = Value::from(obj); // add our arguments in let data = match args.len() { @@ -122,12 +133,14 @@ impl Map { }, }; - // finally create length property - Self::set_size(this, data.len()); + // finally create size property + Self::set_size(&this, data.len()); + // 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::Map(data)); - Ok(this.clone()) + Ok(this) } /// `Map.prototype.entries()` diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index 0f10c52b3c..3bae384c67 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -16,7 +16,7 @@ use super::function::make_builtin_fn; use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData}, + object::{ConstructorBuilder, ObjectData, PROTOTYPE}, property::Attribute, value::{AbstractRelation, IntegerOrInfinity, Value}, BoaProfiler, Context, Result, @@ -154,7 +154,7 @@ impl Number { /// `Number( value )` pub(crate) fn constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { @@ -162,9 +162,25 @@ impl Number { Some(ref value) => value.to_numeric_number(context)?, None => 0.0, }; + if new_target.is_undefined() { + return Ok(Value::from(data)); + } + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().object_object().prototype()); + let this = Value::new_object(context); + this.as_object() + .expect("this should be an object") + .set_prototype_instance(prototype.into()); this.set_data(ObjectData::Number(data)); - Ok(Value::from(data)) + Ok(this) } /// This function returns a `Result` of the number `Value`. diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 9e31021127..fd6f423914 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -15,7 +15,9 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, Object as BuiltinObject, ObjectData, ObjectInitializer}, + object::{ + ConstructorBuilder, Object as BuiltinObject, ObjectData, ObjectInitializer, PROTOTYPE, + }, property::Attribute, property::DataDescriptor, property::PropertyDescriptor, @@ -78,7 +80,25 @@ impl BuiltIn for Object { impl Object { const LENGTH: usize = 1; - fn constructor(_: &Value, args: &[Value], context: &mut Context) -> Result { + fn constructor(new_target: &Value, args: &[Value], context: &mut Context) -> Result { + if !new_target.is_undefined() { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().object_object().prototype()); + let object = Value::new_object(context); + + object + .as_object() + .expect("this should be an object") + .set_prototype_instance(prototype.into()); + return Ok(object); + } if let Some(arg) = args.get(0) { if !arg.is_null_or_undefined() { return Ok(arg.to_object(context)?.into()); diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 439b0733ac..0b40e238e2 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -12,7 +12,7 @@ use crate::{ builtins::BuiltIn, gc::{empty_trace, Finalize, Trace}, - object::{ConstructorBuilder, ObjectData}, + object::{ConstructorBuilder, ObjectData, PROTOTYPE}, property::{Attribute, DataDescriptor}, value::{RcString, Value}, BoaProfiler, Context, Result, @@ -98,7 +98,25 @@ impl RegExp { pub(crate) const LENGTH: usize = 2; /// Create a new `RegExp` - pub(crate) fn constructor(this: &Value, args: &[Value], ctx: &mut Context) -> Result { + pub(crate) fn constructor( + new_target: &Value, + args: &[Value], + ctx: &mut Context, + ) -> Result { + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), ctx) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| ctx.standard_objects().regexp_object().prototype()); + let this = Value::new_object(ctx); + + this.as_object() + .expect("this should be an object") + .set_prototype_instance(prototype.into()); let arg = args.get(0).ok_or_else(Value::undefined)?; let (regex_body, mut regex_flags) = match arg { @@ -186,7 +204,7 @@ impl RegExp { this.set_data(ObjectData::RegExp(Box::new(regexp))); - Ok(this.clone()) + Ok(this) } // /// `RegExp.prototype.dotAll` diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index c157c70a09..9749b9516e 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -13,6 +13,8 @@ pub mod string_iterator; #[cfg(test)] mod tests; +use crate::builtins::Symbol; +use crate::object::PROTOTYPE; use crate::property::DataDescriptor; use crate::{ builtins::{string::string_iterator::StringIterator, Array, BuiltIn, RegExp}, @@ -131,26 +133,50 @@ impl String { /// /// pub(crate) fn constructor( - this: &Value, + new_target: &Value, args: &[Value], context: &mut Context, ) -> Result { - // This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe + // This value is used by console.log and other routines to match Object type // to its Javascript Identifier (global constructor method name) let string = match args.get(0) { + Some(value) if value.is_symbol() && new_target.is_undefined() => { + Symbol::to_string(value, &[], context)? + .as_string() + .expect("'Symbol::to_string' returns 'Value::String'") + .clone() + } Some(ref value) => value.to_string(context)?, None => RcString::default(), }; + if new_target.is_undefined() { + return Ok(string.into()); + } + let prototype = new_target + .as_object() + .and_then(|obj| { + obj.get(&PROTOTYPE.into(), obj.clone().into(), context) + .map(|o| o.as_object()) + .transpose() + }) + .transpose()? + .unwrap_or_else(|| context.standard_objects().object_object().prototype()); + let this = Value::new_object(context); + + this.as_object() + .expect("this should be an object") + .set_prototype_instance(prototype.into()); + let length = DataDescriptor::new( Value::from(string.encode_utf16().count()), Attribute::NON_ENUMERABLE, ); this.set_property("length", length); - this.set_data(ObjectData::String(string.clone())); + this.set_data(ObjectData::String(string)); - Ok(Value::from(string)) + Ok(this) } fn this_string_value(this: &Value, context: &mut Context) -> Result { diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index a7897bf85b..05d1c60be8 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -315,7 +315,14 @@ impl Symbol { /// /// [spec]: https://tc39.es/ecma262/#sec-symbol-description /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol - pub(crate) fn constructor(_: &Value, args: &[Value], context: &mut Context) -> Result { + pub(crate) fn constructor( + new_target: &Value, + args: &[Value], + context: &mut Context, + ) -> Result { + if new_target.is_undefined() { + return context.throw_type_error("Symbol is not a constructor"); + } let description = match args.get(0) { Some(ref value) if !value.is_undefined() => Some(value.to_string(context)?), _ => None, diff --git a/boa/src/environment/lexical_environment.rs b/boa/src/environment/lexical_environment.rs index dec6d0f8f5..07427ba976 100644 --- a/boa/src/environment/lexical_environment.rs +++ b/boa/src/environment/lexical_environment.rs @@ -266,13 +266,14 @@ pub fn new_function_environment( this: Option, outer: Option, binding_status: BindingStatus, + new_target: Value, ) -> Environment { let mut func_env = FunctionEnvironmentRecord { env_rec: FxHashMap::default(), function: f, this_binding_status: binding_status, home_object: Value::undefined(), - new_target: Value::undefined(), + new_target, outer_env: outer, // this will come from Environment set as a private property of F - https://tc39.es/ecma262/#sec-ecmascript-function-objects this_value: Value::undefined(), }; diff --git a/boa/src/object/gcobject.rs b/boa/src/object/gcobject.rs index 4ec239a449..2ead775428 100644 --- a/boa/src/object/gcobject.rs +++ b/boa/src/object/gcobject.rs @@ -40,7 +40,8 @@ pub struct GcObject(Gc>); /// This is needed for the call method since we cannot mutate the function itself since we /// already borrow it so we get the function body clone it then drop the borrow and run the body enum FunctionBody { - BuiltIn(NativeFunction), + BuiltInFunction(NativeFunction), + BuiltInConstructor(NativeFunction), Ordinary(RcStatementList), } @@ -119,8 +120,12 @@ impl GcObject { let f_body = if let Some(function) = self.borrow().as_function() { if function.is_callable() { match function { - Function::BuiltIn(BuiltInFunction(function), _) => { - FunctionBody::BuiltIn(*function) + Function::BuiltIn(BuiltInFunction(function), flags) => { + if flags.is_constructable() { + FunctionBody::BuiltInConstructor(*function) + } else { + FunctionBody::BuiltInFunction(*function) + } } Function::Ordinary { body, @@ -144,6 +149,7 @@ impl GcObject { } else { BindingStatus::Uninitialized }, + Value::undefined(), ); // Add argument bindings to the function environment @@ -182,7 +188,8 @@ impl GcObject { }; match f_body { - FunctionBody::BuiltIn(func) => func(this, args, context), + FunctionBody::BuiltInFunction(func) => func(this, args, context), + FunctionBody::BuiltInConstructor(func) => func(&Value::undefined(), args, context), FunctionBody::Ordinary(body) => { let result = body.run(context); context.realm_mut().environment.pop(); @@ -199,29 +206,18 @@ impl GcObject { /// Panics if the object is currently mutably borrowed. // #[track_caller] - pub fn construct(&self, args: &[Value], context: &mut Context) -> Result { - // 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 = self.get(&PROTOTYPE.into(), self.clone().into(), context)?; - let proto = if proto.is_object() { - proto - } else { - context - .standard_objects() - .object_object() - .prototype() - .into() - }; - let this: Value = Object::create(proto).into(); - + pub fn construct( + &self, + args: &[Value], + new_target: Value, + context: &mut Context, + ) -> Result { let this_function_object = self.clone(); let body = if let Some(function) = self.borrow().as_function() { if function.is_constructable() { match function { Function::BuiltIn(BuiltInFunction(function), _) => { - FunctionBody::BuiltIn(*function) + FunctionBody::BuiltInConstructor(*function) } Function::Ordinary { body, @@ -229,11 +225,31 @@ impl GcObject { environment, flags, } => { + // 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 = new_target.as_object().unwrap().get( + &PROTOTYPE.into(), + new_target.clone(), + context, + )?; + let proto = if proto.is_object() { + proto + } else { + context + .standard_objects() + .object_object() + .prototype() + .into() + }; + let this = Value::from(Object::create(proto)); + // 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(this), 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() { @@ -241,6 +257,7 @@ impl GcObject { } else { BindingStatus::Uninitialized }, + new_target.clone(), ); // Add argument bindings to the function environment @@ -272,7 +289,10 @@ impl GcObject { } } } else { - let name = this.get_field("name", context)?.display().to_string(); + let name = self + .get(&"name".into(), self.clone().into(), context)? + .display() + .to_string(); return context.throw_type_error(format!("{} is not a constructor", name)); } } else { @@ -280,10 +300,7 @@ impl GcObject { }; match body { - FunctionBody::BuiltIn(function) => { - function(&this, args, context)?; - Ok(this) - } + FunctionBody::BuiltInConstructor(function) => function(&new_target, args, context), FunctionBody::Ordinary(body) => { let _ = body.run(context); @@ -291,6 +308,7 @@ impl GcObject { let binding = context.realm_mut().environment.get_this_binding(); binding.map_err(|e| e.to_error(context)) } + FunctionBody::BuiltInFunction(_) => unreachable!("Cannot have a function in construct"), } } diff --git a/boa/src/object/mod.rs b/boa/src/object/mod.rs index 747f91f7f8..93874f107a 100644 --- a/boa/src/object/mod.rs +++ b/boa/src/object/mod.rs @@ -828,7 +828,7 @@ impl<'context> ObjectInitializer<'context> { /// Builder for creating constructors objects, like `Array`. pub struct ConstructorBuilder<'context> { context: &'context mut Context, - constrcutor_function: NativeFunction, + constructor_function: NativeFunction, constructor_object: GcObject, prototype: GcObject, name: Option, @@ -858,7 +858,7 @@ impl<'context> ConstructorBuilder<'context> { pub fn new(context: &'context mut Context, constructor: NativeFunction) -> Self { Self { context, - constrcutor_function: constructor, + constructor_function: constructor, constructor_object: GcObject::new(Object::default()), prototype: GcObject::new(Object::default()), length: 0, @@ -877,7 +877,7 @@ impl<'context> ConstructorBuilder<'context> { ) -> Self { Self { context, - constrcutor_function: constructor, + constructor_function: constructor, constructor_object: object.constructor, prototype: object.prototype, length: 0, @@ -1020,7 +1020,7 @@ impl<'context> ConstructorBuilder<'context> { pub fn build(&mut self) -> GcObject { // Create the native function let function = Function::BuiltIn( - self.constrcutor_function.into(), + self.constructor_function.into(), FunctionFlags::from_parameters(self.callable, self.constructable), ); diff --git a/boa/src/syntax/ast/node/new/mod.rs b/boa/src/syntax/ast/node/new/mod.rs index e67e0a7c56..85be684e41 100644 --- a/boa/src/syntax/ast/node/new/mod.rs +++ b/boa/src/syntax/ast/node/new/mod.rs @@ -69,7 +69,7 @@ impl Executable for New { } match func_object { - Value::Object(ref object) => object.construct(&v_args, context), + Value::Object(ref object) => object.construct(&v_args, object.clone().into(), context), _ => context .throw_type_error(format!("{} is not a constructor", self.expr().to_string(),)), }