diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index 0615c51a0a..80b7554206 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -17,7 +17,11 @@ use crate::{ builtins::array::array_iterator::ArrayIterator, builtins::BuiltIn, builtins::Number, - object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData, PROTOTYPE}, + context::StandardObjects, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, + JsObject, ObjectData, + }, property::{Attribute, PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, value::{IntegerOrInfinity, JsValue}, @@ -130,15 +134,8 @@ impl Array { ) -> JsResult { // If NewTarget is undefined, let newTarget be the active function object; else let newTarget be NewTarget. // 2. Let proto be ? GetPrototypeFromConstructor(newTarget, "%Array.prototype%"). - 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()); + let prototype = + get_prototype_from_constructor(new_target, StandardObjects::array_object, context)?; // 3. Let numberOfArgs be the number of elements in values. let number_of_args = args.len(); diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 9c6e1d5ef3..3739c44659 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -14,7 +14,8 @@ mod tests; use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData, PROTOTYPE}, + context::StandardObjects, + object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, property::Attribute, BoaProfiler, Context, JsResult, JsValue, }; @@ -66,15 +67,8 @@ impl Boolean { if new_target.is_undefined() { return Ok(JsValue::new(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 prototype = + get_prototype_from_constructor(new_target, StandardObjects::boolean_object, context)?; let boolean = JsValue::new_object(context); boolean diff --git a/boa/src/builtins/date/mod.rs b/boa/src/builtins/date/mod.rs index a3be0770e6..7b89169476 100644 --- a/boa/src/builtins/date/mod.rs +++ b/boa/src/builtins/date/mod.rs @@ -3,8 +3,9 @@ mod tests; use crate::{ builtins::BuiltIn, + context::StandardObjects, gc::{empty_trace, Finalize, Trace}, - object::{ConstructorBuilder, ObjectData, PROTOTYPE}, + object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, property::Attribute, symbol::WellKnownSymbols, value::{JsValue, PreferredType}, @@ -340,15 +341,11 @@ impl Date { if new_target.is_undefined() { Ok(Self::make_date_string()) } else { - 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 prototype = get_prototype_from_constructor( + new_target, + StandardObjects::object_object, + context, + )?; let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); let this = obj.into(); diff --git a/boa/src/builtins/error/eval.rs b/boa/src/builtins/error/eval.rs index 81798e8da0..ef914de6c8 100644 --- a/boa/src/builtins/error/eval.rs +++ b/boa/src/builtins/error/eval.rs @@ -11,7 +11,9 @@ //! [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::context::StandardObjects; +use crate::object::internal_methods::get_prototype_from_constructor; + use crate::{ builtins::BuiltIn, object::{ConstructorBuilder, ObjectData}, @@ -62,15 +64,8 @@ impl EvalError { args: &[JsValue], context: &mut Context, ) -> JsResult { - 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 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); diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index 5d23b8e05d..e5da9d9f94 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -12,7 +12,8 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData, PROTOTYPE}, + context::StandardObjects, + object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, profiler::BoaProfiler, property::Attribute, Context, JsResult, JsValue, @@ -78,15 +79,8 @@ impl Error { args: &[JsValue], context: &mut Context, ) -> JsResult { - 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 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); diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 7cdc7b8da5..1a31527460 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -11,7 +11,8 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData, PROTOTYPE}, + context::StandardObjects, + object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, profiler::BoaProfiler, property::Attribute, Context, JsResult, JsValue, @@ -59,15 +60,8 @@ impl RangeError { args: &[JsValue], context: &mut Context, ) -> JsResult { - 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 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); diff --git a/boa/src/builtins/error/reference.rs b/boa/src/builtins/error/reference.rs index 95c16069f2..e95b00211b 100644 --- a/boa/src/builtins/error/reference.rs +++ b/boa/src/builtins/error/reference.rs @@ -11,7 +11,8 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData, PROTOTYPE}, + context::StandardObjects, + object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, profiler::BoaProfiler, property::Attribute, Context, JsResult, JsValue, @@ -58,15 +59,8 @@ impl ReferenceError { args: &[JsValue], context: &mut Context, ) -> JsResult { - 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 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); diff --git a/boa/src/builtins/error/syntax.rs b/boa/src/builtins/error/syntax.rs index b52b6025a7..f84ab4140f 100644 --- a/boa/src/builtins/error/syntax.rs +++ b/boa/src/builtins/error/syntax.rs @@ -13,7 +13,8 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData, PROTOTYPE}, + context::StandardObjects, + object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, profiler::BoaProfiler, property::Attribute, Context, JsResult, JsValue, @@ -61,15 +62,8 @@ impl SyntaxError { args: &[JsValue], context: &mut Context, ) -> JsResult { - 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 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); diff --git a/boa/src/builtins/error/type.rs b/boa/src/builtins/error/type.rs index 6b4877638d..2933cdcca9 100644 --- a/boa/src/builtins/error/type.rs +++ b/boa/src/builtins/error/type.rs @@ -17,7 +17,8 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData, PROTOTYPE}, + context::StandardObjects, + object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, property::Attribute, BoaProfiler, Context, JsResult, JsValue, }; @@ -64,15 +65,8 @@ impl TypeError { args: &[JsValue], context: &mut Context, ) -> JsResult { - 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 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); diff --git a/boa/src/builtins/error/uri.rs b/boa/src/builtins/error/uri.rs index c6f8fdc1d2..6b93dc10d9 100644 --- a/boa/src/builtins/error/uri.rs +++ b/boa/src/builtins/error/uri.rs @@ -12,7 +12,8 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData, PROTOTYPE}, + context::StandardObjects, + object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, profiler::BoaProfiler, property::Attribute, Context, JsResult, JsValue, @@ -60,15 +61,8 @@ impl UriError { args: &[JsValue], context: &mut Context, ) -> JsResult { - 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 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); diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index 5f2c2cf707..6b8695dfd4 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -11,7 +11,9 @@ //! [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::context::StandardObjects; +use crate::object::internal_methods::get_prototype_from_constructor; + use crate::{ builtins::{Array, BuiltIn}, environment::lexical_environment::Environment, @@ -302,15 +304,8 @@ impl BuiltInFunctionObject { _: &[JsValue], context: &mut Context, ) -> JsResult { - 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 prototype = + get_prototype_from_constructor(new_target, StandardObjects::function_object, context)?; let this = JsValue::new_object(context); this.as_object() diff --git a/boa/src/builtins/map/mod.rs b/boa/src/builtins/map/mod.rs index 2dd3333256..9912d18844 100644 --- a/boa/src/builtins/map/mod.rs +++ b/boa/src/builtins/map/mod.rs @@ -14,7 +14,11 @@ use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE}, + context::StandardObjects, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, + ObjectData, + }, property::{Attribute, PropertyDescriptor, PropertyNameKind}, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, JsValue, @@ -114,16 +118,8 @@ impl Map { return context .throw_type_error("calling a builtin Map constructor without new is forbidden"); } - let map_prototype = context.standard_objects().map_object().prototype(); - 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 prototype = + get_prototype_from_constructor(new_target, StandardObjects::map_object, context)?; let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index f44290ab2e..da03083115 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -15,9 +15,10 @@ use super::string::is_trimmable_whitespace; use super::{function::make_builtin_fn, JsArgs}; +use crate::context::StandardObjects; use crate::{ builtins::BuiltIn, - object::{ConstructorBuilder, ObjectData, PROTOTYPE}, + object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, property::Attribute, value::{AbstractRelation, IntegerOrInfinity, JsValue}, BoaProfiler, Context, JsResult, @@ -166,15 +167,8 @@ impl Number { if new_target.is_undefined() { return Ok(JsValue::new(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 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") diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 35914e6b74..58b5174b36 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -15,9 +15,10 @@ use crate::{ builtins::{BuiltIn, JsArgs}, + context::StandardObjects, object::{ - ConstructorBuilder, IntegrityLevel, JsObject, Object as BuiltinObject, ObjectData, - ObjectInitializer, ObjectKind, PROTOTYPE, + internal_methods::get_prototype_from_constructor, ConstructorBuilder, IntegrityLevel, + JsObject, Object as BuiltinObject, ObjectData, ObjectInitializer, ObjectKind, }, property::{Attribute, DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind}, symbol::WellKnownSymbols, @@ -99,15 +100,11 @@ impl Object { context: &mut Context, ) -> JsResult { 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 prototype = get_prototype_from_constructor( + new_target, + StandardObjects::object_object, + context, + )?; let object = JsValue::new_object(context); object diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 719924a109..ca49d3b648 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -13,8 +13,12 @@ pub mod regexp_string_iterator; use crate::{ builtins::{array::Array, string, BuiltIn}, + context::StandardObjects, gc::{empty_trace, Finalize, Trace}, - object::{ConstructorBuilder, FunctionBuilder, JsObject, Object, ObjectData, PROTOTYPE}, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, + JsObject, Object, ObjectData, + }, property::Attribute, symbol::WellKnownSymbols, value::{IntegerOrInfinity, JsValue}, @@ -257,17 +261,9 @@ impl RegExp { /// /// [spec]: https://tc39.es/ecma262/#sec-regexpalloc fn alloc(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let proto = if let Some(obj) = this.as_object() { - obj.get(PROTOTYPE, context)? - } else { - context - .standard_objects() - .regexp_object() - .prototype() - .into() - }; + let proto = get_prototype_from_constructor(this, StandardObjects::regexp_object, context)?; - Ok(JsObject::new(Object::create(proto)).into()) + Ok(JsObject::new(Object::create(proto.into())).into()) } /// `22.2.3.2.2 RegExpInitialize ( obj, pattern, flags )` diff --git a/boa/src/builtins/set/mod.rs b/boa/src/builtins/set/mod.rs index 0cf4b991fb..edd96e656c 100644 --- a/boa/src/builtins/set/mod.rs +++ b/boa/src/builtins/set/mod.rs @@ -12,7 +12,11 @@ use crate::{ builtins::{iterable::get_iterator, BuiltIn}, - object::{ConstructorBuilder, FunctionBuilder, ObjectData, PROTOTYPE}, + context::StandardObjects, + object::{ + internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, + ObjectData, + }, property::{Attribute, PropertyNameKind}, symbol::WellKnownSymbols, BoaProfiler, Context, JsResult, JsValue, @@ -123,16 +127,8 @@ impl Set { } // 2 - let set_prototype = context.standard_objects().set_object().prototype(); - 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(set_prototype); + let prototype = + get_prototype_from_constructor(new_target, StandardObjects::set_object, context)?; let obj = context.construct_object(); obj.set_prototype_instance(prototype.into()); diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index a249d7b654..b280697794 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -14,7 +14,9 @@ pub mod string_iterator; mod tests; use crate::builtins::Symbol; -use crate::object::{JsObject, PROTOTYPE}; +use crate::context::StandardObjects; +use crate::object::internal_methods::get_prototype_from_constructor; +use crate::object::JsObject; use crate::{ builtins::{string::string_iterator::StringIterator, Array, BuiltIn, RegExp}, object::{ConstructorBuilder, ObjectData}, @@ -179,16 +181,8 @@ impl String { return Ok(string.into()); } - // todo: extract `GetPrototypeFromConstructor` function - 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 prototype = + get_prototype_from_constructor(new_target, StandardObjects::string_object, context)?; Ok(Self::string_create(string, prototype, context).into()) } diff --git a/boa/src/object/internal_methods/mod.rs b/boa/src/object/internal_methods/mod.rs index 5ce33579d6..154008411b 100644 --- a/boa/src/object/internal_methods/mod.rs +++ b/boa/src/object/internal_methods/mod.rs @@ -6,12 +6,15 @@ //! [spec]: https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots use crate::{ + context::{StandardConstructor, StandardObjects}, object::JsObject, property::{DescriptorKind, PropertyDescriptor, PropertyKey}, value::JsValue, BoaProfiler, Context, JsResult, }; +use super::PROTOTYPE; + pub(super) mod array; pub(super) mod string; @@ -859,3 +862,36 @@ pub(crate) fn validate_and_apply_property_descriptor( // 10. Return true. true } + +/// Abstract operation `GetPrototypeFromConstructor` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-getprototypefromconstructor +#[inline] +#[track_caller] +pub(crate) fn get_prototype_from_constructor( + constructor: &JsValue, + default: F, + context: &mut Context, +) -> JsResult +where + F: FnOnce(&StandardObjects) -> &StandardConstructor, +{ + // 1. Assert: intrinsicDefaultProto is this specification's name of an intrinsic + // object. + // The corresponding object must be an intrinsic that is intended to be used + // as the [[Prototype]] value of an object. + // 2. Let proto be ? Get(constructor, "prototype"). + if let Some(object) = constructor.as_object() { + if let Some(proto) = object.get(PROTOTYPE, context)?.as_object() { + return Ok(proto); + } + } + // 3. If Type(proto) is not Object, then + // TODO: handle realms + // a. Let realm be ? GetFunctionRealm(constructor). + // b. Set proto to realm's intrinsic object named intrinsicDefaultProto. + Ok(default(context.standard_objects()).prototype()) +}