Browse Source

Unify object creation with `empty` and `from_proto_and_data` methods (#1567)

pull/1633/head
jedel1043 3 years ago committed by GitHub
parent
commit
e1c573aaca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 25
      boa/src/builtins/array/array_iterator.rs
  2. 26
      boa/src/builtins/array/mod.rs
  3. 14
      boa/src/builtins/boolean/mod.rs
  4. 51
      boa/src/builtins/date/mod.rs
  5. 13
      boa/src/builtins/error/eval.rs
  6. 16
      boa/src/builtins/error/mod.rs
  7. 16
      boa/src/builtins/error/range.rs
  8. 16
      boa/src/builtins/error/reference.rs
  9. 16
      boa/src/builtins/error/syntax.rs
  10. 16
      boa/src/builtins/error/type.rs
  11. 16
      boa/src/builtins/error/uri.rs
  12. 376
      boa/src/builtins/function/mod.rs
  13. 21
      boa/src/builtins/iterable/mod.rs
  14. 25
      boa/src/builtins/map/map_iterator.rs
  15. 7
      boa/src/builtins/map/mod.rs
  16. 17
      boa/src/builtins/number/mod.rs
  17. 25
      boa/src/builtins/object/for_in_iterator.rs
  18. 20
      boa/src/builtins/object/mod.rs
  19. 6
      boa/src/builtins/regexp/mod.rs
  20. 42
      boa/src/builtins/regexp/regexp_string_iterator.rs
  21. 17
      boa/src/builtins/set/mod.rs
  22. 25
      boa/src/builtins/set/set_iterator.rs
  23. 4
      boa/src/builtins/string/mod.rs
  24. 27
      boa/src/builtins/string/string_iterator.rs
  25. 2
      boa/src/bytecompiler.rs
  26. 13
      boa/src/class.rs
  27. 50
      boa/src/context.rs
  28. 376
      boa/src/object/function.rs
  29. 57
      boa/src/object/jsobject.rs
  30. 134
      boa/src/object/mod.rs
  31. 8
      boa/src/realm.rs
  32. 2
      boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs
  33. 2
      boa/src/syntax/ast/node/declaration/function_decl/mod.rs
  34. 2
      boa/src/syntax/ast/node/declaration/function_expr/mod.rs
  35. 46
      boa/src/syntax/ast/node/object/mod.rs
  36. 48
      boa/src/value/conversions.rs
  37. 48
      boa/src/value/mod.rs
  38. 20
      boa/src/value/tests.rs
  39. 33
      boa/src/vm/code_block.rs
  40. 9
      boa/src/vm/mod.rs

25
boa/src/builtins/array/array_iterator.rs

@ -1,7 +1,7 @@
use crate::{ 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}, gc::{Finalize, Trace},
object::{function::make_builtin_fn, JsObject, ObjectData}, object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind}, property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, BoaProfiler, Context, JsResult,
@ -46,13 +46,11 @@ impl ArrayIterator {
kind: PropertyNameKind, kind: PropertyNameKind,
context: &Context, context: &Context,
) -> JsValue { ) -> JsValue {
let array_iterator = JsValue::new_object(context); let array_iterator = JsObject::from_proto_and_data(
array_iterator.set_data(ObjectData::array_iterator(Self::new(array, kind))); context.iterator_prototypes().array_iterator(),
array_iterator ObjectData::array_iterator(Self::new(array, kind)),
.as_object() );
.expect("array iterator object") array_iterator.into()
.set_prototype_instance(context.iterator_prototypes().array_iterator().into());
array_iterator
} }
/// %ArrayIteratorPrototype%.next( ) /// %ArrayIteratorPrototype%.next( )
@ -123,13 +121,16 @@ impl ArrayIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object /// [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"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype // Create prototype
let array_iterator = context.construct_object(); let array_iterator =
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &array_iterator, 0, context); 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 = WellKnownSymbols::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()

26
boa/src/builtins/array/mod.rs

@ -223,12 +223,7 @@ impl Array {
Some(prototype) => prototype, Some(prototype) => prototype,
None => context.standard_objects().array_object().prototype(), None => context.standard_objects().array_object().prototype(),
}; };
let array = context.construct_object(); let array = JsObject::from_proto_and_data(prototype, ObjectData::array());
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();
// 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
crate::object::internal_methods::ordinary_define_own_property( crate::object::internal_methods::ordinary_define_own_property(
@ -275,22 +270,9 @@ impl Array {
/// Creates a new `Array` instance. /// Creates a new `Array` instance.
pub(crate) fn new_array(context: &mut Context) -> JsValue { pub(crate) fn new_array(context: &mut Context) -> JsValue {
let array = JsValue::new_object(context); Self::array_create(0, None, context)
array.set_data(ObjectData::array()); .expect("creating an empty array with the default prototype must not fail")
array .into()
.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
} }
/// Utility function for concatenating array objects. /// Utility function for concatenating array objects.

14
boa/src/builtins/boolean/mod.rs

@ -15,7 +15,9 @@ mod tests;
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
property::Attribute, property::Attribute,
BoaProfiler, Context, JsResult, JsValue, BoaProfiler, Context, JsResult, JsValue,
}; };
@ -69,15 +71,9 @@ impl Boolean {
} }
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::boolean_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::boolean_object, context)?;
let boolean = JsValue::new_object(context); let boolean = JsObject::from_proto_and_data(prototype, ObjectData::boolean(data));
boolean Ok(boolean.into())
.as_object()
.expect("this should be an object")
.set_prototype_instance(prototype.into());
boolean.set_data(ObjectData::boolean(data));
Ok(boolean)
} }
/// An Utility function used to get the internal `[[BooleanData]]`. /// An Utility function used to get the internal `[[BooleanData]]`.

51
boa/src/builtins/date/mod.rs

@ -5,7 +5,9 @@ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
gc::{empty_trace, Finalize, Trace}, 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, property::Attribute,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
value::{JsValue, PreferredType}, value::{JsValue, PreferredType},
@ -346,16 +348,14 @@ impl Date {
StandardObjects::object_object, StandardObjects::object_object,
context, context,
)?; )?;
let obj = context.construct_object(); Ok(if args.is_empty() {
obj.set_prototype_instance(prototype.into()); Self::make_date_now(prototype)
let this = obj.into();
if args.is_empty() {
Ok(Self::make_date_now(&this))
} else if args.len() == 1 { } else if args.len() == 1 {
Self::make_date_single(&this, args, context) Self::make_date_single(prototype, args, context)?
} else { } 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 /// [spec]: https://tc39.es/ecma262/#sec-date-constructor
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
pub(crate) fn make_date_now(this: &JsValue) -> JsValue { pub(crate) fn make_date_now(prototype: JsObject) -> JsObject {
let date = Date::default(); JsObject::from_proto_and_data(prototype, ObjectData::date(Date::default()))
this.set_data(ObjectData::date(date));
this.clone()
} }
/// `Date(value)` /// `Date(value)`
@ -400,10 +398,10 @@ impl Date {
/// [spec]: https://tc39.es/ecma262/#sec-date-constructor /// [spec]: https://tc39.es/ecma262/#sec-date-constructor
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
pub(crate) fn make_date_single( pub(crate) fn make_date_single(
this: &JsValue, prototype: JsObject,
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsObject> {
let value = &args[0]; let value = &args[0];
let tv = match this_time_value(value, context) { let tv = match this_time_value(value, context) {
Ok(dt) => dt.0, 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 tv = tv.filter(|time| Self::time_clip(time.timestamp_millis() as f64).is_some());
let date = Date(tv); Ok(JsObject::from_proto_and_data(
this.set_data(ObjectData::date(date)); prototype,
Ok(this.clone()) ObjectData::date(Date(tv)),
))
} }
/// `Date(year, month [ , date [ , hours [ , minutes [ , seconds [ , ms ] ] ] ] ])` /// `Date(year, month [ , date [ , hours [ , minutes [ , seconds [ , ms ] ] ] ] ])`
@ -442,10 +441,10 @@ impl Date {
/// [spec]: https://tc39.es/ecma262/#sec-date-constructor /// [spec]: https://tc39.es/ecma262/#sec-date-constructor
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
pub(crate) fn make_date_multiple( pub(crate) fn make_date_multiple(
this: &JsValue, prototype: JsObject,
args: &[JsValue], args: &[JsValue],
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsObject> {
let mut year = args[0].to_number(context)?; let mut year = args[0].to_number(context)?;
let month = args[1].to_number(context)?; let month = args[1].to_number(context)?;
let day = args let day = args
@ -466,9 +465,10 @@ impl Date {
// If any of the args are infinity or NaN, return an invalid 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) { if !check_normal_opt!(year, month, day, hour, min, sec, milli) {
let date = Date(None); return Ok(JsObject::from_proto_and_data(
this.set_data(ObjectData::date(date)); prototype,
return Ok(this.clone()); ObjectData::date(Date(None)),
));
} }
if (0.0..=99.0).contains(&year) { if (0.0..=99.0).contains(&year) {
@ -493,9 +493,10 @@ impl Date {
Some(milli), Some(milli),
); );
this.set_data(ObjectData::date(date)); Ok(JsObject::from_proto_and_data(
prototype,
Ok(this.clone()) ObjectData::date(date),
))
} }
/// `Date.prototype[@@toPrimitive]` /// `Date.prototype[@@toPrimitive]`

13
boa/src/builtins/error/eval.rs

@ -13,6 +13,7 @@
use crate::context::StandardObjects; use crate::context::StandardObjects;
use crate::object::internal_methods::get_prototype_from_constructor; use crate::object::internal_methods::get_prototype_from_constructor;
use crate::object::JsObject;
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
@ -66,18 +67,12 @@ impl EvalError {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = context.construct_object(); let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
obj.set_prototype_instance(prototype.into());
let this = JsValue::new(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
if !message.is_undefined() { if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, false, context)?; obj.set("message", message.to_string(context)?, false, context)?;
} }
} }
Ok(obj.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::error());
Ok(this)
} }
} }

16
boa/src/builtins/error/mod.rs

@ -13,7 +13,9 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
profiler::BoaProfiler, profiler::BoaProfiler,
property::Attribute, property::Attribute,
Context, JsResult, JsValue, Context, JsResult, JsValue,
@ -81,19 +83,13 @@ impl Error {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = context.construct_object(); let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
obj.set_prototype_instance(prototype.into());
let this = JsValue::new(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
if !message.is_undefined() { if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, false, context)?; obj.set("message", message.to_string(context)?, false, context)?;
} }
} }
Ok(obj.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::error());
Ok(this)
} }
/// `Error.prototype.toString()` /// `Error.prototype.toString()`

16
boa/src/builtins/error/range.rs

@ -12,7 +12,9 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
profiler::BoaProfiler, profiler::BoaProfiler,
property::Attribute, property::Attribute,
Context, JsResult, JsValue, Context, JsResult, JsValue,
@ -62,18 +64,12 @@ impl RangeError {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = context.construct_object(); let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
obj.set_prototype_instance(prototype.into());
let this = JsValue::new(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
if !message.is_undefined() { if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, false, context)?; obj.set("message", message.to_string(context)?, false, context)?;
} }
} }
Ok(obj.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::error());
Ok(this)
} }
} }

16
boa/src/builtins/error/reference.rs

@ -12,7 +12,9 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
profiler::BoaProfiler, profiler::BoaProfiler,
property::Attribute, property::Attribute,
Context, JsResult, JsValue, Context, JsResult, JsValue,
@ -61,18 +63,12 @@ impl ReferenceError {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = context.construct_object(); let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
obj.set_prototype_instance(prototype.into());
let this = JsValue::new(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
if !message.is_undefined() { if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, false, context)?; obj.set("message", message.to_string(context)?, false, context)?;
} }
} }
Ok(obj.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::error());
Ok(this)
} }
} }

16
boa/src/builtins/error/syntax.rs

@ -14,7 +14,9 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
profiler::BoaProfiler, profiler::BoaProfiler,
property::Attribute, property::Attribute,
Context, JsResult, JsValue, Context, JsResult, JsValue,
@ -64,18 +66,12 @@ impl SyntaxError {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = context.construct_object(); let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
obj.set_prototype_instance(prototype.into());
let this = JsValue::new(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
if !message.is_undefined() { if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, false, context)?; obj.set("message", message.to_string(context)?, false, context)?;
} }
} }
Ok(obj.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::error());
Ok(this)
} }
} }

16
boa/src/builtins/error/type.rs

@ -18,7 +18,9 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
property::Attribute, property::Attribute,
BoaProfiler, Context, JsResult, JsValue, BoaProfiler, Context, JsResult, JsValue,
}; };
@ -67,18 +69,12 @@ impl TypeError {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = context.construct_object(); let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
obj.set_prototype_instance(prototype.into());
let this = JsValue::new(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
if !message.is_undefined() { if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, false, context)?; obj.set("message", message.to_string(context)?, false, context)?;
} }
} }
Ok(obj.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::error());
Ok(this)
} }
} }

16
boa/src/builtins/error/uri.rs

@ -13,7 +13,9 @@
use crate::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData}, object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, JsObject, ObjectData,
},
profiler::BoaProfiler, profiler::BoaProfiler,
property::Attribute, property::Attribute,
Context, JsResult, JsValue, Context, JsResult, JsValue,
@ -63,18 +65,12 @@ impl UriError {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::error_object, context)?;
let obj = context.construct_object(); let obj = JsObject::from_proto_and_data(prototype, ObjectData::error());
obj.set_prototype_instance(prototype.into());
let this = JsValue::new(obj);
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
if !message.is_undefined() { if !message.is_undefined() {
this.set_field("message", message.to_string(context)?, false, context)?; obj.set("message", message.to_string(context)?, false, context)?;
} }
} }
Ok(obj.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::error());
Ok(this)
} }
} }

376
boa/src/builtins/function/mod.rs

@ -11,14 +11,26 @@
//! [spec]: https://tc39.es/ecma262/#sec-function-objects //! [spec]: https://tc39.es/ecma262/#sec-function-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function //! [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::{ use crate::{
builtins::BuiltIn, builtins::BuiltIn,
context::StandardObjects, context::StandardObjects,
environment::lexical_environment::Environment,
gc::{Finalize, Trace},
object::JsObject,
object::{ object::{
function::Function, internal_methods::get_prototype_from_constructor, ConstructorBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
FunctionBuilder, ObjectData, NativeObject, ObjectData,
}, },
property::Attribute, property::Attribute,
property::PropertyDescriptor,
syntax::ast::node::{FormalParameter, RcStatementList},
BoaProfiler, Context, JsResult, JsValue, BoaProfiler, Context, JsResult, JsValue,
}; };
@ -27,6 +39,349 @@ use super::JsArgs;
#[cfg(test)] #[cfg(test)]
mod tests; 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<JsValue>;
// Allows restricting closures to only `Copy` ones.
// Used the sealed pattern to disallow external implementations
// of `DynCopy`.
mod sealed {
pub trait Sealed {}
impl<T: Copy> Sealed for T {}
}
pub trait DynCopy: sealed::Sealed {}
impl<T: Copy> 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<JsValue> + DynCopy + DynClone + 'static
{
}
// The `Copy` bound automatically infers `DynCopy` and `DynClone`
impl<T> ClosureFunctionSignature for T where
T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult<JsValue> + Copy + 'static
{
}
// Allows cloning Box<dyn ClosureFunctionSignature>
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<dyn CapturesObject>`.
trait CapturesObject: NativeObject + DynClone {}
impl<T: NativeObject + Clone> CapturesObject for T {}
dyn_clone::clone_trait_object!(CapturesObject);
/// Wrapper for `Box<dyn NativeObject + Clone>` 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<dyn CapturesObject>);
impl Captures {
/// Creates a new capture context.
pub(crate) fn new<T>(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<T>(&self) -> Option<&T>
where
T: NativeObject + Clone,
{
self.0.deref().as_any().downcast_ref::<T>()
}
/// Mutably downcasts `Captures` to the specified type, returning a
/// mutable reference to the downcasted type if successful or `None` otherwise.
pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: NativeObject + Clone,
{
self.0.deref_mut().as_mut_any().downcast_mut::<T>()
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or a `TypeError` otherwise.
pub fn try_downcast_ref<T>(&self, context: &mut Context) -> JsResult<&T>
where
T: NativeObject + Clone,
{
self.0
.deref()
.as_any()
.downcast_ref::<T>()
.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<T>(&mut self, context: &mut Context) -> JsResult<&mut T>
where
T: NativeObject + Clone,
{
self.0
.deref_mut()
.as_mut_any()
.downcast_mut::<T>()
.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)
///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Clone, Trace, Finalize)]
pub enum Function {
Native {
#[unsafe_ignore_trace]
function: NativeFunctionSignature,
constructable: bool,
},
Closure {
#[unsafe_ignore_trace]
function: Box<dyn ClosureFunctionSignature>,
constructable: bool,
captures: Captures,
},
Ordinary {
constructable: bool,
this_mode: ThisMode,
body: RcStatementList,
params: Box<[FormalParameter]>,
environment: Environment,
},
#[cfg(feature = "vm")]
VmOrdinary {
code: gc::Gc<crate::vm::CodeBlock>,
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.
///
/// <https://tc39.es/ecma262/#sec-createunmappedargumentsobject>
pub fn create_unmapped_arguments_object(
arguments_list: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
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 <https://tc39.es/ecma262/#sec-function-instances-length>, 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<N>(
function: NativeFunctionSignature,
name: N,
parent: &JsObject,
length: usize,
interpreter: &Context,
) where
N: Into<String>,
{
let name = name.into();
let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init");
let function = JsObject::from_proto_and_data(
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)] #[derive(Debug, Clone, Copy)]
pub struct BuiltInFunctionObject; pub struct BuiltInFunctionObject;
@ -40,17 +395,16 @@ impl BuiltInFunctionObject {
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::function_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::function_object, context)?;
let this = JsValue::new_object(context);
this.as_object() let this = JsObject::from_proto_and_data(
.expect("this should be an object") prototype,
.set_prototype_instance(prototype.into()); ObjectData::function(Function::Native {
function: |_, _, _| Ok(JsValue::undefined()),
constructable: true,
}),
);
this.set_data(ObjectData::function(Function::Native { Ok(this.into())
function: |_, _, _| Ok(JsValue::undefined()),
constructable: true,
}));
Ok(this)
} }
fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> { fn prototype(_: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult<JsValue> {

21
boa/src/builtins/iterable/mod.rs

@ -24,24 +24,15 @@ impl IteratorPrototypes {
pub(crate) fn init(context: &mut Context) -> Self { pub(crate) fn init(context: &mut Context) -> Self {
let iterator_prototype = create_iterator_prototype(context); let iterator_prototype = create_iterator_prototype(context);
Self { Self {
array_iterator: ArrayIterator::create_prototype( array_iterator: ArrayIterator::create_prototype(iterator_prototype.clone(), context),
iterator_prototype.clone().into(), set_iterator: SetIterator::create_prototype(iterator_prototype.clone(), context),
context, string_iterator: StringIterator::create_prototype(iterator_prototype.clone(), context),
),
set_iterator: SetIterator::create_prototype(iterator_prototype.clone().into(), context),
string_iterator: StringIterator::create_prototype(
iterator_prototype.clone().into(),
context,
),
regexp_string_iterator: RegExpStringIterator::create_prototype( regexp_string_iterator: RegExpStringIterator::create_prototype(
iterator_prototype.clone().into(), iterator_prototype.clone(),
context,
),
map_iterator: MapIterator::create_prototype(iterator_prototype.clone().into(), context),
for_in_iterator: ForInIterator::create_prototype(
iterator_prototype.clone().into(),
context, context,
), ),
map_iterator: MapIterator::create_prototype(iterator_prototype.clone(), context),
for_in_iterator: ForInIterator::create_prototype(iterator_prototype.clone(), context),
iterator_prototype, iterator_prototype,
} }
} }

25
boa/src/builtins/map/map_iterator.rs

@ -1,6 +1,6 @@
use crate::{ use crate::{
builtins::{iterable::create_iter_result_object, Array, JsValue}, builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
object::{function::make_builtin_fn, JsObject, ObjectData}, object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind}, property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, BoaProfiler, Context, JsResult,
@ -47,13 +47,11 @@ impl MapIterator {
map_iteration_kind: kind, map_iteration_kind: kind,
lock, lock,
}; };
let map_iterator = JsValue::new_object(context); let map_iterator = JsObject::from_proto_and_data(
map_iterator.set_data(ObjectData::map_iterator(iter)); context.iterator_prototypes().map_iterator(),
map_iterator ObjectData::map_iterator(iter),
.as_object() );
.expect("map iterator object") return Ok(map_iterator.into());
.set_prototype_instance(context.iterator_prototypes().map_iterator().into());
return Ok(map_iterator);
} }
} }
context.throw_type_error("`this` is not a Map") context.throw_type_error("`this` is not a Map")
@ -123,13 +121,16 @@ impl MapIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%-object /// [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"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype // Create prototype
let map_iterator = context.construct_object(); let map_iterator =
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &map_iterator, 0, context); 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 = WellKnownSymbols::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()

7
boa/src/builtins/map/mod.rs

@ -133,13 +133,10 @@ impl Map {
} }
// 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%Map.prototype%", « [[MapData]] »). // 2. Let map be ? OrdinaryCreateFromConstructor(NewTarget, "%Map.prototype%", « [[MapData]] »).
// 3. Set map.[[MapData]] to a new empty List.
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::map_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::map_object, context)?;
let map = context.construct_object(); let map = JsObject::from_proto_and_data(prototype, ObjectData::map(OrderedMap::new()));
map.set_prototype_instance(prototype.into());
// 3. Set map.[[MapData]] to a new empty List.
map.borrow_mut().data = ObjectData::map(OrderedMap::new());
// 4. If iterable is either undefined or null, return map. // 4. If iterable is either undefined or null, return map.
let iterable = match args.get_or_undefined(0) { let iterable = match args.get_or_undefined(0) {

17
boa/src/builtins/number/mod.rs

@ -16,12 +16,10 @@
use super::string::is_trimmable_whitespace; use super::string::is_trimmable_whitespace;
use super::JsArgs; use super::JsArgs;
use crate::context::StandardObjects; use crate::context::StandardObjects;
use crate::object::JsObject;
use crate::{ use crate::{
builtins::BuiltIn, builtins::{function::make_builtin_fn, BuiltIn},
object::{ object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData},
function::make_builtin_fn, internal_methods::get_prototype_from_constructor,
ConstructorBuilder, ObjectData,
},
property::Attribute, property::Attribute,
value::{AbstractRelation, IntegerOrInfinity, JsValue}, value::{AbstractRelation, IntegerOrInfinity, JsValue},
BoaProfiler, Context, JsResult, BoaProfiler, Context, JsResult,
@ -172,13 +170,8 @@ impl Number {
} }
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::number_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::number_object, context)?;
let this = JsValue::new_object(context); let this = JsObject::from_proto_and_data(prototype, ObjectData::number(data));
this.as_object() Ok(this.into())
.expect("this should be an object")
.set_prototype_instance(prototype.into());
this.set_data(ObjectData::number(data));
Ok(this)
} }
/// This function returns a `JsResult` of the number `Value`. /// This function returns a `JsResult` of the number `Value`.

25
boa/src/builtins/object/for_in_iterator.rs

@ -1,7 +1,7 @@
use crate::{ use crate::{
builtins::iterable::create_iter_result_object, builtins::{function::make_builtin_fn, iterable::create_iter_result_object},
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::{function::make_builtin_fn, JsObject, ObjectData}, object::{JsObject, ObjectData},
property::PropertyDescriptor, property::PropertyDescriptor,
property::PropertyKey, property::PropertyKey,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -46,13 +46,11 @@ impl ForInIterator {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-createforiniterator /// [spec]: https://tc39.es/ecma262/#sec-createforiniterator
pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context) -> JsValue { pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context) -> JsValue {
let for_in_iterator = JsValue::new_object(context); let for_in_iterator = JsObject::from_proto_and_data(
for_in_iterator.set_data(ObjectData::for_in_iterator(Self::new(object))); context.iterator_prototypes().for_in_iterator(),
for_in_iterator ObjectData::for_in_iterator(Self::new(object)),
.as_object() );
.expect("for in iterator object") for_in_iterator.into()
.set_prototype_instance(context.iterator_prototypes().for_in_iterator().into());
for_in_iterator
} }
/// %ForInIteratorPrototype%.next( ) /// %ForInIteratorPrototype%.next( )
@ -129,13 +127,16 @@ impl ForInIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%-object /// [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"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype // Create prototype
let for_in_iterator = context.construct_object(); let for_in_iterator =
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &for_in_iterator, 0, context); 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 = WellKnownSymbols::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()

20
boa/src/builtins/object/mod.rs

@ -18,7 +18,7 @@ use crate::{
context::StandardObjects, context::StandardObjects,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, IntegrityLevel, 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}, property::{Attribute, DescriptorKind, PropertyDescriptor, PropertyKey, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -107,20 +107,15 @@ impl Object {
StandardObjects::object_object, StandardObjects::object_object,
context, context,
)?; )?;
let object = JsValue::new_object(context); let object = JsObject::from_proto_and_data(prototype, ObjectData::ordinary());
return Ok(object.into());
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 let Some(arg) = args.get(0) {
if !arg.is_null_or_undefined() { if !arg.is_null_or_undefined() {
return Ok(arg.to_object(context)?.into()); return Ok(arg.to_object(context)?.into());
} }
} }
Ok(JsValue::new_object(context)) Ok(context.construct_object().into())
} }
/// `Object.create( proto, [propertiesObject] )` /// `Object.create( proto, [propertiesObject] )`
@ -138,10 +133,9 @@ impl Object {
let properties = args.get_or_undefined(1); let properties = args.get_or_undefined(1);
let obj = match prototype { let obj = match prototype {
JsValue::Object(_) | JsValue::Null => JsObject::new(BuiltinObject::with_prototype( JsValue::Object(_) | JsValue::Null => {
prototype.clone(), JsObject::from_proto_and_data(prototype.as_object(), ObjectData::ordinary())
ObjectData::ordinary(), }
)),
_ => { _ => {
return context.throw_type_error(format!( return context.throw_type_error(format!(
"Object prototype may only be an Object or null: {}", "Object prototype may only be an Object or null: {}",

6
boa/src/builtins/regexp/mod.rs

@ -17,7 +17,7 @@ use crate::{
gc::{empty_trace, Finalize, Trace}, gc::{empty_trace, Finalize, Trace},
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, Object, ObjectData, JsObject, ObjectData,
}, },
property::Attribute, property::Attribute,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -263,7 +263,7 @@ impl RegExp {
fn alloc(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { fn alloc(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
let proto = get_prototype_from_constructor(this, StandardObjects::regexp_object, context)?; 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(proto, ObjectData::ordinary()).into())
} }
/// `22.2.3.2.2 RegExpInitialize ( obj, pattern, flags )` /// `22.2.3.2.2 RegExpInitialize ( obj, pattern, flags )`
@ -983,7 +983,7 @@ impl RegExp {
let named_groups = match_value.named_groups(); let named_groups = match_value.named_groups();
let groups = if named_groups.clone().count() > 0 { let groups = if named_groups.clone().count() > 0 {
// a. Let groups be ! OrdinaryObjectCreate(null). // a. Let groups be ! OrdinaryObjectCreate(null).
let groups = JsValue::from(JsObject::new(Object::create(JsValue::null()))); let groups = JsValue::from(JsObject::empty());
// Perform 27.f here // Perform 27.f here
// f. If the ith capture of R was defined with a GroupName, then // f. If the ith capture of R was defined with a GroupName, then

42
boa/src/builtins/regexp/regexp_string_iterator.rs

@ -12,9 +12,9 @@
use regexp::{advance_string_index, RegExp}; use regexp::{advance_string_index, RegExp};
use crate::{ use crate::{
builtins::{iterable::create_iter_result_object, regexp}, builtins::{function::make_builtin_fn, iterable::create_iter_result_object, regexp},
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::{function::make_builtin_fn, JsObject, ObjectData}, object::{JsObject, ObjectData},
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, JsString, JsValue, BoaProfiler, Context, JsResult, JsString, JsValue,
@ -66,24 +66,18 @@ impl RegExpStringIterator {
// and fullUnicode and performs the following steps when called: // and fullUnicode and performs the following steps when called:
// 5. Return ! CreateIteratorFromClosure(closure, "%RegExpStringIteratorPrototype%", %RegExpStringIteratorPrototype%). // 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( let regexp_string_iterator = JsObject::from_proto_and_data(
matcher.clone(), context.iterator_prototypes().regexp_string_iterator(),
string, ObjectData::reg_exp_string_iterator(Self::new(
global, matcher.clone(),
unicode, string,
))); global,
regexp_string_iterator unicode,
.as_object() )),
.expect("regexp string iterator object") );
.set_prototype_instance(
context Ok(regexp_string_iterator.into())
.iterator_prototypes()
.regexp_string_iterator()
.into(),
);
Ok(regexp_string_iterator)
} }
pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
@ -161,13 +155,15 @@ impl RegExpStringIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object /// [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"); let _timer = BoaProfiler::global().start_event("RegExp String Iterator", "init");
// Create prototype // Create prototype
let result = context.construct_object(); let result = JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &result, 0, context); 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 = WellKnownSymbols::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()

17
boa/src/builtins/set/mod.rs

@ -15,7 +15,7 @@ use crate::{
context::StandardObjects, context::StandardObjects,
object::{ object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder, internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
ObjectData, JsObject, ObjectData,
}, },
property::{Attribute, PropertyNameKind}, property::{Attribute, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
@ -130,21 +130,16 @@ impl Set {
let prototype = let prototype =
get_prototype_from_constructor(new_target, StandardObjects::set_object, context)?; get_prototype_from_constructor(new_target, StandardObjects::set_object, context)?;
let obj = context.construct_object(); let obj = JsObject::from_proto_and_data(prototype, ObjectData::set(OrderedSet::default()));
obj.set_prototype_instance(prototype.into());
let set = JsValue::new(obj);
// 3
set.set_data(ObjectData::set(OrderedSet::default()));
let iterable = args.get_or_undefined(0); let iterable = args.get_or_undefined(0);
// 4 // 4
if iterable.is_null_or_undefined() { if iterable.is_null_or_undefined() {
return Ok(set); return Ok(obj.into());
} }
// 5 // 5
let adder = set.get_field("add", context)?; let adder = obj.get("add", context)?;
// 6 // 6
if !adder.is_function() { if !adder.is_function() {
@ -163,7 +158,7 @@ impl Set {
let next_value = next.value; let next_value = next.value;
// d, e // 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); return iterator_record.close(Err(status), context);
} }
@ -171,7 +166,7 @@ impl Set {
} }
// 8.b // 8.b
Ok(set) Ok(obj.into())
} }
/// `get Set [ @@species ]` /// `get Set [ @@species ]`

25
boa/src/builtins/set/set_iterator.rs

@ -1,8 +1,8 @@
use crate::{ use crate::{
builtins::iterable::create_iter_result_object,
builtins::Array, builtins::Array,
builtins::JsValue, 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}, property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, BoaProfiler, Context, JsResult,
@ -47,13 +47,11 @@ impl SetIterator {
kind: PropertyNameKind, kind: PropertyNameKind,
context: &Context, context: &Context,
) -> JsValue { ) -> JsValue {
let set_iterator = JsValue::new_object(context); let set_iterator = JsObject::from_proto_and_data(
set_iterator.set_data(ObjectData::set_iterator(Self::new(set, kind))); context.iterator_prototypes().set_iterator(),
set_iterator ObjectData::set_iterator(Self::new(set, kind)),
.as_object() );
.expect("set iterator object") set_iterator.into()
.set_prototype_instance(context.iterator_prototypes().set_iterator().into());
set_iterator
} }
/// %SetIteratorPrototype%.next( ) /// %SetIteratorPrototype%.next( )
@ -140,13 +138,16 @@ impl SetIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%-object /// [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"); let _timer = BoaProfiler::global().start_event(Self::NAME, "init");
// Create prototype // Create prototype
let set_iterator = context.construct_object(); let set_iterator =
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &set_iterator, 0, context); 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 = WellKnownSymbols::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()

4
boa/src/builtins/string/mod.rs

@ -205,9 +205,7 @@ impl String {
// 4. Set S.[[GetOwnProperty]] as specified in 10.4.3.1. // 4. Set S.[[GetOwnProperty]] as specified in 10.4.3.1.
// 5. Set S.[[DefineOwnProperty]] as specified in 10.4.3.2. // 5. Set S.[[DefineOwnProperty]] as specified in 10.4.3.2.
// 6. Set S.[[OwnPropertyKeys]] as specified in 10.4.3.3. // 6. Set S.[[OwnPropertyKeys]] as specified in 10.4.3.3.
let s = context.construct_object(); let s = JsObject::from_proto_and_data(prototype, ObjectData::string(value));
s.set_prototype_instance(prototype.into());
s.borrow_mut().data = ObjectData::string(value);
// 8. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor { [[Value]]: 𝔽(length), // 8. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor { [[Value]]: 𝔽(length),
// [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }). // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }).

27
boa/src/builtins/string/string_iterator.rs

@ -1,7 +1,9 @@
use crate::{ 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}, gc::{Finalize, Trace},
object::{function::make_builtin_fn, JsObject, ObjectData}, object::{JsObject, ObjectData},
property::PropertyDescriptor, property::PropertyDescriptor,
symbol::WellKnownSymbols, symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, JsValue, BoaProfiler, Context, JsResult, JsValue,
@ -22,13 +24,11 @@ impl StringIterator {
} }
pub fn create_string_iterator(string: JsValue, context: &mut Context) -> JsResult<JsValue> { pub fn create_string_iterator(string: JsValue, context: &mut Context) -> JsResult<JsValue> {
let string_iterator = JsValue::new_object(context); let string_iterator = JsObject::from_proto_and_data(
string_iterator.set_data(ObjectData::string_iterator(Self::new(string))); context.iterator_prototypes().string_iterator(),
string_iterator ObjectData::string_iterator(Self::new(string)),
.as_object() );
.expect("array iterator object") Ok(string_iterator.into())
.set_prototype_instance(context.iterator_prototypes().string_iterator().into());
Ok(string_iterator)
} }
pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> { pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
@ -76,13 +76,16 @@ impl StringIterator {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%-object /// [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"); let _timer = BoaProfiler::global().start_event("String Iterator", "init");
// Create prototype // Create prototype
let array_iterator = context.construct_object(); let array_iterator =
JsObject::from_proto_and_data(iterator_prototype, ObjectData::ordinary());
make_builtin_fn(Self::next, "next", &array_iterator, 0, context); 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 = WellKnownSymbols::to_string_tag();
let to_string_tag_property = PropertyDescriptor::builder() let to_string_tag_property = PropertyDescriptor::builder()

2
boa/src/bytecompiler.rs

@ -1,7 +1,7 @@
use gc::Gc; use gc::Gc;
use crate::{ use crate::{
object::function::ThisMode, builtins::function::ThisMode,
syntax::ast::{ syntax::ast::{
node::{Declaration, GetConstField, GetField, StatementList}, node::{Declaration, GetConstField, GetField, StatementList},
op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp},

13
boa/src/class.rs

@ -62,10 +62,8 @@
//! [class-trait]: ./trait.Class.html //! [class-trait]: ./trait.Class.html
use crate::{ use crate::{
object::{ builtins::function::NativeFunctionSignature,
function::NativeFunctionSignature, ConstructorBuilder, JsObject, NativeObject, ObjectData, object::{ConstructorBuilder, JsObject, NativeObject, ObjectData, PROTOTYPE},
PROTOTYPE,
},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
@ -142,9 +140,10 @@ impl<T: Class> ClassConstructor for T {
.unwrap_or(class_prototype); .unwrap_or(class_prototype);
let native_instance = Self::constructor(this, args, context)?; let native_instance = Self::constructor(this, args, context)?;
let object_instance = context.construct_object(); let object_instance = JsObject::from_proto_and_data(
object_instance.set_prototype_instance(prototype.into()); prototype,
object_instance.borrow_mut().data = ObjectData::native_object(Box::new(native_instance)); ObjectData::native_object(Box::new(native_instance)),
);
Ok(object_instance.into()) Ok(object_instance.into())
} }
} }

50
boa/src/context.rs

@ -1,13 +1,16 @@
//! Javascript context. //! Javascript context.
use crate::{ use crate::{
builtins::{self, iterable::IteratorPrototypes, typed_array::TypedArray}, builtins::{
class::{Class, ClassBuilder}, self,
exec::Interpreter,
object::{
function::{Function, NativeFunctionSignature, ThisMode}, function::{Function, NativeFunctionSignature, ThisMode},
FunctionBuilder, JsObject, Object, PROTOTYPE, iterable::IteratorPrototypes,
typed_array::TypedArray,
}, },
class::{Class, ClassBuilder},
exec::Interpreter,
object::PROTOTYPE,
object::{FunctionBuilder, JsObject, ObjectData},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm, realm::Realm,
syntax::{ syntax::{
@ -39,18 +42,18 @@ pub struct StandardConstructor {
impl Default for StandardConstructor { impl Default for StandardConstructor {
fn default() -> Self { fn default() -> Self {
Self { Self {
constructor: JsObject::new(Object::default()), constructor: JsObject::empty(),
prototype: JsObject::new(Object::default()), prototype: JsObject::empty(),
} }
} }
} }
impl StandardConstructor { impl StandardConstructor {
/// Build a constructor with a defined prototype. /// Build a constructor with a defined prototype.
fn with_prototype(prototype: Object) -> Self { fn with_prototype(prototype: JsObject) -> Self {
Self { Self {
constructor: JsObject::new(Object::default()), constructor: JsObject::empty(),
prototype: JsObject::new(prototype), prototype,
} }
} }
@ -114,9 +117,18 @@ impl Default for StandardObjects {
function: StandardConstructor::default(), function: StandardConstructor::default(),
array: StandardConstructor::default(), array: StandardConstructor::default(),
bigint: StandardConstructor::default(), bigint: StandardConstructor::default(),
number: StandardConstructor::with_prototype(Object::number(0.0)), number: StandardConstructor::with_prototype(JsObject::from_proto_and_data(
boolean: StandardConstructor::with_prototype(Object::boolean(false)), None,
string: StandardConstructor::with_prototype(Object::string("")), 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(), regexp: StandardConstructor::default(),
symbol: StandardConstructor::default(), symbol: StandardConstructor::default(),
error: StandardConstructor::default(), error: StandardConstructor::default(),
@ -484,8 +496,10 @@ impl Context {
/// Constructs an object with the `%Object.prototype%` prototype. /// Constructs an object with the `%Object.prototype%` prototype.
#[inline] #[inline]
pub fn construct_object(&self) -> JsObject { pub fn construct_object(&self) -> JsObject {
let object_prototype: JsValue = self.standard_objects().object_object().prototype().into(); JsObject::from_proto_and_data(
JsObject::new(Object::create(object_prototype)) self.standard_objects().object_object().prototype(),
ObjectData::ordinary(),
)
} }
/// <https://tc39.es/ecma262/#sec-call> /// <https://tc39.es/ecma262/#sec-call>
@ -682,8 +696,7 @@ impl Context {
P: Into<Box<[FormalParameter]>>, P: Into<Box<[FormalParameter]>>,
{ {
let name = name.into(); let name = name.into();
let function_prototype: JsValue = let function_prototype = self.standard_objects().function_object().prototype();
self.standard_objects().function_object().prototype().into();
// Every new function has a prototype property pre-made // Every new function has a prototype property pre-made
let prototype = self.construct_object(); let prototype = self.construct_object();
@ -703,7 +716,8 @@ impl Context {
environment: self.get_current_environment().clone(), environment: self.get_current_environment().clone(),
}; };
let function = JsObject::new(Object::function(func, function_prototype)); let function =
JsObject::from_proto_and_data(function_prototype, ObjectData::function(func));
// Set constructor field to the newly created Value (function object) // Set constructor field to the newly created Value (function object)
let constructor = PropertyDescriptor::builder() let constructor = PropertyDescriptor::builder()

376
boa/src/object/function.rs

@ -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<JsValue>;
// Allows restricting closures to only `Copy` ones.
// Used the sealed pattern to disallow external implementations
// of `DynCopy`.
mod sealed {
pub trait Sealed {}
impl<T: Copy> Sealed for T {}
}
pub trait DynCopy: sealed::Sealed {}
impl<T: Copy> 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<JsValue> + DynCopy + DynClone + 'static
{
}
// The `Copy` bound automatically infers `DynCopy` and `DynClone`
impl<T> ClosureFunctionSignature for T where
T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult<JsValue> + Copy + 'static
{
}
// Allows cloning Box<dyn ClosureFunctionSignature>
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<dyn CapturesObject>`.
trait CapturesObject: NativeObject + DynClone {}
impl<T: NativeObject + Clone> CapturesObject for T {}
dyn_clone::clone_trait_object!(CapturesObject);
/// Wrapper for `Box<dyn NativeObject + Clone>` 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<dyn CapturesObject>);
impl Captures {
/// Creates a new capture context.
pub(crate) fn new<T>(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<T>(&self) -> Option<&T>
where
T: NativeObject + Clone,
{
self.0.deref().as_any().downcast_ref::<T>()
}
/// Mutably downcasts `Captures` to the specified type, returning a
/// mutable reference to the downcasted type if successful or `None` otherwise.
pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: NativeObject + Clone,
{
self.0.deref_mut().as_mut_any().downcast_mut::<T>()
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or a `TypeError` otherwise.
pub fn try_downcast_ref<T>(&self, context: &mut Context) -> JsResult<&T>
where
T: NativeObject + Clone,
{
self.0
.deref()
.as_any()
.downcast_ref::<T>()
.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<T>(&mut self, context: &mut Context) -> JsResult<&mut T>
where
T: NativeObject + Clone,
{
self.0
.deref_mut()
.as_mut_any()
.downcast_mut::<T>()
.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)
///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Clone, Trace, Finalize)]
pub enum Function {
Native {
#[unsafe_ignore_trace]
function: NativeFunctionSignature,
constructable: bool,
},
Closure {
#[unsafe_ignore_trace]
function: Box<dyn ClosureFunctionSignature>,
constructable: bool,
captures: Captures,
},
Ordinary {
constructable: bool,
this_mode: ThisMode,
body: RcStatementList,
params: Box<[FormalParameter]>,
environment: Environment,
},
#[cfg(feature = "vm")]
VmOrdinary {
code: gc::Gc<crate::vm::CodeBlock>,
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.
///
/// <https://tc39.es/ecma262/#sec-createunmappedargumentsobject>
pub fn create_unmapped_arguments_object(
arguments_list: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
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 <https://tc39.es/ecma262/#sec-function-instances-length>, 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<N>(
function: NativeFunctionSignature,
name: N,
parent: &JsObject,
length: usize,
interpreter: &Context,
) where
N: Into<String>,
{
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),
);
}

57
boa/src/object/jsobject.rs

@ -20,19 +20,16 @@ use std::{
#[cfg(not(feature = "vm"))] #[cfg(not(feature = "vm"))]
use crate::{ use crate::{
builtins::function::{
create_unmapped_arguments_object, Captures, ClosureFunctionSignature, Function,
NativeFunctionSignature,
},
environment::{ environment::{
environment_record_trait::EnvironmentRecordTrait, environment_record_trait::EnvironmentRecordTrait,
function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::Environment, lexical_environment::Environment,
}, },
exec::InterpreterState, exec::InterpreterState,
object::{
function::{
create_unmapped_arguments_object, Captures, ClosureFunctionSignature, Function,
NativeFunctionSignature,
},
PROTOTYPE,
},
syntax::ast::node::RcStatementList, syntax::ast::node::RcStatementList,
Executable, Executable,
}; };
@ -63,12 +60,32 @@ enum FunctionBody {
} }
impl JsObject { impl JsObject {
/// Create a new `GcObject` from a `Object`. /// Create a new `JsObject` from an internal `Object`.
#[inline] #[inline]
pub fn new(object: Object) -> Self { fn from_object(object: Object) -> Self {
Self(Gc::new(GcCell::new(object))) 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<O: Into<Option<JsObject>>>(prototype: O, data: ObjectData) -> Self {
Self::from_object(Object {
data,
prototype: prototype.into().map_or(JsValue::Null, JsValue::new),
extensible: true,
properties: Default::default(),
})
}
/// Immutably borrows the `Object`. /// Immutably borrows the `Object`.
/// ///
/// The borrow lasts until the returned `Ref` exits scope. /// The borrow lasts until the returned `Ref` exits scope.
@ -143,6 +160,10 @@ impl JsObject {
context: &mut Context, context: &mut Context,
construct: bool, construct: bool,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
use crate::context::StandardObjects;
use super::internal_methods::get_prototype_from_constructor;
let this_function_object = self.clone(); let this_function_object = self.clone();
let mut has_parameter_expressions = false; let mut has_parameter_expressions = false;
@ -180,21 +201,13 @@ impl JsObject {
// prototype as prototype for the new object // prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor> // see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor> // see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let proto = this_target.as_object().unwrap().__get__( let proto = get_prototype_from_constructor(
&PROTOTYPE.into(), this_target,
this_target.clone(), StandardObjects::object_object,
context, context,
)?; )?;
let proto = if proto.is_object() { JsObject::from_proto_and_data(Some(proto), ObjectData::ordinary())
proto .into()
} else {
context
.standard_objects()
.object_object()
.prototype()
.into()
};
JsValue::new(Object::create(proto))
} else { } else {
this_target.clone() this_target.clone()
}; };

134
boa/src/object/mod.rs

@ -2,17 +2,22 @@
use crate::{ use crate::{
builtins::{ builtins::{
array::array_iterator::ArrayIterator, array_buffer::ArrayBuffer, array::array_iterator::ArrayIterator,
map::map_iterator::MapIterator, map::ordered_map::OrderedMap, array_buffer::ArrayBuffer,
regexp::regexp_string_iterator::RegExpStringIterator, set::ordered_set::OrderedSet, function::{Captures, Function, NativeFunctionSignature},
set::set_iterator::SetIterator, string::string_iterator::StringIterator, map::map_iterator::MapIterator,
typed_array::integer_indexed_object::IntegerIndexed, Date, RegExp, map::ordered_map::OrderedMap,
regexp::regexp_string_iterator::RegExpStringIterator,
set::ordered_set::OrderedSet,
set::set_iterator::SetIterator,
string::string_iterator::StringIterator,
typed_array::integer_indexed_object::IntegerIndexed,
Date, RegExp,
}, },
context::StandardConstructor, context::StandardConstructor,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::function::{Captures, Function, NativeFunctionSignature},
property::{Attribute, PropertyDescriptor, PropertyKey}, property::{Attribute, PropertyDescriptor, PropertyKey},
BoaProfiler, Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue, Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue,
}; };
use std::{ use std::{
any::Any, any::Any,
@ -23,7 +28,6 @@ use std::{
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
pub mod function;
pub(crate) mod internal_methods; pub(crate) mod internal_methods;
mod jsobject; mod jsobject;
mod operations; mod operations;
@ -352,98 +356,6 @@ impl Default for Object {
} }
impl 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,
}
}
/// `OrdinaryObjectCreate` is used to specify the runtime creation of new ordinary objects.
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate
#[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<S>(value: S) -> Self
where
S: Into<JsString>,
{
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<T>(value: T) -> Self
where
T: NativeObject,
{
Self {
data: ObjectData::native_object(Box::new(value)),
properties: PropertyMap::default(),
prototype: JsValue::null(),
extensible: true,
}
}
#[inline] #[inline]
pub fn kind(&self) -> &ObjectKind { pub fn kind(&self) -> &ObjectKind {
&self.data.kind &self.data.kind
@ -1007,15 +919,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`. /// Returns `true` if it holds an Rust type that implements `NativeObject`.
#[inline] #[inline]
pub fn is_native_object(&self) -> bool { pub fn is_native_object(&self) -> bool {
@ -1300,13 +1203,12 @@ impl<'context> FunctionBuilder<'context> {
/// Build the function object. /// Build the function object.
#[inline] #[inline]
pub fn build(&mut self) -> JsObject { pub fn build(&mut self) -> JsObject {
let mut function = Object::function( let function = JsObject::from_proto_and_data(
self.function.take().unwrap(),
self.context self.context
.standard_objects() .standard_objects()
.function_object() .function_object()
.prototype() .prototype(),
.into(), ObjectData::function(self.function.take().unwrap()),
); );
let property = PropertyDescriptor::builder() let property = PropertyDescriptor::builder()
.writable(false) .writable(false)
@ -1315,7 +1217,7 @@ impl<'context> FunctionBuilder<'context> {
function.insert_property("name", property.clone().value(self.name.clone())); function.insert_property("name", property.clone().value(self.name.clone()));
function.insert_property("length", property.value(self.length)); function.insert_property("length", property.value(self.length));
JsObject::new(function) function
} }
/// Initializes the `Function.prototype` function object. /// Initializes the `Function.prototype` function object.
@ -1470,8 +1372,8 @@ impl<'context> ConstructorBuilder<'context> {
Self { Self {
context, context,
constructor_function: constructor, constructor_function: constructor,
constructor_object: JsObject::new(Object::default()), constructor_object: JsObject::empty(),
prototype: JsObject::new(Object::default()), prototype: JsObject::empty(),
length: 0, length: 0,
name: JsString::default(), name: JsString::default(),
callable: true, callable: true,

8
boa/src/realm.rs

@ -8,7 +8,7 @@ use crate::{
environment::{ environment::{
global_environment_record::GlobalEnvironmentRecord, lexical_environment::LexicalEnvironment, global_environment_record::GlobalEnvironmentRecord, lexical_environment::LexicalEnvironment,
}, },
object::{JsObject, Object, ObjectData}, object::{JsObject, ObjectData},
BoaProfiler, BoaProfiler,
}; };
use gc::Gc; use gc::Gc;
@ -29,12 +29,8 @@ impl Realm {
let _timer = BoaProfiler::global().start_event("Realm::create", "realm"); let _timer = BoaProfiler::global().start_event("Realm::create", "realm");
// Create brand new global object // Create brand new global object
// Global has no prototype to pass None to new_obj // Global has no prototype to pass None to new_obj
let mut global = Object::default();
// Allow identification of the global object easily // Allow identification of the global object easily
global.data = ObjectData::global(); let gc_global = JsObject::from_proto_and_data(None, ObjectData::global());
let gc_global = JsObject::new(global);
// We need to clone the global here because its referenced from separate places (only pointer is cloned) // 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()); let global_env = GlobalEnvironmentRecord::new(gc_global.clone(), gc_global.clone());

2
boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs

@ -1,7 +1,7 @@
use crate::{ use crate::{
builtins::function::ThisMode,
exec::Executable, exec::Executable,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::function::ThisMode,
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };

2
boa/src/syntax/ast/node/declaration/function_decl/mod.rs

@ -1,8 +1,8 @@
use crate::{ use crate::{
builtins::function::ThisMode,
environment::lexical_environment::VariableScope, environment::lexical_environment::VariableScope,
exec::Executable, exec::Executable,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::function::ThisMode,
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
BoaProfiler, Context, JsResult, JsValue, BoaProfiler, Context, JsResult, JsValue,
}; };

2
boa/src/syntax/ast/node/declaration/function_expr/mod.rs

@ -1,7 +1,7 @@
use crate::{ use crate::{
builtins::function::ThisMode,
exec::Executable, exec::Executable,
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::function::ThisMode,
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList}, syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };

46
boa/src/syntax/ast/node/object/mod.rs

@ -89,7 +89,7 @@ impl Object {
impl Executable for Object { impl Executable for Object {
fn run(&self, context: &mut Context) -> JsResult<JsValue> { fn run(&self, context: &mut Context) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("object", "exec"); 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. // TODO: Implement the rest of the property types.
for property in self.properties().iter() { for property in self.properties().iter() {
@ -101,14 +101,16 @@ impl Executable for Object {
node.run(context)?.to_property_key(context)? node.run(context)?.to_property_key(context)?
} }
}; };
obj.set_property( obj.__define_own_property__(
name, name,
PropertyDescriptor::builder() PropertyDescriptor::builder()
.value(value.run(context)?) .value(value.run(context)?)
.writable(true) .writable(true)
.enumerable(true) .enumerable(true)
.configurable(true), .configurable(true)
); .build(),
context,
)?;
} }
PropertyDefinition::MethodDefinition(kind, name, func) => { PropertyDefinition::MethodDefinition(kind, name, func) => {
let name = match name { let name = match name {
@ -119,44 +121,50 @@ impl Executable for Object {
}; };
match kind { match kind {
MethodDefinitionKind::Ordinary => { MethodDefinitionKind::Ordinary => {
obj.set_property( obj.__define_own_property__(
name, name,
PropertyDescriptor::builder() PropertyDescriptor::builder()
.value(func.run(context)?) .value(func.run(context)?)
.writable(true) .writable(true)
.enumerable(true) .enumerable(true)
.configurable(true), .configurable(true)
); .build(),
context,
)?;
} }
MethodDefinitionKind::Get => { MethodDefinitionKind::Get => {
let set = obj let set = obj
.get_property(name.clone()) .__get_own_property__(&name, context)?
.as_ref() .as_ref()
.and_then(|a| a.set()) .and_then(|a| a.set())
.cloned(); .cloned();
obj.set_property( obj.__define_own_property__(
name, name,
PropertyDescriptor::builder() PropertyDescriptor::builder()
.maybe_get(func.run(context)?.as_object()) .maybe_get(func.run(context)?.as_object())
.maybe_set(set) .maybe_set(set)
.enumerable(true) .enumerable(true)
.configurable(true), .configurable(true)
) .build(),
context,
)?;
} }
MethodDefinitionKind::Set => { MethodDefinitionKind::Set => {
let get = obj let get = obj
.get_property(name.clone()) .__get_own_property__(&name, context)?
.as_ref() .as_ref()
.and_then(|a| a.get()) .and_then(|a| a.get())
.cloned(); .cloned();
obj.set_property( obj.__define_own_property__(
name, name,
PropertyDescriptor::builder() PropertyDescriptor::builder()
.maybe_get(get) .maybe_get(get)
.maybe_set(func.run(context)?.as_object()) .maybe_set(func.run(context)?.as_object())
.enumerable(true) .enumerable(true)
.configurable(true), .configurable(true)
) .build(),
context,
)?;
} }
} }
} }
@ -168,17 +176,13 @@ impl Executable for Object {
continue; continue;
} }
obj.as_object().unwrap().copy_data_properties::<String>( obj.copy_data_properties::<String>(&val, vec![], context)?;
&val,
vec![],
context,
)?;
} }
_ => {} // unimplemented!("{:?} type of property", i), _ => {} // unimplemented!("{:?} type of property", i),
} }
} }
Ok(obj) Ok(obj.into())
} }
} }

48
boa/src/value/conversions.rs

@ -120,54 +120,6 @@ impl From<bool> for JsValue {
} }
} }
impl<T> From<&[T]> for JsValue
where
T: Clone + Into<JsValue>,
{
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<T> From<Vec<T>> for JsValue
where
T: Into<JsValue>,
{
fn from(value: Vec<T>) -> 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<Object> for JsValue {
#[inline]
fn from(object: Object) -> Self {
let _timer = BoaProfiler::global().start_event("From<Object>", "value");
JsValue::Object(JsObject::new(object))
}
}
impl From<JsObject> for JsValue { impl From<JsObject> for JsValue {
#[inline] #[inline]
fn from(object: JsObject) -> Self { fn from(object: JsObject) -> Self {

48
boa/src/value/mod.rs

@ -10,7 +10,7 @@ use crate::{
number::{f64_to_int32, f64_to_uint32}, number::{f64_to_int32, f64_to_uint32},
Number, Number,
}, },
object::{JsObject, Object, ObjectData}, object::{JsObject, ObjectData},
property::{PropertyDescriptor, PropertyKey}, property::{PropertyDescriptor, PropertyKey},
symbol::{JsSymbol, WellKnownSymbols}, symbol::{JsSymbol, WellKnownSymbols},
BoaProfiler, Context, JsBigInt, JsResult, JsString, BoaProfiler, Context, JsBigInt, JsResult, JsString,
@ -113,22 +113,16 @@ impl JsValue {
/// Creates a new number with `Infinity` value. /// Creates a new number with `Infinity` value.
#[inline] #[inline]
pub fn positive_inifnity() -> Self { pub fn positive_infinity() -> Self {
Self::Rational(f64::INFINITY) Self::Rational(f64::INFINITY)
} }
/// Creates a new number with `-Infinity` value. /// Creates a new number with `-Infinity` value.
#[inline] #[inline]
pub fn negative_inifnity() -> Self { pub fn negative_infinity() -> Self {
Self::Rational(f64::NEG_INFINITY) Self::Rational(f64::NEG_INFINITY)
} }
/// Returns a new empty object with the `%Object.prototype%` prototype.
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 /// Returns true if the value is an object
#[inline] #[inline]
pub fn is_object(&self) -> bool { pub fn is_object(&self) -> bool {
@ -526,32 +520,30 @@ impl JsValue {
} }
JsValue::Boolean(boolean) => { JsValue::Boolean(boolean) => {
let prototype = context.standard_objects().boolean_object().prototype(); let prototype = context.standard_objects().boolean_object().prototype();
Ok(JsObject::new(Object::with_prototype( Ok(JsObject::from_proto_and_data(
prototype.into(), prototype,
ObjectData::boolean(*boolean), ObjectData::boolean(*boolean),
))) ))
} }
JsValue::Integer(integer) => { JsValue::Integer(integer) => {
let prototype = context.standard_objects().number_object().prototype(); let prototype = context.standard_objects().number_object().prototype();
Ok(JsObject::new(Object::with_prototype( Ok(JsObject::from_proto_and_data(
prototype.into(), prototype,
ObjectData::number(f64::from(*integer)), ObjectData::number(f64::from(*integer)),
))) ))
} }
JsValue::Rational(rational) => { JsValue::Rational(rational) => {
let prototype = context.standard_objects().number_object().prototype(); let prototype = context.standard_objects().number_object().prototype();
Ok(JsObject::new(Object::with_prototype( Ok(JsObject::from_proto_and_data(
prototype.into(), prototype,
ObjectData::number(*rational), ObjectData::number(*rational),
))) ))
} }
JsValue::String(ref string) => { JsValue::String(ref string) => {
let prototype = context.standard_objects().string_object().prototype(); let prototype = context.standard_objects().string_object().prototype();
let object = JsObject::new(Object::with_prototype( let object =
prototype.into(), JsObject::from_proto_and_data(prototype, ObjectData::string(string.clone()));
ObjectData::string(string.clone()),
));
// Make sure the correct length is set on our new string object // Make sure the correct length is set on our new string object
object.insert_property( object.insert_property(
"length", "length",
@ -565,17 +557,17 @@ impl JsValue {
} }
JsValue::Symbol(ref symbol) => { JsValue::Symbol(ref symbol) => {
let prototype = context.standard_objects().symbol_object().prototype(); let prototype = context.standard_objects().symbol_object().prototype();
Ok(JsObject::new(Object::with_prototype( Ok(JsObject::from_proto_and_data(
prototype.into(), prototype,
ObjectData::symbol(symbol.clone()), ObjectData::symbol(symbol.clone()),
))) ))
} }
JsValue::BigInt(ref bigint) => { JsValue::BigInt(ref bigint) => {
let prototype = context.standard_objects().bigint_object().prototype(); let prototype = context.standard_objects().bigint_object().prototype();
Ok(JsObject::new(Object::with_prototype( Ok(JsObject::from_proto_and_data(
prototype.into(), prototype,
ObjectData::big_int(bigint.clone()), ObjectData::big_int(bigint.clone()),
))) ))
} }
JsValue::Object(jsobject) => Ok(jsobject.clone()), JsValue::Object(jsobject) => Ok(jsobject.clone()),
} }

20
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::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
#[test]
fn is_object() {
let context = Context::new();
let val = JsValue::new_object(&context);
assert!(val.is_object());
}
#[test] #[test]
fn string_to_value() { fn string_to_value() {
let s = String::from("Hello"); let s = String::from("Hello");
@ -31,15 +24,12 @@ fn undefined() {
#[test] #[test]
fn get_set_field() { fn get_set_field() {
let mut context = Context::new(); let mut context = Context::new();
let obj = JsValue::new_object(&context); let obj = &context.construct_object();
// Create string and convert it to a Value // Create string and convert it to a Value
let s = JsValue::new("bar"); let s = JsValue::new("bar");
obj.set_field("foo", s, false, &mut context).unwrap(); obj.set("foo", s, false, &mut context).unwrap();
assert_eq!( assert_eq!(
obj.get_field("foo", &mut context) obj.get("foo", &mut context).unwrap().display().to_string(),
.unwrap()
.display()
.to_string(),
"\"bar\"" "\"bar\""
); );
} }
@ -142,11 +132,11 @@ fn hash_rational() {
#[test] #[test]
#[allow(clippy::eq_op)] #[allow(clippy::eq_op)]
fn hash_object() { fn hash_object() {
let object1 = JsValue::new(Object::default()); let object1 = JsValue::new(JsObject::empty());
assert_eq!(object1, object1); assert_eq!(object1, object1);
assert_eq!(object1, object1.clone()); assert_eq!(object1, object1.clone());
let object2 = JsValue::new(Object::default()); let object2 = JsValue::new(JsObject::empty());
assert_ne!(object1, object2); assert_ne!(object1, object2);
assert_eq!(hash_value(&object1), hash_value(&object1.clone())); assert_eq!(hash_value(&object1), hash_value(&object1.clone()));

33
boa/src/vm/code_block.rs

@ -1,15 +1,14 @@
use crate::{ use crate::{
builtins::function::{
Captures, ClosureFunctionSignature, Function, NativeFunctionSignature, ThisMode,
},
context::StandardObjects,
environment::{ environment::{
function_environment_record::{BindingStatus, FunctionEnvironmentRecord}, function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::Environment, lexical_environment::Environment,
}, },
gc::{Finalize, Trace}, gc::{Finalize, Trace},
object::{ object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
function::{
Captures, ClosureFunctionSignature, Function, NativeFunctionSignature, ThisMode,
},
JsObject, Object, PROTOTYPE,
},
property::PropertyDescriptor, property::PropertyDescriptor,
syntax::ast::node::FormalParameter, syntax::ast::node::FormalParameter,
vm::Opcode, vm::Opcode,
@ -311,7 +310,8 @@ impl JsVmFunction {
let function = Function::VmOrdinary { code, environment }; let function = Function::VmOrdinary { code, environment };
let constructor = JsObject::new(Object::function(function, function_prototype.into())); let constructor =
JsObject::from_proto_and_data(function_prototype, ObjectData::function(function));
let constructor_property = PropertyDescriptor::builder() let constructor_property = PropertyDescriptor::builder()
.value(constructor.clone()) .value(constructor.clone())
@ -515,26 +515,17 @@ impl JsObject {
(function)(this_target, args, captures, context) (function)(this_target, args, captures, context)
} }
FunctionBody::Ordinary { code, environment } => { FunctionBody::Ordinary { code, environment } => {
let this = { let this: JsValue = {
// If the prototype of the constructor is not an object, then use the default object // If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object // prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor> // see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor> // see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let proto = this_target.as_object().unwrap().__get__( let prototype = get_prototype_from_constructor(
&PROTOTYPE.into(), this_target,
this_target.clone(), StandardObjects::object_object,
context, context,
)?; )?;
let proto = if proto.is_object() { JsObject::from_proto_and_data(prototype, ObjectData::ordinary()).into()
proto
} else {
context
.standard_objects()
.object_object()
.prototype()
.into()
};
JsValue::from(Object::create(proto))
}; };
let lexical_this_mode = code.this_mode == ThisMode::Lexical; let lexical_this_mode = code.this_mode == ThisMode::Lexical;

9
boa/src/vm/mod.rs

@ -155,22 +155,21 @@ impl Context {
self.vm.push(value); self.vm.push(value);
} }
Opcode::PushNaN => self.vm.push(JsValue::nan()), Opcode::PushNaN => self.vm.push(JsValue::nan()),
Opcode::PushPositiveInfinity => self.vm.push(JsValue::positive_inifnity()), Opcode::PushPositiveInfinity => self.vm.push(JsValue::positive_infinity()),
Opcode::PushNegativeInfinity => self.vm.push(JsValue::negative_inifnity()), Opcode::PushNegativeInfinity => self.vm.push(JsValue::negative_infinity()),
Opcode::PushLiteral => { Opcode::PushLiteral => {
let index = self.vm.read::<u32>() as usize; let index = self.vm.read::<u32>() as usize;
let value = self.vm.frame().code.literals[index].clone(); let value = self.vm.frame().code.literals[index].clone();
self.vm.push(value) self.vm.push(value)
} }
Opcode::PushEmptyObject => self.vm.push(JsValue::new_object(self)), Opcode::PushEmptyObject => self.vm.push(self.construct_object()),
Opcode::PushNewArray => { Opcode::PushNewArray => {
let count = self.vm.read::<u32>(); let count = self.vm.read::<u32>();
let mut elements = Vec::with_capacity(count as usize); let mut elements = Vec::with_capacity(count as usize);
for _ in 0..count { for _ in 0..count {
elements.push(self.vm.pop()); elements.push(self.vm.pop());
} }
let array = Array::new_array(self); let array = Array::create_array_from_list(elements, self);
Array::add_to_array_object(&array, &elements, self)?;
self.vm.push(array); self.vm.push(array);
} }
Opcode::Add => bin_op!(add), Opcode::Add => bin_op!(add),

Loading…
Cancel
Save