From 0456990f005556ec989d1d80e8536ac7be8624fb Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 04:01:43 -0600 Subject: [PATCH 01/15] Enable unsized types on the GC --- boa_gc/src/internals/ephemeron_box.rs | 8 ++--- boa_gc/src/internals/weak_map_box.rs | 4 +-- boa_gc/src/lib.rs | 4 +-- boa_gc/src/pointers/ephemeron.rs | 14 ++++----- boa_gc/src/pointers/weak.rs | 4 +-- boa_gc/src/pointers/weak_map.rs | 44 +++++++++++++++------------ 6 files changed, 42 insertions(+), 36 deletions(-) diff --git a/boa_gc/src/internals/ephemeron_box.rs b/boa_gc/src/internals/ephemeron_box.rs index 87225f95ed8..f58d1633c45 100644 --- a/boa_gc/src/internals/ephemeron_box.rs +++ b/boa_gc/src/internals/ephemeron_box.rs @@ -87,17 +87,17 @@ impl core::fmt::Debug for EphemeronBoxHeader { } /// The inner allocation of an [`Ephemeron`][crate::Ephemeron] pointer. -pub(crate) struct EphemeronBox { +pub(crate) struct EphemeronBox { pub(crate) header: EphemeronBoxHeader, data: UnsafeCell>>, } -struct Data { +struct Data { key: NonNull>, value: V, } -impl EphemeronBox { +impl EphemeronBox { /// Creates a new `EphemeronBox` that tracks `key` and has `value` as its inner data. pub(crate) fn new(key: &Gc, value: V) -> Self { Self { @@ -221,7 +221,7 @@ pub(crate) trait ErasedEphemeronBox { fn finalize_and_clear(&self); } -impl ErasedEphemeronBox for EphemeronBox { +impl ErasedEphemeronBox for EphemeronBox { fn header(&self) -> &EphemeronBoxHeader { &self.header } diff --git a/boa_gc/src/internals/weak_map_box.rs b/boa_gc/src/internals/weak_map_box.rs index e07fe1fa7d2..3d7be191300 100644 --- a/boa_gc/src/internals/weak_map_box.rs +++ b/boa_gc/src/internals/weak_map_box.rs @@ -1,7 +1,7 @@ use crate::{pointers::RawWeakMap, GcRefCell, Trace, WeakGc}; /// A box that is used to track [`WeakMap`][`crate::WeakMap`]s. -pub(crate) struct WeakMapBox { +pub(crate) struct WeakMapBox { pub(crate) map: WeakGc>>, } @@ -17,7 +17,7 @@ pub(crate) trait ErasedWeakMapBox { unsafe fn trace(&self); } -impl ErasedWeakMapBox for WeakMapBox { +impl ErasedWeakMapBox for WeakMapBox { fn clear_dead_entries(&self) { if let Some(map) = self.map.upgrade() { if let Ok(mut map) = map.try_borrow_mut() { diff --git a/boa_gc/src/lib.rs b/boa_gc/src/lib.rs index b110c916636..0be00ade7c9 100644 --- a/boa_gc/src/lib.rs +++ b/boa_gc/src/lib.rs @@ -144,7 +144,7 @@ impl Allocator { }) } - fn alloc_ephemeron( + fn alloc_ephemeron( value: EphemeronBox, ) -> NonNull> { let _timer = Profiler::global().start_event("New EphemeronBox", "BoaAlloc"); @@ -164,7 +164,7 @@ impl Allocator { }) } - fn alloc_weak_map() -> WeakMap { + fn alloc_weak_map() -> WeakMap { let _timer = Profiler::global().start_event("New WeakMap", "BoaAlloc"); let weak_map = WeakMap { diff --git a/boa_gc/src/pointers/ephemeron.rs b/boa_gc/src/pointers/ephemeron.rs index e687ab53ad4..592148d8602 100644 --- a/boa_gc/src/pointers/ephemeron.rs +++ b/boa_gc/src/pointers/ephemeron.rs @@ -17,11 +17,11 @@ use std::ptr::NonNull; /// [eph]: https://docs.racket-lang.org/reference/ephemerons.html /// [acm]: https://dl.acm.org/doi/10.1145/263700.263733 #[derive(Debug)] -pub struct Ephemeron { +pub struct Ephemeron { inner_ptr: NonNull>, } -impl Ephemeron { +impl Ephemeron { /// Gets the stored value of this `Ephemeron`, or `None` if the key was already garbage collected. /// /// This needs to return a clone of the value because holding a reference to it between @@ -59,7 +59,7 @@ impl Ephemeron { } } -impl Ephemeron { +impl Ephemeron { /// Creates a new `Ephemeron`. #[must_use] pub fn new(key: &Gc, value: V) -> Self { @@ -95,7 +95,7 @@ impl Ephemeron { } } -impl Finalize for Ephemeron { +impl Finalize for Ephemeron { fn finalize(&self) { // SAFETY: inner_ptr should be alive when calling finalize. // We don't call inner_ptr() to avoid overhead of calling finalizer_safe(). @@ -107,7 +107,7 @@ impl Finalize for Ephemeron { // SAFETY: `Ephemeron`s trace implementation only marks its inner box because we want to stop // tracing through weakly held pointers. -unsafe impl Trace for Ephemeron { +unsafe impl Trace for Ephemeron { unsafe fn trace(&self) { // SAFETY: We need to mark the inner box of the `Ephemeron` since it is reachable // from a root and this means it cannot be dropped. @@ -125,7 +125,7 @@ unsafe impl Trace for Ephemeron { } } -impl Clone for Ephemeron { +impl Clone for Ephemeron { fn clone(&self) -> Self { let ptr = self.inner_ptr(); self.inner().inc_ref_count(); @@ -134,7 +134,7 @@ impl Clone for Ephemeron { } } -impl Drop for Ephemeron { +impl Drop for Ephemeron { fn drop(&mut self) { if finalizer_safe() { Finalize::finalize(self); diff --git a/boa_gc/src/pointers/weak.rs b/boa_gc/src/pointers/weak.rs index 179d394c10a..4cd38a8f489 100644 --- a/boa_gc/src/pointers/weak.rs +++ b/boa_gc/src/pointers/weak.rs @@ -7,11 +7,11 @@ use std::hash::{Hash, Hasher}; /// garbage collections. However, this also means [`WeakGc::upgrade`] could return `None` at any moment. #[derive(Debug, Trace, Finalize)] #[repr(transparent)] -pub struct WeakGc { +pub struct WeakGc { inner: Ephemeron, } -impl WeakGc { +impl WeakGc { /// Creates a new weak pointer for a garbage collected value. #[inline] #[must_use] diff --git a/boa_gc/src/pointers/weak_map.rs b/boa_gc/src/pointers/weak_map.rs index a4d0d0414b8..968cbfdfadb 100644 --- a/boa_gc/src/pointers/weak_map.rs +++ b/boa_gc/src/pointers/weak_map.rs @@ -11,12 +11,18 @@ use crate::{custom_trace, Allocator, Ephemeron, Finalize, Gc, GcRefCell, Trace}; use std::{fmt, hash::BuildHasher, marker::PhantomData, mem}; /// A map that holds weak references to its keys and is traced by the garbage collector. -#[derive(Clone, Debug, Default, Trace, Finalize)] -pub struct WeakMap { +#[derive(Clone, Debug, Default, Finalize)] +pub struct WeakMap { pub(crate) inner: Gc>>, } -impl WeakMap { +unsafe impl Trace for WeakMap { + custom_trace!(this, { + mark(&this.inner); + }); +} + +impl WeakMap { /// Creates a new `WeakMap`. #[must_use] #[inline] @@ -58,7 +64,7 @@ impl WeakMap { /// held. pub(crate) struct RawWeakMap where - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { hash_builder: S, @@ -67,7 +73,7 @@ where impl Finalize for RawWeakMap where - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { } @@ -75,7 +81,7 @@ where // SAFETY: The implementation correctly marks all ephemerons inside the map. unsafe impl Trace for RawWeakMap where - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { custom_trace!(this, { @@ -88,7 +94,7 @@ where impl Default for RawWeakMap where S: Default, - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { fn default() -> Self { @@ -98,7 +104,7 @@ where impl RawWeakMap where - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { /// Creates an empty `RawWeakMap`. @@ -121,7 +127,7 @@ where impl RawWeakMap where - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { /// Creates an empty `RawWeakMap` which will use the given hash builder to hash @@ -226,7 +232,7 @@ where impl RawWeakMap where - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + Clone + 'static, S: BuildHasher, { @@ -339,7 +345,7 @@ where pub(crate) struct Iter<'a, K, V> where - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { inner: RawIter>, @@ -348,7 +354,7 @@ where impl Clone for Iter<'_, K, V> where - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { #[inline] @@ -362,7 +368,7 @@ where impl fmt::Debug for Iter<'_, K, V> where - K: Trace + 'static + fmt::Debug, + K: Trace + ?Sized + 'static + fmt::Debug, V: Trace + 'static + fmt::Debug, { #[inline] @@ -373,7 +379,7 @@ where impl<'a, K, V> Iterator for Iter<'a, K, V> where - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { type Item = &'a Ephemeron; @@ -393,7 +399,7 @@ where impl fmt::Debug for RawWeakMap where - K: fmt::Debug + Trace + Finalize, + K: fmt::Debug + ?Sized + Trace + Finalize, V: fmt::Debug + Trace + Finalize, { #[inline] @@ -405,7 +411,7 @@ where fn make_hasher(hash_builder: &S) -> impl Fn(&Ephemeron) -> u64 + '_ where S: BuildHasher, - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { move |val| make_hash_from_eph::(hash_builder, val) @@ -414,7 +420,7 @@ where fn make_hash_from_eph(hash_builder: &S, eph: &Ephemeron) -> u64 where S: BuildHasher, - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { use std::hash::Hasher; @@ -435,7 +441,7 @@ where fn make_hash_from_gc(hash_builder: &S, gc: &Gc) -> u64 where S: BuildHasher, - K: Trace + 'static, + K: Trace + ?Sized + 'static, { use std::hash::Hasher; let mut state = hash_builder.build_hasher(); @@ -445,7 +451,7 @@ where fn equivalent_key(k: &Gc) -> impl Fn(&Ephemeron) -> bool + '_ where - K: Trace + 'static, + K: Trace + ?Sized + 'static, V: Trace + 'static, { // SAFETY: The return value of `key` is only used inside eq, which From 31397628ec6f51c4cf24407a2d9de26e2bba76ec Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 04:14:36 -0600 Subject: [PATCH 02/15] Implement erased objects --- boa_engine/src/object/datatypes.rs | 193 +++ boa_engine/src/object/jsobject.rs | 691 ++------- boa_engine/src/object/mod.rs | 2114 +++------------------------ boa_engine/src/object/operations.rs | 25 +- 4 files changed, 513 insertions(+), 2510 deletions(-) create mode 100644 boa_engine/src/object/datatypes.rs diff --git a/boa_engine/src/object/datatypes.rs b/boa_engine/src/object/datatypes.rs new file mode 100644 index 00000000000..e80f80707e7 --- /dev/null +++ b/boa_engine/src/object/datatypes.rs @@ -0,0 +1,193 @@ +use std::{ + any::TypeId, + borrow::Cow, + cell::Cell, + collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}, + hash::{BuildHasher, Hash}, + marker::PhantomData, + num::{ + NonZeroI128, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroIsize, NonZeroU128, + NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8, NonZeroUsize, + }, + path::{Path, PathBuf}, + rc::Rc, + sync::atomic, +}; + +use boa_gc::{Ephemeron, Gc, GcRefCell, Trace, WeakGc, WeakMap}; +pub use boa_macros::JsData; + +use super::internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; + +pub trait JsData { + #[doc(hidden)] + fn internal_methods(&self) -> &'static InternalObjectMethods + where + Self: Sized, + { + &ORDINARY_INTERNAL_METHODS + } +} + +macro_rules! default_impls { + ($($T:ty),*$(,)?) => { + $( + impl JsData for $T {} + )* + } +} + +default_impls![ + (), + bool, + isize, + usize, + i8, + u8, + i16, + u16, + i32, + u32, + i64, + u64, + i128, + u128, + f32, + f64, + char, + TypeId, + String, + Path, + PathBuf, + NonZeroIsize, + NonZeroUsize, + NonZeroI8, + NonZeroU8, + NonZeroI16, + NonZeroU16, + NonZeroI32, + NonZeroU32, + NonZeroI64, + NonZeroU64, + NonZeroI128, + NonZeroU128, +]; + +#[cfg(target_has_atomic = "8")] +default_impls![atomic::AtomicBool, atomic::AtomicI8, atomic::AtomicU8]; + +#[cfg(target_has_atomic = "16")] +default_impls![atomic::AtomicI16, atomic::AtomicU16]; + +#[cfg(target_has_atomic = "32")] +default_impls![atomic::AtomicI32, atomic::AtomicU32]; + +#[cfg(target_has_atomic = "64")] +default_impls![atomic::AtomicI64, atomic::AtomicU64]; + +#[cfg(target_has_atomic = "ptr")] +default_impls![atomic::AtomicIsize, atomic::AtomicUsize]; + +impl JsData for [T; N] {} + +macro_rules! fn_one { + ($ty:ty $(,$args:ident)*) => { + impl JsData for $ty {} + } +} + +macro_rules! fn_impls { + () => { + fn_one!(extern "Rust" fn () -> Ret); + fn_one!(extern "C" fn () -> Ret); + fn_one!(unsafe extern "Rust" fn () -> Ret); + fn_one!(unsafe extern "C" fn () -> Ret); + }; + ($($args:ident),*) => { + fn_one!(extern "Rust" fn ($($args),*) -> Ret, $($args),*); + fn_one!(extern "C" fn ($($args),*) -> Ret, $($args),*); + fn_one!(extern "C" fn ($($args),*, ...) -> Ret, $($args),*); + fn_one!(unsafe extern "Rust" fn ($($args),*) -> Ret, $($args),*); + fn_one!(unsafe extern "C" fn ($($args),*) -> Ret, $($args),*); + fn_one!(unsafe extern "C" fn ($($args),*, ...) -> Ret, $($args),*); + } +} + +macro_rules! tuple_impls { + () => {}; // This case is handled above, by default_impls!(). + ($($args:ident),*) => { + impl<$($args),*> JsData for ($($args,)*) {} + } +} + +macro_rules! type_arg_tuple_based_impls { + ($(($($args:ident),*);)*) => { + $( + fn_impls!($($args),*); + tuple_impls!($($args),*); + )* + } +} + +type_arg_tuple_based_impls![ + (); + (A); + (A, B); + (A, B, C); + (A, B, C, D); + (A, B, C, D, E); + (A, B, C, D, E, F); + (A, B, C, D, E, F, G); + (A, B, C, D, E, F, G, H); + (A, B, C, D, E, F, G, H, I); + (A, B, C, D, E, F, G, H, I, J); + (A, B, C, D, E, F, G, H, I, J, K); + (A, B, C, D, E, F, G, H, I, J, K, L); +]; + +impl JsData for Box {} + +impl JsData for Rc {} + +impl JsData for Vec {} + +impl JsData for thin_vec::ThinVec {} + +impl JsData for Option {} + +impl JsData for Result {} + +impl JsData for BinaryHeap {} + +impl JsData for BTreeMap {} + +impl JsData for BTreeSet {} + +impl JsData for hashbrown::hash_map::HashMap {} + +impl JsData for HashMap {} + +impl JsData for HashSet {} + +impl JsData for LinkedList {} + +impl JsData for PhantomData {} + +impl JsData for VecDeque {} + +impl JsData for Cow<'static, T> {} + +impl JsData for Cell> {} + +#[cfg(feature = "intl")] +default_impls!(icu_locid::Locale); + +impl JsData for Gc {} + +impl JsData for WeakGc {} + +impl JsData for Ephemeron {} + +impl JsData for GcRefCell {} + +impl JsData for WeakMap {} diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 6094c7db785..b99e0318b3f 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -3,30 +3,30 @@ //! The `JsObject` is a garbage collected Object. use super::{ - internal_methods::{ - non_existant_call, non_existant_construct, InternalMethodContext, InternalObjectMethods, - ARRAY_EXOTIC_INTERNAL_METHODS, - }, + internal_methods::{non_existant_call, non_existant_construct, InternalMethodContext, InternalObjectMethods}, shape::RootShape, JsPrototype, NativeObject, Object, PrivateName, PropertyMap, }; use crate::{ + builtins::{ + array::ARRAY_EXOTIC_INTERNAL_METHODS, function::OrdinaryFunction, object::OrdinaryObject, + }, context::intrinsics::Intrinsics, error::JsNativeError, js_string, - object::{ObjectData, ObjectKind}, property::{PropertyDescriptor, PropertyKey}, string::utf16, value::PreferredType, Context, JsResult, JsString, JsValue, }; -use boa_gc::{self, Finalize, Gc, GcRefCell, Trace}; +use boa_gc::{self, Finalize, Gc, GcBox, GcRefCell, Trace}; use std::{ cell::RefCell, collections::HashMap, error::Error, fmt::{self, Debug, Display}, hash::Hash, + ptr::NonNull, result::Result as StdResult, }; use thin_vec::ThinVec; @@ -37,10 +37,15 @@ pub type Ref<'a, T> = boa_gc::GcRef<'a, T>; /// A wrapper type for a mutably borrowed type T. pub type RefMut<'a, T, U> = boa_gc::GcRefMut<'a, T, U>; +/// An `Object` with inner data set to `dyn NativeObject`. +pub type ErasedObject = Object; + +pub(crate) type ErasedVTableObject = VTableObject; + /// Garbage collected `Object`. #[derive(Trace, Finalize, Clone)] pub struct JsObject { - inner: Gc, + inner: Gc>, } /// An `Object` that has an additional `vtable` with its internal methods. @@ -49,31 +54,38 @@ pub struct JsObject { // so we have to force our users to debug the `JsObject` instead. #[allow(missing_debug_implementations)] #[derive(Trace, Finalize)] -pub struct VTableObject { - object: GcRefCell, +pub(crate) struct VTableObject { #[unsafe_ignore_trace] vtable: &'static InternalObjectMethods, + object: GcRefCell>, } impl Default for JsObject { fn default() -> Self { - let data = ObjectData::ordinary(); - Self::from_object_and_vtable(Object::default(), data.internal_methods) + Self::from_proto_and_data(None, OrdinaryObject) } } impl JsObject { /// Creates a new `JsObject` from its inner object and its vtable. - pub(crate) fn from_object_and_vtable( - object: Object, + pub(crate) fn from_object_and_vtable( + object: Object, vtable: &'static InternalObjectMethods, ) -> Self { - Self { - inner: Gc::new(VTableObject { - object: GcRefCell::new(object), - vtable, - }), - } + let gc = Gc::new(VTableObject { + object: GcRefCell::new(object), + vtable, + }); + + // SAFETY: This just makes the casting from sized to unsized. Should eventually be replaced by + // https://github.com/rust-lang/rust/issues/18598 + let gc: Gc> = unsafe { + let ptr = Gc::into_raw(gc); + let ptr: NonNull>> = ptr; + Gc::from_raw(ptr) + }; + + Self { inner: gc } } /// Creates a new ordinary object with its prototype set to the `Object` prototype. @@ -87,7 +99,7 @@ impl JsObject { pub fn with_object_proto(intrinsics: &Intrinsics) -> Self { Self::from_proto_and_data( intrinsics.constructors().object().prototype(), - ObjectData::ordinary(), + OrdinaryObject, ) } @@ -100,7 +112,7 @@ impl JsObject { #[inline] #[must_use] pub fn with_null_proto() -> Self { - Self::from_proto_and_data(None, ObjectData::ordinary()) + Self::from_proto_and_data(None, OrdinaryObject) } /// Creates a new object with the provided prototype and object data. @@ -110,18 +122,30 @@ impl JsObject { /// the [`ObjectData`] provided. /// /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate - pub fn from_proto_and_data>>(prototype: O, data: ObjectData) -> Self { - Self { - inner: Gc::new(VTableObject { - object: GcRefCell::new(Object { - kind: data.kind, - properties: PropertyMap::from_prototype_unique_shape(prototype.into()), - extensible: true, - private_elements: ThinVec::new(), - }), - vtable: data.internal_methods, + pub fn from_proto_and_data>, T: NativeObject>( + prototype: O, + data: T, + ) -> Self { + let internal_methods = data.internal_methods(); + let gc = Gc::new(VTableObject { + object: GcRefCell::new(Object { + data, + properties: PropertyMap::from_prototype_unique_shape(prototype.into()), + extensible: true, + private_elements: ThinVec::new(), }), - } + vtable: internal_methods, + }); + + // SAFETY: This just makes the casting from sized to unsized. Should eventually be replaced by + // https://github.com/rust-lang/rust/issues/18598 + let gc: Gc> = unsafe { + let ptr = Gc::into_raw(gc); + let ptr: NonNull>> = ptr; + Gc::from_raw(ptr) + }; + + Self { inner: gc } } /// Creates a new object with the provided prototype and object data. @@ -131,25 +155,34 @@ impl JsObject { /// the [`ObjectData`] provided. /// /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate - pub(crate) fn from_proto_and_data_with_shared_shape>>( + pub(crate) fn from_proto_and_data_with_shared_shape>, T: NativeObject>( root_shape: &RootShape, prototype: O, - data: ObjectData, + data: T, ) -> Self { - Self { - inner: Gc::new(VTableObject { - object: GcRefCell::new(Object { - kind: data.kind, - properties: PropertyMap::from_prototype_with_shared_shape( - root_shape, - prototype.into(), - ), - extensible: true, - private_elements: ThinVec::new(), - }), - vtable: data.internal_methods, + let internal_methods = data.internal_methods(); + let gc = Gc::new(VTableObject { + object: GcRefCell::new(Object { + data, + properties: PropertyMap::from_prototype_with_shared_shape( + root_shape, + prototype.into(), + ), + extensible: true, + private_elements: ThinVec::new(), }), - } + vtable: internal_methods, + }); + + // SAFETY: This just makes the casting from sized to unsized. Should eventually be replaced by + // https://github.com/rust-lang/rust/issues/18598 + let gc: Gc> = unsafe { + let ptr = Gc::into_raw(gc); + let ptr: NonNull>> = ptr; + Gc::from_raw(ptr) + }; + + Self { inner: gc } } /// Immutably borrows the `Object`. @@ -163,7 +196,7 @@ impl JsObject { #[inline] #[must_use] #[track_caller] - pub fn borrow(&self) -> Ref<'_, Object> { + pub fn borrow(&self) -> Ref<'_, ErasedObject> { self.try_borrow().expect("Object already mutably borrowed") } @@ -177,7 +210,7 @@ impl JsObject { #[inline] #[must_use] #[track_caller] - pub fn borrow_mut(&self) -> RefMut<'_, Object, Object> { + pub fn borrow_mut(&self) -> RefMut<'_, ErasedObject, ErasedObject> { self.try_borrow_mut().expect("Object already borrowed") } @@ -188,7 +221,7 @@ impl JsObject { /// /// This is the non-panicking variant of [`borrow`](#method.borrow). #[inline] - pub fn try_borrow(&self) -> StdResult, BorrowError> { + pub fn try_borrow(&self) -> StdResult, BorrowError> { self.inner.object.try_borrow().map_err(|_| BorrowError) } @@ -199,7 +232,9 @@ impl JsObject { /// /// This is the non-panicking variant of [`borrow_mut`](#method.borrow_mut). #[inline] - pub fn try_borrow_mut(&self) -> StdResult, BorrowMutError> { + pub fn try_borrow_mut( + &self, + ) -> StdResult, BorrowMutError> { self.inner .object .try_borrow_mut() @@ -288,40 +323,16 @@ impl JsObject { .into()) } - /// Return `true` if it is a native object and the native type is `T`. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[must_use] - #[track_caller] - pub fn is(&self) -> bool - where - T: NativeObject, - { - self.borrow().is::() - } - /// Downcast a reference to the object, - /// if the object is type native object type `T`. + /// if the object is of type `T`. /// /// # Panics /// /// Panics if the object is currently mutably borrowed. #[must_use] #[track_caller] - pub fn downcast_ref(&self) -> Option> - where - T: NativeObject, - { - let object = self.borrow(); - if object.is::() { - Some(Ref::map(object, |x| { - x.downcast_ref::().expect("downcasting reference failed") - })) - } else { - None - } + pub fn downcast_ref(&self) -> Option> { + Ref::try_map(self.borrow(), |x| x.downcast_ref()) } /// Downcast a mutable reference to the object, @@ -332,19 +343,8 @@ impl JsObject { /// Panics if the object is currently borrowed. #[must_use] #[track_caller] - pub fn downcast_mut(&self) -> Option> - where - T: NativeObject, - { - let object = self.borrow_mut(); - if object.is::() { - Some(RefMut::map(object, |x| { - x.downcast_mut::() - .expect("downcasting mutable reference failed") - })) - } else { - None - } + pub fn downcast_mut(&self) -> Option> { + RefMut::try_map(self.borrow_mut(), |x| x.downcast_mut()) } /// Get the prototype of the object. @@ -380,15 +380,7 @@ impl JsObject { self.borrow_mut().set_prototype(prototype) } - /// Checks if it's an `Array` object. - #[inline] - #[must_use] - #[track_caller] - pub fn is_array(&self) -> bool { - std::ptr::eq(self.vtable(), &ARRAY_EXOTIC_INTERNAL_METHODS) - } - - /// Checks if it's a `DataView` object. + /// Checks if this object is an instance of a certain `NativeObject`. /// /// # Panics /// @@ -396,23 +388,11 @@ impl JsObject { #[inline] #[must_use] #[track_caller] - pub fn is_data_view(&self) -> bool { - self.borrow().is_data_view() - } - - /// Checks if it is an `ArrayIterator` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_array_iterator(&self) -> bool { - self.borrow().is_array_iterator() + pub fn is(&self) -> bool { + self.borrow().is::() } - /// Checks if it's an `ArrayBuffer` object. + /// Checks if it's an ordinary object. /// /// # Panics /// @@ -420,20 +400,16 @@ impl JsObject { #[inline] #[must_use] #[track_caller] - pub fn is_array_buffer(&self) -> bool { - self.borrow().is_array_buffer() + pub fn is_ordinary(&self) -> bool { + self.is::() } - /// Checks if it's a `SharedArrayBuffer` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. + /// Checks if it's an `Array` object. #[inline] #[must_use] #[track_caller] - pub fn is_shared_array_buffer(&self) -> bool { - self.borrow().as_shared_array_buffer().is_some() + pub fn is_array(&self) -> bool { + std::ptr::eq(self.vtable(), &ARRAY_EXOTIC_INTERNAL_METHODS) } /// Checks if it's an `ArrayBuffer` or `SharedArrayBuffer` object. @@ -448,446 +424,6 @@ impl JsObject { self.borrow().as_buffer().is_some() } - /// Checks if it is a `Map` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_map(&self) -> bool { - self.borrow().is_map() - } - - /// Checks if it's a `MapIterator` object - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_map_iterator(&self) -> bool { - self.borrow().is_map_iterator() - } - - /// Checks if it is a `Set` object - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_set(&self) -> bool { - self.borrow().is_set() - } - - /// Checks if it is a `SetIterator` object - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_set_iterator(&self) -> bool { - self.borrow().is_set_iterator() - } - - /// Checks if it's a `String` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_string(&self) -> bool { - self.borrow().is_string() - } - - /// Checks if it's a `Function` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_ordinary_function(&self) -> bool { - self.borrow().is_ordinary_function() - } - - /// Checks if it's a native `Function` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_native_function(&self) -> bool { - self.borrow().is_native_function() - } - - /// Checks if it's a `Generator` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_generator(&self) -> bool { - self.borrow().is_generator() - } - - /// Checks if it's a `Symbol` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_symbol(&self) -> bool { - self.borrow().is_symbol() - } - - /// Checks if it's an `Error` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_error(&self) -> bool { - self.borrow().is_error() - } - - /// Checks if it's a `Boolean` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_boolean(&self) -> bool { - self.borrow().is_boolean() - } - - /// Checks if it's a `Number` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_number(&self) -> bool { - self.borrow().is_number() - } - - /// Checks if it's a `BigInt` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_bigint(&self) -> bool { - self.borrow().is_bigint() - } - - /// Checks if it's a `Date` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_date(&self) -> bool { - self.borrow().is_date() - } - - /// Checks if it's a `RegExp` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_regexp(&self) -> bool { - self.borrow().is_regexp() - } - - /// Checks if it's a `TypedArray` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_typed_array(&self) -> bool { - self.borrow().is_typed_array() - } - - /// Checks if it's a `Uint8Array` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_typed_uint8_array(&self) -> bool { - self.borrow().is_typed_uint8_array() - } - - /// Checks if it's a `Int8Array` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_typed_int8_array(&self) -> bool { - self.borrow().is_typed_int8_array() - } - - /// Checks if it's a `Uint16Array` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_typed_uint16_array(&self) -> bool { - self.borrow().is_typed_uint16_array() - } - - /// Checks if it's a `Int16Array` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_typed_int16_array(&self) -> bool { - self.borrow().is_typed_int16_array() - } - - /// Checks if it's a `Uint32Array` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_typed_uint32_array(&self) -> bool { - self.borrow().is_typed_uint32_array() - } - - /// Checks if it's a `Int32Array` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_typed_int32_array(&self) -> bool { - self.borrow().is_typed_int32_array() - } - - /// Checks if it's a `Float32Array` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_typed_float32_array(&self) -> bool { - self.borrow().is_typed_float32_array() - } - - /// Checks if it's a `Float64Array` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_typed_float64_array(&self) -> bool { - self.borrow().is_typed_float64_array() - } - - /// Checks if it's a `Promise` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_promise(&self) -> bool { - self.borrow().is_promise() - } - - /// Checks if it's an ordinary object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_ordinary(&self) -> bool { - self.borrow().is_ordinary() - } - - /// Checks if current object is a `Temporal.Duration` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - #[cfg(feature = "temporal")] - pub fn is_duration(&self) -> bool { - self.borrow().is_duration() - } - - /// Checks if current object is a `Temporal.TimeZone` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - #[cfg(feature = "temporal")] - pub fn is_time_zone(&self) -> bool { - self.borrow().is_time_zone() - } - - /// Checks if current object is a `Temporal.PlainDateTime` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - #[cfg(feature = "temporal")] - pub fn is_plain_date_time(&self) -> bool { - self.borrow().is_plain_date_time() - } - - /// Checks if current object is a `Temporal.PlainDate` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - #[cfg(feature = "temporal")] - pub fn is_plain_date(&self) -> bool { - self.borrow().is_plain_date() - } - - /// Checks if current object is a `Temporal.PlainYearMonth` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - #[cfg(feature = "temporal")] - pub fn is_plain_year_month(&self) -> bool { - self.borrow().is_plain_year_month() - } - - /// Checks if current object is a `Temporal.PlainMonthDay` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - #[cfg(feature = "temporal")] - pub fn is_plain_month_day(&self) -> bool { - self.borrow().is_plain_month_day() - } - - /// Checks if current object is a `Temporal.ZonedDateTime` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - #[cfg(feature = "temporal")] - pub fn is_zoned_date_time(&self) -> bool { - self.borrow().is_zoned_date_time() - } - - /// Checks if current object is a `Temporal.Calendar` object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - #[cfg(feature = "temporal")] - pub fn is_calendar(&self) -> bool { - self.borrow().is_calendar() - } - - /// Checks if it's a proxy object. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_proxy(&self) -> bool { - self.borrow().is_proxy() - } - - /// Returns `true` if it holds an Rust type that implements `NativeObject`. - /// - /// # Panics - /// - /// Panics if the object is currently mutably borrowed. - #[inline] - #[must_use] - #[track_caller] - pub fn is_native_object(&self) -> bool { - self.borrow().is_native_object() - } - /// The abstract operation `ToPropertyDescriptor`. /// /// More information: @@ -1117,37 +653,31 @@ Cannot both specify accessors and a value or writable attribute", self.inner.vtable.__construct__ != non_existant_construct } - /// Returns true if the `JsObject` is the global for a Realm - #[must_use] - pub fn is_global(&self) -> bool { - matches!(self.inner.object.borrow().kind, ObjectKind::Global) - } - pub(crate) fn vtable(&self) -> &'static InternalObjectMethods { self.inner.vtable } - pub(crate) const fn inner(&self) -> &Gc { + pub(crate) const fn inner(&self) -> &Gc> { &self.inner } /// Create a new private name with this object as the unique identifier. pub(crate) fn private_name(&self, description: JsString) -> PrivateName { let ptr: *const _ = self.as_ref(); - PrivateName::new(description, ptr as usize) + PrivateName::new(description, (ptr as *const ()) as usize) } } -impl AsRef> for JsObject { +impl AsRef> for JsObject { #[inline] - fn as_ref(&self) -> &GcRefCell { + fn as_ref(&self) -> &GcRefCell { &self.inner.object } } -impl From> for JsObject { +impl From>> for JsObject { #[inline] - fn from(inner: Gc) -> Self { + fn from(inner: Gc>) -> Self { Self { inner } } } @@ -1250,10 +780,10 @@ impl RecursionLimiter { /// This is done by maintaining a thread-local hashset containing the pointers of `T` values that have been /// visited. The first `T` visited will clear the hashset, while any others will check if they are contained /// by the hashset. - pub fn new(o: &T) -> Self { + pub fn new(o: &T) -> Self { // We shouldn't have to worry too much about this being moved during Debug::fmt. #[allow(trivial_casts)] - let ptr = (o as *const _) as usize; + let ptr = (o as *const _ as *const ()) as usize; let (top_level, visited, live) = SEEN.with(|hm| { let mut hm = hm.borrow_mut(); let top_level = hm.is_empty(); @@ -1287,9 +817,10 @@ impl Debug for JsObject { // at most once, hopefully making things a bit clearer. if !limiter.visited && !limiter.live { let ptr: *const _ = self.as_ref(); + let ptr = ptr as *const (); let obj = self.borrow(); - let kind = obj.kind(); - if obj.is_ordinary_function() { + let kind = obj.data.type_name_of_value(); + if obj.is::() { let name_prop = obj .properties() .get(&PropertyKey::String(JsString::from("name"))); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index 699e1622075..c46a940603e 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -7,70 +7,31 @@ pub use operations::IntegrityLevel; pub use property_map::*; use thin_vec::ThinVec; -use self::{ - internal_methods::{ - arguments::ARGUMENTS_EXOTIC_INTERNAL_METHODS, - array::ARRAY_EXOTIC_INTERNAL_METHODS, - bound_function::{ - BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS, BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS, - }, - function::{ - CONSTRUCTOR_INTERNAL_METHODS, FUNCTION_INTERNAL_METHODS, - NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS, - }, - immutable_prototype::IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, - integer_indexed::INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, - module_namespace::MODULE_NAMESPACE_EXOTIC_INTERNAL_METHODS, - proxy::{ - PROXY_EXOTIC_INTERNAL_METHODS_ALL, PROXY_EXOTIC_INTERNAL_METHODS_BASIC, - PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL, - }, - string::STRING_EXOTIC_INTERNAL_METHODS, - InternalObjectMethods, ORDINARY_INTERNAL_METHODS, - }, - shape::Shape, -}; -#[cfg(feature = "temporal")] -use crate::builtins::temporal::{ - Calendar, Duration, Instant, PlainDate, PlainDateTime, PlainMonthDay, PlainTime, - PlainYearMonth, TimeZone, ZonedDateTime, -}; +use self::{internal_methods::ORDINARY_INTERNAL_METHODS, shape::Shape}; use crate::{ builtins::{ - array::ArrayIterator, array_buffer::{ArrayBuffer, BufferRef, BufferRefMut, SharedArrayBuffer}, - async_generator::AsyncGenerator, - error::ErrorKind, - function::arguments::Arguments, - function::{arguments::ParameterMap, BoundFunction, ConstructorKind, OrdinaryFunction}, - generator::Generator, - iterable::AsyncFromSyncIterator, - map::ordered_map::OrderedMap, - map::MapIterator, - object::for_in_iterator::ForInIterator, - proxy::Proxy, - regexp::RegExpStringIterator, - set::ordered_set::OrderedSet, - set::SetIterator, - string::StringIterator, - typed_array::{IntegerIndexed, TypedArrayKind}, - DataView, Date, Promise, RegExp, + function::{ + arguments::{MappedArguments, UnmappedArguments}, + ConstructorKind, + }, + typed_array::{TypedArray, TypedArrayKind}, + OrdinaryObject, }, context::intrinsics::StandardConstructor, js_string, - module::ModuleNamespace, native_function::{NativeFunction, NativeFunctionObject}, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, string::utf16, - Context, JsBigInt, JsString, JsSymbol, JsValue, + Context, JsString, JsSymbol, JsValue, }; -use boa_gc::{custom_trace, Finalize, Trace, WeakGc}; +use boa_gc::{Finalize, Trace}; use std::{ any::{Any, TypeId}, - fmt::{self, Debug}, - ops::{Deref, DerefMut}, + fmt::Debug, + ops::Deref, }; #[cfg(test)] @@ -79,6 +40,7 @@ mod tests; pub(crate) mod internal_methods; pub mod builtins; +mod datatypes; mod jsobject; mod operations; mod property_map; @@ -86,6 +48,7 @@ pub mod shape; pub(crate) use builtins::*; +pub use datatypes::JsData; pub use jsobject::*; pub(crate) trait JsObjectType: @@ -113,8 +76,8 @@ pub(crate) type ObjectStorage = Vec; /// This trait allows Rust types to be passed around as objects. /// -/// This is automatically implemented when a type implements `Any` and `Trace`. -pub trait NativeObject: Any + Trace { +/// This is automatically implemented when a type implements `Any`, `Trace`, and `JsData`. +pub trait NativeObject: Any + Trace + JsData { /// Convert the Rust type which implements `NativeObject` to a `&dyn Any`. fn as_any(&self) -> &dyn Any; @@ -122,17 +85,11 @@ pub trait NativeObject: Any + Trace { fn as_mut_any(&mut self) -> &mut dyn Any; /// Gets the type name of the value. - fn type_name_of_value(&self) -> &'static str { - fn name_of_val(_val: &T) -> &'static str { - std::any::type_name::() - } - - name_of_val(self) - } + fn type_name_of_value(&self) -> &'static str; } // TODO: Use super trait casting in Rust 1.75 -impl NativeObject for T { +impl NativeObject for T { fn as_any(&self) -> &dyn Any { self } @@ -140,6 +97,14 @@ impl NativeObject for T { fn as_mut_any(&mut self) -> &mut dyn Any { self } + + fn type_name_of_value(&self) -> &'static str { + fn name_of_val(_val: &T) -> &'static str { + std::any::type_name::() + } + + name_of_val(self) + } } // TODO: Use super trait casting in Rust 1.75 @@ -214,31 +179,31 @@ impl dyn NativeObject { /// The internal representation of a JavaScript object. #[derive(Debug, Finalize)] -pub struct Object { - /// The type of the object. - kind: ObjectKind, +pub struct Object { /// The collection of properties contained in the object - properties: PropertyMap, + pub(crate) properties: PropertyMap, /// Whether it can have new properties added to it. pub(crate) extensible: bool, /// The `[[PrivateElements]]` internal slot. private_elements: ThinVec<(PrivateName, PrivateElement)>, + /// The inner object data + data: T, } -impl Default for Object { +impl Default for Object { fn default() -> Self { Self { - kind: ObjectKind::Ordinary, properties: PropertyMap::default(), extensible: true, private_elements: ThinVec::new(), + data: T::default(), } } } -unsafe impl Trace for Object { +unsafe impl Trace for Object { boa_gc::custom_trace!(this, { - mark(&this.kind); + mark(&this.data); mark(&this.properties); for (_, element) in &this.private_elements { mark(element); @@ -282,1533 +247,29 @@ pub enum PrivateElement { }, } -/// Defines the kind of an object and its internal methods -pub struct ObjectData { - pub(crate) kind: ObjectKind, - pub(crate) internal_methods: &'static InternalObjectMethods, -} - -impl Debug for ObjectData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let ptr: *const _ = self.internal_methods; - f.debug_struct("ObjectData") - .field("kind", &self.kind) - .field("internal_methods", &ptr) - .finish() - } -} - -/// Defines the different types of objects. -#[derive(Finalize)] -pub enum ObjectKind { - /// The `AsyncFromSyncIterator` object kind. - AsyncFromSyncIterator(AsyncFromSyncIterator), - - /// The `AsyncGenerator` object kind. - AsyncGenerator(AsyncGenerator), - - /// The `Array` object kind. - Array, - - /// The `ArrayIterator` object kind. - ArrayIterator(ArrayIterator), - - /// The `ArrayBuffer` object kind. - ArrayBuffer(ArrayBuffer), - - /// The `SharedArrayBuffer` object kind. - SharedArrayBuffer(SharedArrayBuffer), - - /// The `Map` object kind. - Map(OrderedMap), - - /// The `MapIterator` object kind. - MapIterator(MapIterator), - - /// The `RegExp` object kind. - RegExp(Box), - - /// The `RegExpStringIterator` object kind. - RegExpStringIterator(RegExpStringIterator), - - /// The `BigInt` object kind. - BigInt(JsBigInt), - - /// The `Boolean` object kind. - Boolean(bool), - - /// The `DataView` object kind. - DataView(DataView), - - /// The `ForInIterator` object kind. - ForInIterator(ForInIterator), - - /// The `Function` object kind. - OrdinaryFunction(OrdinaryFunction), - - /// The `BoundFunction` object kind. - BoundFunction(BoundFunction), - - /// The `Generator` object kind. - Generator(Generator), - - /// A native rust function. - NativeFunction(NativeFunctionObject), - - /// The `Set` object kind. - Set(OrderedSet), - - /// The `SetIterator` object kind. - SetIterator(SetIterator), - - /// The `String` object kind. - String(JsString), - - /// The `StringIterator` object kind. - StringIterator(StringIterator), - - /// The `Number` object kind. - Number(f64), - - /// The `Symbol` object kind. - Symbol(JsSymbol), - - /// The `Error` object kind. - Error(ErrorKind), - - /// The ordinary object kind. - Ordinary, - - /// The `Proxy` object kind. - Proxy(Proxy), - - /// The `Date` object kind. - Date(Date), - - /// The `Global` object kind. - Global, - - /// The arguments exotic object kind. - Arguments(Arguments), - - /// The rust native object kind. - NativeObject(Box), - - /// The integer-indexed exotic object kind. - IntegerIndexed(IntegerIndexed), - - /// The `Promise` object kind. - Promise(Promise), - - /// The `WeakRef` object kind. - WeakRef(WeakGc), - - /// The `WeakMap` object kind. - WeakMap(boa_gc::WeakMap), - - /// The `WeakSet` object kind. - WeakSet(boa_gc::WeakMap), - - /// The `ModuleNamespace` object kind. - ModuleNamespace(ModuleNamespace), - - /// The `Temporal.Instant` object kind. - #[cfg(feature = "temporal")] - Instant(Instant), - - /// The `Temporal.PlainDateTime` object kind. - #[cfg(feature = "temporal")] - PlainDateTime(PlainDateTime), - - /// The `Temporal.PlainDate` object kind. - #[cfg(feature = "temporal")] - PlainDate(PlainDate), - - /// The `Temporal.PlainTime` object kind. - #[cfg(feature = "temporal")] - PlainTime(PlainTime), - - /// The `Temporal.PlainYearMonth` object kind. - #[cfg(feature = "temporal")] - PlainYearMonth(PlainYearMonth), - - /// The `Temporal.PlainMonthDay` object kind. - #[cfg(feature = "temporal")] - PlainMonthDay(PlainMonthDay), - - /// The `Temporal.TimeZone` object kind. - #[cfg(feature = "temporal")] - TimeZone(TimeZone), - - /// The `Temporal.Duration` object kind. - #[cfg(feature = "temporal")] - Duration(Duration), - - /// The `Temporal.ZonedDateTime` object kind. - #[cfg(feature = "temporal")] - ZonedDateTime(ZonedDateTime), - - /// The `Temporal.Calendar` object kind. - #[cfg(feature = "temporal")] - Calendar(Calendar), -} - -unsafe impl Trace for ObjectKind { - custom_trace! {this, { - match this { - Self::AsyncFromSyncIterator(a) => mark(a), - Self::ArrayIterator(i) => mark(i), - Self::ArrayBuffer(b) => mark(b), - Self::Map(m) => mark(m), - Self::MapIterator(i) => mark(i), - Self::RegExpStringIterator(i) => mark(i), - Self::DataView(v) => mark(v), - Self::ForInIterator(i) => mark(i), - Self::OrdinaryFunction(f) => mark(f), - Self::BoundFunction(f) => mark(f), - Self::Generator(g) => mark(g), - Self::NativeFunction(f) => mark(f), - Self::Set(s) => mark(s), - Self::SetIterator(i) => mark(i), - Self::StringIterator(i) => mark(i), - Self::Proxy(p) => mark(p), - Self::Arguments(a) => mark(a), - Self::NativeObject(o) => mark(o), - Self::IntegerIndexed(i) => mark(i), - Self::Promise(p) => mark(p), - Self::AsyncGenerator(g) => mark(g), - Self::WeakRef(wr) => mark(wr), - Self::WeakMap(wm) => mark(wm), - Self::WeakSet(ws) => mark(ws), - Self::ModuleNamespace(m) => mark(m), - Self::RegExp(_) - | Self::BigInt(_) - | Self::Boolean(_) - | Self::String(_) - | Self::Date(_) - | Self::Array - | Self::Error(_) - | Self::Ordinary - | Self::Global - | Self::Number(_) - | Self::Symbol(_) - | Self::SharedArrayBuffer(_) => {} - #[cfg(feature = "temporal")] - Self::Instant(_) - | Self::PlainDateTime(_) - | Self::PlainDate(_) - | Self::PlainTime(_) - | Self::PlainYearMonth(_) - | Self::PlainMonthDay(_) - | Self::TimeZone(_) - | Self::Calendar(_) - | Self::Duration(_) - | Self::ZonedDateTime(_) => {} - } - }} -} - -impl ObjectData { - /// Create the immutable `%Object.prototype%` object data - pub(crate) fn object_prototype() -> Self { - Self { - kind: ObjectKind::Ordinary, - internal_methods: &IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, - } - } - - /// Create the `AsyncFromSyncIterator` object data - #[must_use] - pub fn async_from_sync_iterator(async_from_sync_iterator: AsyncFromSyncIterator) -> Self { - Self { - kind: ObjectKind::AsyncFromSyncIterator(async_from_sync_iterator), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `AsyncGenerator` object data - #[must_use] - pub fn async_generator(async_generator: AsyncGenerator) -> Self { - Self { - kind: ObjectKind::AsyncGenerator(async_generator), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Array` object data and reference its exclusive internal methods - #[must_use] - pub fn array() -> Self { - Self { - kind: ObjectKind::Array, - internal_methods: &ARRAY_EXOTIC_INTERNAL_METHODS, - } - } - - /// Create the `ArrayIterator` object data - #[must_use] - pub fn array_iterator(array_iterator: ArrayIterator) -> Self { - Self { - kind: ObjectKind::ArrayIterator(array_iterator), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `ArrayBuffer` object data - #[must_use] - pub fn array_buffer(array_buffer: ArrayBuffer) -> Self { - Self { - kind: ObjectKind::ArrayBuffer(array_buffer), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `SharedArrayBuffer` object data - #[must_use] - pub fn shared_array_buffer(shared_array_buffer: SharedArrayBuffer) -> Self { - Self { - kind: ObjectKind::SharedArrayBuffer(shared_array_buffer), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Map` object data - #[must_use] - pub fn map(map: OrderedMap) -> Self { - Self { - kind: ObjectKind::Map(map), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `MapIterator` object data - #[must_use] - pub fn map_iterator(map_iterator: MapIterator) -> Self { - Self { - kind: ObjectKind::MapIterator(map_iterator), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `RegExp` object data - #[must_use] - pub fn regexp(reg_exp: RegExp) -> Self { - Self { - kind: ObjectKind::RegExp(Box::new(reg_exp)), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `RegExpStringIterator` object data - #[must_use] - pub fn reg_exp_string_iterator(reg_exp_string_iterator: RegExpStringIterator) -> Self { - Self { - kind: ObjectKind::RegExpStringIterator(reg_exp_string_iterator), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `BigInt` object data - #[must_use] - pub fn big_int(big_int: JsBigInt) -> Self { - Self { - kind: ObjectKind::BigInt(big_int), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Boolean` object data - #[must_use] - pub fn boolean(boolean: bool) -> Self { - Self { - kind: ObjectKind::Boolean(boolean), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `DataView` object data - #[must_use] - pub fn data_view(data_view: DataView) -> Self { - Self { - kind: ObjectKind::DataView(data_view), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Promise` object data - #[must_use] - pub fn promise(promise: Promise) -> Self { - Self { - kind: ObjectKind::Promise(promise), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `ForInIterator` object data - #[must_use] - pub fn for_in_iterator(for_in_iterator: ForInIterator) -> Self { - Self { - kind: ObjectKind::ForInIterator(for_in_iterator), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the ordinary function object data - #[must_use] - pub fn ordinary_function(function: OrdinaryFunction, constructor: bool) -> Self { - let internal_methods = if constructor { - &CONSTRUCTOR_INTERNAL_METHODS - } else { - &FUNCTION_INTERNAL_METHODS - }; - - Self { - internal_methods, - kind: ObjectKind::OrdinaryFunction(function), - } - } - - /// Create the native function object data - #[must_use] - pub fn native_function(function: NativeFunctionObject) -> Self { - let internal_methods = if function.constructor.is_some() { - &NATIVE_CONSTRUCTOR_INTERNAL_METHODS - } else { - &NATIVE_FUNCTION_INTERNAL_METHODS - }; - - Self { - internal_methods, - kind: ObjectKind::NativeFunction(function), - } - } - - /// Create the `BoundFunction` object data - #[must_use] - pub fn bound_function(bound_function: BoundFunction, constructor: bool) -> Self { - Self { - kind: ObjectKind::BoundFunction(bound_function), - internal_methods: if constructor { - &BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS - } else { - &BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS - }, - } - } - - /// Create the `Generator` object data - #[must_use] - pub fn generator(generator: Generator) -> Self { - Self { - kind: ObjectKind::Generator(generator), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Set` object data - #[must_use] - pub fn set(set: OrderedSet) -> Self { - Self { - kind: ObjectKind::Set(set), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `SetIterator` object data - #[must_use] - pub fn set_iterator(set_iterator: SetIterator) -> Self { - Self { - kind: ObjectKind::SetIterator(set_iterator), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `String` object data and reference its exclusive internal methods - #[must_use] - pub fn string(string: JsString) -> Self { - Self { - kind: ObjectKind::String(string), - internal_methods: &STRING_EXOTIC_INTERNAL_METHODS, - } - } - - /// Create the `StringIterator` object data - #[must_use] - pub fn string_iterator(string_iterator: StringIterator) -> Self { - Self { - kind: ObjectKind::StringIterator(string_iterator), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Number` object data - #[must_use] - pub fn number(number: f64) -> Self { - Self { - kind: ObjectKind::Number(number), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Symbol` object data - #[must_use] - pub fn symbol(symbol: JsSymbol) -> Self { - Self { - kind: ObjectKind::Symbol(symbol), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Error` object data - pub(crate) fn error(error: ErrorKind) -> Self { - Self { - kind: ObjectKind::Error(error), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Ordinary` object data - #[must_use] - pub fn ordinary() -> Self { - Self { - kind: ObjectKind::Ordinary, - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Proxy` object data - #[must_use] - pub fn proxy(proxy: Proxy, call: bool, construct: bool) -> Self { - Self { - kind: ObjectKind::Proxy(proxy), - internal_methods: if call && construct { - &PROXY_EXOTIC_INTERNAL_METHODS_ALL - } else if call { - &PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL - } else { - &PROXY_EXOTIC_INTERNAL_METHODS_BASIC - }, - } - } - - /// Create the `Date` object data - #[must_use] - pub fn date(date: Date) -> Self { - Self { - kind: ObjectKind::Date(date), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Arguments` object data - #[must_use] - pub fn arguments(arguments: Arguments) -> Self { - Self { - internal_methods: if matches!(arguments, Arguments::Unmapped) { - &ORDINARY_INTERNAL_METHODS - } else { - &ARGUMENTS_EXOTIC_INTERNAL_METHODS - }, - kind: ObjectKind::Arguments(arguments), - } - } - - /// Creates the `WeakRef` object data - #[must_use] - pub fn weak_ref(weak_ref: WeakGc) -> Self { - Self { - kind: ObjectKind::WeakRef(weak_ref), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `WeakMap` object data - #[must_use] - pub fn weak_map(weak_map: boa_gc::WeakMap) -> Self { - Self { - kind: ObjectKind::WeakMap(weak_map), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `WeakSet` object data - #[must_use] - pub fn weak_set(weak_set: boa_gc::WeakMap) -> Self { - Self { - kind: ObjectKind::WeakSet(weak_set), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `NativeObject` object data - #[must_use] - pub fn native_object(native_object: T) -> Self { - Self { - kind: ObjectKind::NativeObject(Box::new(native_object)), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Creates the `IntegerIndexed` object data - #[must_use] - pub fn integer_indexed(integer_indexed: IntegerIndexed) -> Self { - Self { - kind: ObjectKind::IntegerIndexed(integer_indexed), - internal_methods: &INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS, - } - } - - /// Creates the `ModuleNamespace` object data - #[must_use] - pub fn module_namespace(namespace: ModuleNamespace) -> Self { - Self { - kind: ObjectKind::ModuleNamespace(namespace), - internal_methods: &MODULE_NAMESPACE_EXOTIC_INTERNAL_METHODS, - } - } - - /// Create the `Instant` object data - #[cfg(feature = "temporal")] - #[must_use] - pub fn instant(instant: Instant) -> Self { - Self { - kind: ObjectKind::Instant(instant), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `PlainDateTime` object data - #[cfg(feature = "temporal")] - #[must_use] - pub fn plain_date_time(date_time: PlainDateTime) -> Self { - Self { - kind: ObjectKind::PlainDateTime(date_time), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - /// Create the `PlainDate` object data - #[cfg(feature = "temporal")] - #[must_use] - pub fn plain_date(date: PlainDate) -> Self { - Self { - kind: ObjectKind::PlainDate(date), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `PlainTime` object data - #[cfg(feature = "temporal")] - #[must_use] - pub fn plain_time(time: PlainTime) -> Self { - Self { - kind: ObjectKind::PlainTime(time), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `PlainYearMonth` object data - #[cfg(feature = "temporal")] - #[must_use] - pub fn plain_year_month(year_month: PlainYearMonth) -> Self { - Self { - kind: ObjectKind::PlainYearMonth(year_month), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `PlainMonthDay` object data - #[cfg(feature = "temporal")] - #[must_use] - pub fn plain_month_day(month_day: PlainMonthDay) -> Self { - Self { - kind: ObjectKind::PlainMonthDay(month_day), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `TimeZone` object data - #[cfg(feature = "temporal")] - #[must_use] - pub fn time_zone(time_zone: TimeZone) -> Self { - Self { - kind: ObjectKind::TimeZone(time_zone), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Duration` object data - #[cfg(feature = "temporal")] - #[must_use] - pub fn duration(duration: Duration) -> Self { - Self { - kind: ObjectKind::Duration(duration), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `ZonedDateTime` object data. - #[cfg(feature = "temporal")] - #[must_use] - pub fn zoned_date_time(zoned_date_time: ZonedDateTime) -> Self { - Self { - kind: ObjectKind::ZonedDateTime(zoned_date_time), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } - - /// Create the `Calendar` object data. - #[cfg(feature = "temporal")] - #[must_use] - pub fn calendar(calendar: Calendar) -> Self { - Self { - kind: ObjectKind::Calendar(calendar), - internal_methods: &ORDINARY_INTERNAL_METHODS, - } - } -} - -impl Debug for ObjectKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Self::AsyncFromSyncIterator(_) => "AsyncFromSyncIterator", - Self::AsyncGenerator(_) => "AsyncGenerator", - Self::Array => "Array", - Self::ArrayIterator(_) => "ArrayIterator", - Self::ArrayBuffer(_) => "ArrayBuffer", - Self::SharedArrayBuffer(_) => "SharedArrayBuffer", - Self::ForInIterator(_) => "ForInIterator", - Self::OrdinaryFunction(_) => "Function", - Self::BoundFunction(_) => "BoundFunction", - Self::Generator(_) => "Generator", - Self::NativeFunction { .. } => "NativeFunction", - Self::RegExp(_) => "RegExp", - Self::RegExpStringIterator(_) => "RegExpStringIterator", - Self::Map(_) => "Map", - Self::MapIterator(_) => "MapIterator", - Self::Set(_) => "Set", - Self::SetIterator(_) => "SetIterator", - Self::String(_) => "String", - Self::StringIterator(_) => "StringIterator", - Self::Symbol(_) => "Symbol", - Self::Error(_) => "Error", - Self::Ordinary => "Ordinary", - Self::Proxy(_) => "Proxy", - Self::Boolean(_) => "Boolean", - Self::Number(_) => "Number", - Self::BigInt(_) => "BigInt", - Self::Date(_) => "Date", - Self::Global => "Global", - Self::Arguments(_) => "Arguments", - Self::IntegerIndexed(_) => "TypedArray", - Self::DataView(_) => "DataView", - Self::Promise(_) => "Promise", - Self::WeakRef(_) => "WeakRef", - Self::WeakMap(_) => "WeakMap", - Self::WeakSet(_) => "WeakSet", - Self::ModuleNamespace(_) => "ModuleNamespace", - Self::NativeObject(obj) => (**obj).type_name_of_value(), - #[cfg(feature = "temporal")] - Self::Instant(_) => "Instant", - #[cfg(feature = "temporal")] - Self::PlainDateTime(_) => "PlainDateTime", - #[cfg(feature = "temporal")] - Self::PlainDate(_) => "PlainDate", - #[cfg(feature = "temporal")] - Self::PlainTime(_) => "PlainTime", - #[cfg(feature = "temporal")] - Self::PlainYearMonth(_) => "PlainYearMonth", - #[cfg(feature = "temporal")] - Self::PlainMonthDay(_) => "PlainMonthDay", - #[cfg(feature = "temporal")] - Self::TimeZone(_) => "TimeZone", - #[cfg(feature = "temporal")] - Self::Duration(_) => "Duration", - #[cfg(feature = "temporal")] - Self::ZonedDateTime(_) => "ZonedDateTime", - #[cfg(feature = "temporal")] - Self::Calendar(_) => "Calendar", - }) - } -} - -impl Object { - /// Creates a new `Object` with the specified `ObjectKind`. - pub(crate) fn with_kind(kind: ObjectKind) -> Self { - Self { - kind, - ..Self::default() - } - } - - /// Returns the shape of the object. - #[must_use] - pub const fn shape(&self) -> &Shape { - &self.properties.shape - } - - /// Returns the kind of the object. - #[inline] - #[must_use] - pub const fn kind(&self) -> &ObjectKind { - &self.kind - } - - /// Checks if it's an `AsyncFromSyncIterator` object. - #[inline] - #[must_use] - pub const fn is_async_from_sync_iterator(&self) -> bool { - matches!(self.kind, ObjectKind::AsyncFromSyncIterator(_)) - } - - /// Returns a reference to the `AsyncFromSyncIterator` data on the object. - #[inline] - #[must_use] - pub const fn as_async_from_sync_iterator(&self) -> Option<&AsyncFromSyncIterator> { - match self.kind { - ObjectKind::AsyncFromSyncIterator(ref async_from_sync_iterator) => { - Some(async_from_sync_iterator) - } - _ => None, - } - } - - /// Checks if it's an `AsyncGenerator` object. - #[inline] - #[must_use] - pub const fn is_async_generator(&self) -> bool { - matches!(self.kind, ObjectKind::AsyncGenerator(_)) - } - - /// Returns a reference to the async generator data on the object. - #[inline] - #[must_use] - pub const fn as_async_generator(&self) -> Option<&AsyncGenerator> { - match self.kind { - ObjectKind::AsyncGenerator(ref async_generator) => Some(async_generator), - _ => None, - } - } - - /// Returns a mutable reference to the async generator data on the object. - #[inline] - pub fn as_async_generator_mut(&mut self) -> Option<&mut AsyncGenerator> { - match self.kind { - ObjectKind::AsyncGenerator(ref mut async_generator) => Some(async_generator), - _ => None, - } - } - - /// Checks if the object is a `Array` object. - #[inline] - #[must_use] - pub const fn is_array(&self) -> bool { - matches!(self.kind, ObjectKind::Array) - } - - #[inline] - #[must_use] - pub(crate) const fn has_viewed_array_buffer(&self) -> bool { - self.is_typed_array() || self.is_data_view() - } - - /// Checks if the object is a `DataView` object. - #[inline] - #[must_use] - pub const fn is_data_view(&self) -> bool { - matches!(self.kind, ObjectKind::DataView(_)) - } - - /// Checks if the object is a `ArrayBuffer` object. - #[inline] - #[must_use] - pub const fn is_array_buffer(&self) -> bool { - matches!(self.kind, ObjectKind::ArrayBuffer(_)) - } - - /// Gets the array buffer data if the object is a `ArrayBuffer`. - #[inline] - #[must_use] - pub const fn as_array_buffer(&self) -> Option<&ArrayBuffer> { - match &self.kind { - ObjectKind::ArrayBuffer(buffer) => Some(buffer), - _ => None, - } - } - - /// Gets the shared array buffer data if the object is a `SharedArrayBuffer`. - #[inline] - #[must_use] - pub const fn as_shared_array_buffer(&self) -> Option<&SharedArrayBuffer> { - match &self.kind { - ObjectKind::SharedArrayBuffer(buffer) => Some(buffer), - _ => None, - } - } - - /// Gets the mutable array buffer data if the object is a `ArrayBuffer`. - #[inline] - pub fn as_array_buffer_mut(&mut self) -> Option<&mut ArrayBuffer> { - match &mut self.kind { - ObjectKind::ArrayBuffer(buffer) => Some(buffer), - _ => None, - } - } - - /// Gets the buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`. - #[inline] - #[must_use] - pub(crate) const fn as_buffer(&self) -> Option> { - match &self.kind { - ObjectKind::ArrayBuffer(buffer) => Some(BufferRef::Buffer(buffer)), - ObjectKind::SharedArrayBuffer(buffer) => Some(BufferRef::SharedBuffer(buffer)), - _ => None, - } - } - - /// Gets the mutable buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`. - #[inline] - pub(crate) fn as_buffer_mut(&mut self) -> Option> { - match &mut self.kind { - ObjectKind::ArrayBuffer(buffer) => Some(BufferRefMut::Buffer(buffer)), - ObjectKind::SharedArrayBuffer(buffer) => Some(BufferRefMut::SharedBuffer(buffer)), - _ => None, - } - } - - /// Checks if the object is a `ArrayIterator` object. - #[inline] - #[must_use] - pub const fn is_array_iterator(&self) -> bool { - matches!(self.kind, ObjectKind::ArrayIterator(_)) - } - - /// Gets the array-iterator data if the object is a `ArrayIterator`. - #[inline] - #[must_use] - pub const fn as_array_iterator(&self) -> Option<&ArrayIterator> { - match self.kind { - ObjectKind::ArrayIterator(ref iter) => Some(iter), - _ => None, - } - } - - /// Gets the mutable array-iterator data if the object is a `ArrayIterator`. - #[inline] - pub fn as_array_iterator_mut(&mut self) -> Option<&mut ArrayIterator> { - match &mut self.kind { - ObjectKind::ArrayIterator(iter) => Some(iter), - _ => None, - } - } - - /// Gets the mutable string-iterator data if the object is a `StringIterator`. - #[inline] - pub fn as_string_iterator_mut(&mut self) -> Option<&mut StringIterator> { - match &mut self.kind { - ObjectKind::StringIterator(iter) => Some(iter), - _ => None, - } - } - - /// Gets the mutable regexp-string-iterator data if the object is a `RegExpStringIterator`. - #[inline] - pub fn as_regexp_string_iterator_mut(&mut self) -> Option<&mut RegExpStringIterator> { - match &mut self.kind { - ObjectKind::RegExpStringIterator(iter) => Some(iter), - _ => None, - } - } - - /// Gets the for-in-iterator data if the object is a `ForInIterator`. - #[inline] - #[must_use] - pub const fn as_for_in_iterator(&self) -> Option<&ForInIterator> { - match &self.kind { - ObjectKind::ForInIterator(iter) => Some(iter), - _ => None, - } - } - - /// Gets the mutable for-in-iterator data if the object is a `ForInIterator`. - #[inline] - pub fn as_for_in_iterator_mut(&mut self) -> Option<&mut ForInIterator> { - match &mut self.kind { - ObjectKind::ForInIterator(iter) => Some(iter), - _ => None, - } - } - - /// Checks if the object is a `Map` object. - #[inline] - #[must_use] - pub const fn is_map(&self) -> bool { - matches!(self.kind, ObjectKind::Map(_)) - } - - /// Gets the map data if the object is a `Map`. - #[inline] - #[must_use] - pub const fn as_map(&self) -> Option<&OrderedMap> { - match self.kind { - ObjectKind::Map(ref map) => Some(map), - _ => None, - } - } - - /// Gets the mutable map data if the object is a `Map`. - #[inline] - pub fn as_map_mut(&mut self) -> Option<&mut OrderedMap> { - match &mut self.kind { - ObjectKind::Map(map) => Some(map), - _ => None, - } - } - - /// Checks if the object is a `MapIterator` object. - #[inline] - #[must_use] - pub const fn is_map_iterator(&self) -> bool { - matches!(self.kind, ObjectKind::MapIterator(_)) - } - - /// Gets the map iterator data if the object is a `MapIterator`. - #[inline] - #[must_use] - pub const fn as_map_iterator_ref(&self) -> Option<&MapIterator> { - match &self.kind { - ObjectKind::MapIterator(iter) => Some(iter), - _ => None, - } - } - - /// Gets the mutable map iterator data if the object is a `MapIterator`. - #[inline] - pub fn as_map_iterator_mut(&mut self) -> Option<&mut MapIterator> { - match &mut self.kind { - ObjectKind::MapIterator(iter) => Some(iter), - _ => None, - } - } - - /// Checks if the object is a `Set` object. - #[inline] - #[must_use] - pub const fn is_set(&self) -> bool { - matches!(self.kind, ObjectKind::Set(_)) - } - - /// Gets the set data if the object is a `Set`. - #[inline] - #[must_use] - pub const fn as_set(&self) -> Option<&OrderedSet> { - match self.kind { - ObjectKind::Set(ref set) => Some(set), - _ => None, - } - } - - /// Gets the mutable set data if the object is a `Set`. - #[inline] - pub fn as_set_mut(&mut self) -> Option<&mut OrderedSet> { - match &mut self.kind { - ObjectKind::Set(set) => Some(set), - _ => None, - } - } - - /// Checks if the object is a `SetIterator` object. - #[inline] - #[must_use] - pub const fn is_set_iterator(&self) -> bool { - matches!(self.kind, ObjectKind::SetIterator(_)) - } - - /// Gets the mutable set iterator data if the object is a `SetIterator`. - #[inline] - pub fn as_set_iterator_mut(&mut self) -> Option<&mut SetIterator> { - match &mut self.kind { - ObjectKind::SetIterator(iter) => Some(iter), - _ => None, - } - } - - /// Checks if the object is a `String` object. - #[inline] - #[must_use] - pub const fn is_string(&self) -> bool { - matches!(self.kind, ObjectKind::String(_)) - } - - /// Gets the string data if the object is a `String`. - #[inline] - #[must_use] - pub fn as_string(&self) -> Option { - match self.kind { - ObjectKind::String(ref string) => Some(string.clone()), - _ => None, - } - } - - /// Checks if the object is a `Function` object. - #[inline] - #[must_use] - pub const fn is_ordinary_function(&self) -> bool { - matches!(self.kind, ObjectKind::OrdinaryFunction(_)) - } - - /// Gets the function data if the object is a `Function`. - #[inline] - #[must_use] - pub const fn as_function(&self) -> Option<&OrdinaryFunction> { - match self.kind { - ObjectKind::OrdinaryFunction(ref function) => Some(function), - _ => None, - } - } - - /// Gets the mutable function data if the object is a `Function`. - #[inline] - pub fn as_function_mut(&mut self) -> Option<&mut OrdinaryFunction> { - match self.kind { - ObjectKind::OrdinaryFunction(ref mut function) => Some(function), - _ => None, - } - } - - /// Checks if the object is a `BoundFunction` object. - #[inline] - #[must_use] - pub const fn is_bound_function(&self) -> bool { - matches!(self.kind, ObjectKind::BoundFunction(_)) - } - - /// Gets the bound function data if the object is a `BoundFunction`. - #[inline] - #[must_use] - pub const fn as_bound_function(&self) -> Option<&BoundFunction> { - match self.kind { - ObjectKind::BoundFunction(ref bound_function) => Some(bound_function), - _ => None, - } - } - - /// Checks if the object is a `Generator` object. - #[inline] - #[must_use] - pub const fn is_generator(&self) -> bool { - matches!(self.kind, ObjectKind::Generator(_)) - } - - /// Gets the generator data if the object is a `Generator`. - #[inline] - #[must_use] - pub const fn as_generator(&self) -> Option<&Generator> { - match self.kind { - ObjectKind::Generator(ref generator) => Some(generator), - _ => None, - } - } - - /// Gets the mutable generator data if the object is a `Generator`. - #[inline] - pub fn as_generator_mut(&mut self) -> Option<&mut Generator> { - match self.kind { - ObjectKind::Generator(ref mut generator) => Some(generator), - _ => None, - } - } - - /// Checks if the object is a `Symbol` object. - #[inline] - #[must_use] - pub const fn is_symbol(&self) -> bool { - matches!(self.kind, ObjectKind::Symbol(_)) - } - - /// Gets the error data if the object is a `Symbol`. - #[inline] - #[must_use] - pub fn as_symbol(&self) -> Option { - match self.kind { - ObjectKind::Symbol(ref symbol) => Some(symbol.clone()), - _ => None, - } - } - - /// Checks if the object is a `Error` object. - #[inline] - #[must_use] - pub const fn is_error(&self) -> bool { - matches!(self.kind, ObjectKind::Error(_)) - } - - /// Gets the error data if the object is a `Error`. - #[inline] - #[must_use] - pub const fn as_error(&self) -> Option { - match self.kind { - ObjectKind::Error(e) => Some(e), - _ => None, - } - } - - /// Checks if the object is a `Boolean` object. - #[inline] - #[must_use] - pub const fn is_boolean(&self) -> bool { - matches!(self.kind, ObjectKind::Boolean(_)) - } - - /// Gets the boolean data if the object is a `Boolean`. - #[inline] - #[must_use] - pub const fn as_boolean(&self) -> Option { - match self.kind { - ObjectKind::Boolean(boolean) => Some(boolean), - _ => None, - } - } - - /// Checks if the object is a `Number` object. - #[inline] - #[must_use] - pub const fn is_number(&self) -> bool { - matches!(self.kind, ObjectKind::Number(_)) - } - - /// Gets the number data if the object is a `Number`. - #[inline] - #[must_use] - pub const fn as_number(&self) -> Option { - match self.kind { - ObjectKind::Number(number) => Some(number), - _ => None, - } - } - - /// Checks if the object is a `BigInt` object. - #[inline] - #[must_use] - pub const fn is_bigint(&self) -> bool { - matches!(self.kind, ObjectKind::BigInt(_)) - } - - /// Gets the bigint data if the object is a `BigInt`. - #[inline] - #[must_use] - pub const fn as_bigint(&self) -> Option<&JsBigInt> { - match self.kind { - ObjectKind::BigInt(ref bigint) => Some(bigint), - _ => None, - } - } - - /// Checks if the object is a `Date` object. - #[inline] - #[must_use] - pub const fn is_date(&self) -> bool { - matches!(self.kind, ObjectKind::Date(_)) - } - - /// Gets the date data if the object is a `Date`. - #[inline] - #[must_use] - pub const fn as_date(&self) -> Option<&Date> { - match self.kind { - ObjectKind::Date(ref date) => Some(date), - _ => None, - } - } - - /// Gets the mutable date data if the object is a `Date`. - #[inline] - pub fn as_date_mut(&mut self) -> Option<&mut Date> { - match self.kind { - ObjectKind::Date(ref mut date) => Some(date), - _ => None, - } - } - - /// Checks if it a `RegExp` object. - #[inline] - #[must_use] - pub const fn is_regexp(&self) -> bool { - matches!(self.kind, ObjectKind::RegExp(_)) - } - - /// Gets the regexp data if the object is a regexp. - #[inline] - #[must_use] - pub const fn as_regexp(&self) -> Option<&RegExp> { - match self.kind { - ObjectKind::RegExp(ref regexp) => Some(regexp), - _ => None, - } - } - - /// Gets a mutable reference to the regexp data if the object is a regexp. - #[inline] - #[must_use] - pub fn as_regexp_mut(&mut self) -> Option<&mut RegExp> { - match &mut self.kind { - ObjectKind::RegExp(regexp) => Some(regexp), - _ => None, - } - } - - /// Checks if it a `TypedArray` object. - #[inline] - #[must_use] - pub const fn is_typed_array(&self) -> bool { - matches!(self.kind, ObjectKind::IntegerIndexed(_)) - } - - /// Checks if it a `Uint8Array` object. - #[inline] - #[must_use] - pub const fn is_typed_uint8_array(&self) -> bool { - if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.kind(), TypedArrayKind::Uint8) - } else { - false - } - } - - /// Checks if it a `Int8Array` object. - #[inline] - #[must_use] - pub const fn is_typed_int8_array(&self) -> bool { - if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.kind(), TypedArrayKind::Int8) - } else { - false - } - } - - /// Checks if it a `Uint16Array` object. - #[inline] - #[must_use] - pub const fn is_typed_uint16_array(&self) -> bool { - if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.kind(), TypedArrayKind::Uint16) - } else { - false - } - } - - /// Checks if it a `Int16Array` object. - #[inline] - #[must_use] - pub const fn is_typed_int16_array(&self) -> bool { - if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.kind(), TypedArrayKind::Int16) - } else { - false - } - } - - /// Checks if it a `Uint32Array` object. - #[inline] - #[must_use] - pub const fn is_typed_uint32_array(&self) -> bool { - if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.kind(), TypedArrayKind::Uint32) - } else { - false - } - } - - /// Checks if it a `Int32Array` object. - #[inline] - #[must_use] - pub const fn is_typed_int32_array(&self) -> bool { - if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.kind(), TypedArrayKind::Int32) - } else { - false - } - } - - /// Checks if it a `Float32Array` object. - #[inline] - #[must_use] - pub const fn is_typed_float32_array(&self) -> bool { - if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.kind(), TypedArrayKind::Float32) - } else { - false - } - } - - /// Checks if it a `Float64Array` object. - #[inline] - #[must_use] - pub const fn is_typed_float64_array(&self) -> bool { - if let ObjectKind::IntegerIndexed(ref int) = self.kind { - matches!(int.kind(), TypedArrayKind::Float64) - } else { - false - } - } - - /// Gets the data view data if the object is a `DataView`. - #[inline] - #[must_use] - pub const fn as_data_view(&self) -> Option<&DataView> { - match &self.kind { - ObjectKind::DataView(data_view) => Some(data_view), - _ => None, - } - } - - /// Gets the mutable data view data if the object is a `DataView`. - #[inline] - pub fn as_data_view_mut(&mut self) -> Option<&mut DataView> { - match &mut self.kind { - ObjectKind::DataView(data_view) => Some(data_view), - _ => None, - } - } - - /// Checks if it is an `Arguments` object. - #[inline] - #[must_use] - pub const fn is_arguments(&self) -> bool { - matches!(self.kind, ObjectKind::Arguments(_)) - } - - /// Gets the mapped arguments data if this is a mapped arguments object. - #[inline] - #[must_use] - pub const fn as_mapped_arguments(&self) -> Option<&ParameterMap> { - match self.kind { - ObjectKind::Arguments(Arguments::Mapped(ref args)) => Some(args), - _ => None, - } - } - - /// Gets the mutable mapped arguments data if this is a mapped arguments object. - #[inline] - pub fn as_mapped_arguments_mut(&mut self) -> Option<&mut ParameterMap> { - match self.kind { - ObjectKind::Arguments(Arguments::Mapped(ref mut args)) => Some(args), - _ => None, - } - } - - /// Gets the typed array data (integer indexed object) if this is a typed array. - #[inline] - #[must_use] - pub const fn as_typed_array(&self) -> Option<&IntegerIndexed> { - match self.kind { - ObjectKind::IntegerIndexed(ref integer_indexed_object) => Some(integer_indexed_object), - _ => None, - } - } - - /// Gets the typed array data (integer indexed object) if this is a typed array. - #[inline] - pub fn as_typed_array_mut(&mut self) -> Option<&mut IntegerIndexed> { - match self.kind { - ObjectKind::IntegerIndexed(ref mut integer_indexed_object) => { - Some(integer_indexed_object) - } - _ => None, - } - } - - /// Checks if it an ordinary object. - #[inline] - #[must_use] - pub const fn is_ordinary(&self) -> bool { - matches!(self.kind, ObjectKind::Ordinary) - } - - /// Checks if it's an proxy object. - #[inline] - #[must_use] - pub const fn is_proxy(&self) -> bool { - matches!(self.kind, ObjectKind::Proxy(_)) - } - - /// Gets the proxy data if the object is a `Proxy`. - #[inline] - #[must_use] - pub const fn as_proxy(&self) -> Option<&Proxy> { - match self.kind { - ObjectKind::Proxy(ref proxy) => Some(proxy), - _ => None, - } - } - - /// Gets the mutable proxy data if the object is a `Proxy`. - #[inline] - pub fn as_proxy_mut(&mut self) -> Option<&mut Proxy> { - match self.kind { - ObjectKind::Proxy(ref mut proxy) => Some(proxy), - _ => None, - } - } - - /// Gets the weak map data if the object is a `WeakMap`. - #[inline] - #[must_use] - pub const fn as_weak_map(&self) -> Option<&boa_gc::WeakMap> { - match self.kind { - ObjectKind::WeakMap(ref weak_map) => Some(weak_map), - _ => None, - } - } - - /// Gets the mutable weak map data if the object is a `WeakMap`. - #[inline] - pub fn as_weak_map_mut(&mut self) -> Option<&mut boa_gc::WeakMap> { - match self.kind { - ObjectKind::WeakMap(ref mut weak_map) => Some(weak_map), - _ => None, - } - } - - /// Gets the weak set data if the object is a `WeakSet`. - #[inline] - #[must_use] - pub const fn as_weak_set(&self) -> Option<&boa_gc::WeakMap> { - match self.kind { - ObjectKind::WeakSet(ref weak_set) => Some(weak_set), - _ => None, +impl Object { + pub(crate) fn new(data: T) -> Self { + Self { + data, + properties: PropertyMap::default(), + extensible: true, + private_elements: ThinVec::new(), } } +} - /// Gets the mutable weak set data if the object is a `WeakSet`. - #[inline] - pub fn as_weak_set_mut(&mut self) -> Option<&mut boa_gc::WeakMap> { - match self.kind { - ObjectKind::WeakSet(ref mut weak_set) => Some(weak_set), - _ => None, - } +impl Object { + /// Returns the shape of the object. + #[must_use] + pub const fn shape(&self) -> &Shape { + &self.properties.shape } - /// Returns `true` if it holds a native Rust function. + /// Returns the data of the object. #[inline] #[must_use] - pub const fn is_native_function(&self) -> bool { - matches!(self.kind, ObjectKind::NativeFunction { .. }) - } - - /// Returns this `NativeFunctionObject` if this object contains a `NativeFunctionObject`. - pub(crate) fn as_native_function(&self) -> Option<&NativeFunctionObject> { - match &self.kind { - ObjectKind::NativeFunction(f) => Some(f), - _ => None, - } - } - - /// Returns a mutable reference to this `NativeFunctionObject` if this object contains a - /// `NativeFunctionObject`. - pub(crate) fn as_native_function_mut(&mut self) -> Option<&mut NativeFunctionObject> { - match &mut self.kind { - ObjectKind::NativeFunction(f) => Some(f), - _ => None, - } + pub const fn data(&self) -> &T { + &self.data } /// Gets the prototype instance of this object. @@ -1836,390 +297,204 @@ impl Object { } } - /// Returns `true` if it holds an Rust type that implements `NativeObject`. - #[inline] - #[must_use] - pub const fn is_native_object(&self) -> bool { - matches!(self.kind, ObjectKind::NativeObject(_)) - } - - /// Gets the native object data if the object is a `NativeObject`. - #[inline] - #[must_use] - pub fn as_native_object(&self) -> Option<&dyn NativeObject> { - match self.kind { - ObjectKind::NativeObject(ref object) => Some(object.as_ref()), - _ => None, - } - } - - /// Checks if it is a `Promise` object. - #[inline] - #[must_use] - pub const fn is_promise(&self) -> bool { - matches!(self.kind, ObjectKind::Promise(_)) - } - - /// Gets the promise data if the object is a `Promise`. + /// Returns the properties of the object. #[inline] #[must_use] - pub const fn as_promise(&self) -> Option<&Promise> { - match self.kind { - ObjectKind::Promise(ref promise) => Some(promise), - _ => None, - } - } - - /// Gets the mutable promise data if the object is a `Promise`. - #[inline] - pub fn as_promise_mut(&mut self) -> Option<&mut Promise> { - match self.kind { - ObjectKind::Promise(ref mut promise) => Some(promise), - _ => None, - } + pub const fn properties(&self) -> &PropertyMap { + &self.properties } - /// Gets the `WeakRef` data if the object is a `WeakRef`. #[inline] - #[must_use] - pub const fn as_weak_ref(&self) -> Option<&WeakGc> { - match self.kind { - ObjectKind::WeakRef(ref weak_ref) => Some(weak_ref), - _ => None, - } + pub(crate) fn properties_mut(&mut self) -> &mut PropertyMap { + &mut self.properties } - /// Gets a reference to the module namespace if the object is a `ModuleNamespace`. - #[inline] - #[must_use] - pub const fn as_module_namespace(&self) -> Option<&ModuleNamespace> { - match &self.kind { - ObjectKind::ModuleNamespace(ns) => Some(ns), - _ => None, - } + /// Inserts a field in the object `properties` without checking if it's writable. + /// + /// If a field was already in the object with the same name, then `true` is returned + /// otherwise, `false` is returned. + pub(crate) fn insert(&mut self, key: K, property: P) -> bool + where + K: Into, + P: Into, + { + self.properties.insert(&key.into(), property.into()) } - /// Gets a mutable reference module namespace if the object is a `ModuleNamespace`. + /// Helper function for property removal without checking if it's configurable. + /// + /// Returns `true` if the property was removed, `false` otherwise. #[inline] - pub fn as_module_namespace_mut(&mut self) -> Option<&mut ModuleNamespace> { - match &mut self.kind { - ObjectKind::ModuleNamespace(ns) => Some(ns), - _ => None, - } + pub(crate) fn remove(&mut self, key: &PropertyKey) -> bool { + self.properties.remove(key) } - /// Gets the `TimeZone` data if the object is a `Temporal.TimeZone`. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub const fn as_time_zone(&self) -> Option<&TimeZone> { - match self.kind { - ObjectKind::TimeZone(ref tz) => Some(tz), - _ => None, + /// Append a private element to an object. + pub(crate) fn append_private_element(&mut self, name: PrivateName, element: PrivateElement) { + if let PrivateElement::Accessor { getter, setter } = &element { + for (key, value) in &mut self.private_elements { + if name == *key { + if let PrivateElement::Accessor { + getter: existing_getter, + setter: existing_setter, + } = value + { + if existing_getter.is_none() { + *existing_getter = getter.clone(); + } + if existing_setter.is_none() { + *existing_setter = setter.clone(); + } + return; + } + } + } } - } - - /// Checks if the object is a `TimeZone` object. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub const fn is_time_zone(&self) -> bool { - matches!(self.kind, ObjectKind::TimeZone(_)) - } - /// Gets a mutable reference to `Instant` data if the object is a `Temporal.Instant`. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub fn as_instant_mut(&mut self) -> Option<&mut Instant> { - match &mut self.kind { - ObjectKind::Instant(instant) => Some(instant), - _ => None, - } + self.private_elements.push((name, element)); } +} - /// Gets the `Instant` data if the object is a `Temporal.Instant`. - #[inline] +impl Object { + /// Return `true` if it is a native object and the native type is `T`. #[must_use] - #[cfg(feature = "temporal")] - pub const fn as_instant(&self) -> Option<&Instant> { - match &self.kind { - ObjectKind::Instant(instant) => Some(instant), - _ => None, - } + pub fn is(&self) -> bool { + self.data.is::() } - /// Checks if the object is a `Duration` object. - #[inline] + /// Downcast a reference to the object, + /// if the object is type native object type `T`. #[must_use] - #[cfg(feature = "temporal")] - pub const fn is_duration(&self) -> bool { - matches!(self.kind, ObjectKind::Duration(_)) + pub fn downcast_ref(&self) -> Option<&T> { + self.data.downcast_ref::() } - /// Gets a mutable reference to `Duration` data if the object is a `Temporal.Duration`. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub fn as_duration_mut(&mut self) -> Option<&mut Duration> { - match &mut self.kind { - ObjectKind::Duration(dur) => Some(dur), - _ => None, - } + /// Downcast a mutable reference to the object, + /// if the object is type native object type `T`. + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.data.downcast_mut::() } - /// Gets the `Duration` data if the object is a `Temporal.Duration`. + /// Gets the buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`. #[inline] #[must_use] - #[cfg(feature = "temporal")] - pub const fn as_duration(&self) -> Option<&Duration> { - match &self.kind { - ObjectKind::Duration(dur) => Some(dur), - _ => None, + pub(crate) fn as_buffer(&self) -> Option> { + if let Some(buffer) = self.downcast_ref::() { + Some(BufferRef::Buffer(buffer)) + } else if let Some(buffer) = self.downcast_ref::() { + Some(BufferRef::SharedBuffer(buffer)) + } else { + None } } - /// Checks if object is a `PlainDateTime` object. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub const fn is_plain_date_time(&self) -> bool { - matches!(self.kind, ObjectKind::PlainDateTime(_)) - } - - /// Gets a reference to `PlainDateTime` data if the object is a `Temporal.PlainDateTime`. + /// Gets the mutable buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`. #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub const fn as_plain_date_time(&self) -> Option<&PlainDateTime> { - match &self.kind { - ObjectKind::PlainDateTime(date) => Some(date), - _ => None, + pub(crate) fn as_buffer_mut(&mut self) -> Option> { + // Workaround for Problem case 3 of the current borrow checker. + // https://rust-lang.github.io/rfcs/2094-nll.html#problem-case-3-conditional-control-flow-across-functions + if self.is::() { + match self.downcast_mut::() { + Some(buffer) => return Some(BufferRefMut::Buffer(buffer)), + None => unreachable!(), + } } - } - /// Checks if object is a `PlainDate` object. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub const fn is_plain_date(&self) -> bool { - matches!(self.kind, ObjectKind::PlainDate(_)) + self.downcast_mut::() + .map(|buf| BufferRefMut::SharedBuffer(buf)) } - /// Gets a mutable reference to `PlainDate` data if the object is a `Temporal.PlainDate`. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub fn as_plain_date_mut(&mut self) -> Option<&mut PlainDate> { - match &mut self.kind { - ObjectKind::PlainDate(date) => Some(date), - _ => None, - } + /// Checks if this object is an `Arguments` object. + pub(crate) fn is_arguments(&self) -> bool { + self.is::() || self.is::() } - /// Gets the `PlainDate` data if the object is a `Temporal.PlainDate`. + /// Checks if it a `Uint8Array` object. #[inline] #[must_use] - #[cfg(feature = "temporal")] - pub const fn as_plain_date(&self) -> Option<&PlainDate> { - match &self.kind { - ObjectKind::PlainDate(date) => Some(date), - _ => None, + pub fn is_typed_uint8_array(&self) -> bool { + if let Some(int) = self.downcast_ref::() { + matches!(int.kind(), TypedArrayKind::Uint8) + } else { + false } } - /// Checks if object is a `PlainYearMonth` object. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub const fn is_plain_year_month(&self) -> bool { - matches!(self.kind, ObjectKind::PlainYearMonth(_)) - } - - /// Gets a mutable reference to `PlainYearMonth` data if the object is a `Temporal.PlainYearMonth`. + /// Checks if it a `Int8Array` object. #[inline] #[must_use] - #[cfg(feature = "temporal")] - pub fn as_plain_year_month_mut(&mut self) -> Option<&mut PlainYearMonth> { - match &mut self.kind { - ObjectKind::PlainYearMonth(year_month) => Some(year_month), - _ => None, + pub fn is_typed_int8_array(&self) -> bool { + if let Some(int) = self.downcast_ref::() { + matches!(int.kind(), TypedArrayKind::Int8) + } else { + false } } - /// Gets the `PlainYearMonth` data if the object is a `Temporal.PlainYearMonth`. + /// Checks if it a `Uint16Array` object. #[inline] #[must_use] - #[cfg(feature = "temporal")] - pub const fn as_plain_year_month(&self) -> Option<&PlainYearMonth> { - match &self.kind { - ObjectKind::PlainYearMonth(ym) => Some(ym), - _ => None, + pub fn is_typed_uint16_array(&self) -> bool { + if let Some(int) = self.downcast_ref::() { + matches!(int.kind(), TypedArrayKind::Uint16) + } else { + false } } - /// Checks if object is a `PlainMonthDay` object. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub const fn is_plain_month_day(&self) -> bool { - matches!(self.kind, ObjectKind::PlainMonthDay(_)) - } - - /// Gets the `PlainMonthDay` data if the object is a `Temporal.PlainMonthDay`. + /// Checks if it a `Int16Array` object. #[inline] #[must_use] - #[cfg(feature = "temporal")] - pub const fn as_plain_month_day(&self) -> Option<&PlainMonthDay> { - match &self.kind { - ObjectKind::PlainMonthDay(md) => Some(md), - _ => None, + pub fn is_typed_int16_array(&self) -> bool { + if let Some(int) = self.downcast_ref::() { + matches!(int.kind(), TypedArrayKind::Int16) + } else { + false } } - /// Gets a mutable reference to `PlainMonthDay` data if the object is a `Temporal.PlainMonthDay`. + /// Checks if it a `Uint32Array` object. #[inline] #[must_use] - #[cfg(feature = "temporal")] - pub fn as_plain_month_day_mut(&mut self) -> Option<&mut PlainMonthDay> { - match &mut self.kind { - ObjectKind::PlainMonthDay(month_day) => Some(month_day), - _ => None, + pub fn is_typed_uint32_array(&self) -> bool { + if let Some(int) = self.downcast_ref::() { + matches!(int.kind(), TypedArrayKind::Uint32) + } else { + false } } - /// Gets the `PlainDate` data if the object is a `Temporal.PlainDate`. + /// Checks if it a `Int32Array` object. #[inline] #[must_use] - #[cfg(feature = "temporal")] - pub const fn as_zoned_date_time(&self) -> Option<&ZonedDateTime> { - match &self.kind { - ObjectKind::ZonedDateTime(zdt) => Some(zdt), - _ => None, + pub fn is_typed_int32_array(&self) -> bool { + if let Some(int) = self.downcast_ref::() { + matches!(int.kind(), TypedArrayKind::Int32) + } else { + false } } - /// Checks if the object is a `ZonedDateTime` object. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub const fn is_zoned_date_time(&self) -> bool { - matches!(self.kind, ObjectKind::ZonedDateTime(_)) - } - - /// Checks if the object is a `Calendar` object. - #[inline] - #[must_use] - #[cfg(feature = "temporal")] - pub const fn is_calendar(&self) -> bool { - matches!(self.kind, ObjectKind::Calendar(_)) - } - - /// Gets the `Calendar` data if the object is a `Temporal.Calendar`. + /// Checks if it a `Float32Array` object. #[inline] #[must_use] - #[cfg(feature = "temporal")] - pub const fn as_calendar(&self) -> Option<&Calendar> { - match &self.kind { - ObjectKind::Calendar(calendar) => Some(calendar), - _ => None, - } - } - - /// Return `true` if it is a native object and the native type is `T`. - #[must_use] - pub fn is(&self) -> bool - where - T: NativeObject, - { - match self.kind { - ObjectKind::NativeObject(ref object) => object.deref().as_any().is::(), - _ => false, - } - } - - /// Downcast a reference to the object, - /// if the object is type native object type `T`. - #[must_use] - pub fn downcast_ref(&self) -> Option<&T> - where - T: NativeObject, - { - match self.kind { - ObjectKind::NativeObject(ref object) => object.deref().as_any().downcast_ref::(), - _ => None, - } - } - - /// Downcast a mutable reference to the object, - /// if the object is type native object type `T`. - pub fn downcast_mut(&mut self) -> Option<&mut T> - where - T: NativeObject, - { - match self.kind { - ObjectKind::NativeObject(ref mut object) => { - object.deref_mut().as_mut_any().downcast_mut::() - } - _ => None, + pub fn is_typed_float32_array(&self) -> bool { + if let Some(int) = self.downcast_ref::() { + matches!(int.kind(), TypedArrayKind::Float32) + } else { + false } } - /// Returns the properties of the object. + /// Checks if it a `Float64Array` object. #[inline] #[must_use] - pub const fn properties(&self) -> &PropertyMap { - &self.properties - } - - #[inline] - pub(crate) fn properties_mut(&mut self) -> &mut PropertyMap { - &mut self.properties - } - - /// Inserts a field in the object `properties` without checking if it's writable. - /// - /// If a field was already in the object with the same name, then `true` is returned - /// otherwise, `false` is returned. - pub(crate) fn insert(&mut self, key: K, property: P) -> bool - where - K: Into, - P: Into, - { - self.properties.insert(&key.into(), property.into()) - } - - /// Helper function for property removal without checking if it's configurable. - /// - /// Returns `true` if the property was removed, `false` otherwise. - #[inline] - pub(crate) fn remove(&mut self, key: &PropertyKey) -> bool { - self.properties.remove(key) - } - - /// Append a private element to an object. - pub(crate) fn append_private_element(&mut self, name: PrivateName, element: PrivateElement) { - if let PrivateElement::Accessor { getter, setter } = &element { - for (key, value) in &mut self.private_elements { - if name == *key { - if let PrivateElement::Accessor { - getter: existing_getter, - setter: existing_setter, - } = value - { - if existing_getter.is_none() { - *existing_getter = getter.clone(); - } - if existing_setter.is_none() { - *existing_setter = setter.clone(); - } - return; - } - } - } + pub fn is_typed_float64_array(&self) -> bool { + if let Some(int) = self.downcast_ref::() { + matches!(int.kind(), TypedArrayKind::Float64) + } else { + false } - - self.private_elements.push((name, element)); } } @@ -2336,11 +611,11 @@ impl<'realm> FunctionObjectBuilder<'realm> { #[must_use] pub fn build(self) -> JsFunction { let object = self.realm.intrinsics().templates().function().create( - ObjectData::native_function(NativeFunctionObject { + NativeFunctionObject { f: self.function, constructor: self.constructor, realm: Some(self.realm.clone()), - }), + }, vec![self.length.into(), self.name.into()], ); @@ -2400,7 +675,7 @@ impl<'ctx> ObjectInitializer<'ctx> { let object = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().constructors().object().prototype(), - ObjectData::native_object(data), + data, ); Self { context, object } } @@ -2411,11 +686,8 @@ impl<'ctx> ObjectInitializer<'ctx> { proto: JsObject, context: &'ctx mut Context, ) -> Self { - let object = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - proto, - ObjectData::native_object(data), - ); + let object = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, data); Self { context, object } } @@ -2502,9 +774,9 @@ impl<'ctx> ObjectInitializer<'ctx> { pub struct ConstructorBuilder<'ctx> { context: &'ctx mut Context, function: NativeFunction, - constructor_object: Object, + constructor_object: Object, has_prototype_property: bool, - prototype: Object, + prototype: Object, name: JsString, length: usize, callable: bool, @@ -2521,13 +793,13 @@ impl<'ctx> ConstructorBuilder<'ctx> { context, function, constructor_object: Object { - kind: ObjectKind::Ordinary, + data: OrdinaryObject, properties: PropertyMap::default(), extensible: true, private_elements: ThinVec::new(), }, prototype: Object { - kind: ObjectKind::Ordinary, + data: OrdinaryObject, properties: PropertyMap::default(), extensible: true, private_elements: ThinVec::new(), @@ -2786,16 +1058,19 @@ impl<'ctx> ConstructorBuilder<'ctx> { }; let constructor = { - let mut constructor = self.constructor_object; + let mut constructor = Object { + properties: self.constructor_object.properties, + extensible: self.constructor_object.extensible, + private_elements: self.constructor_object.private_elements, + data: NativeFunctionObject { + f: self.function, + constructor: self.kind, + realm: Some(self.context.realm().clone()), + }, + }; + constructor.insert(utf16!("length"), length); constructor.insert(utf16!("name"), name); - let data = ObjectData::native_function(NativeFunctionObject { - f: self.function, - constructor: self.kind, - realm: Some(self.context.realm().clone()), - }); - - constructor.kind = data.kind; if let Some(proto) = self.custom_prototype.take() { constructor.set_prototype(proto); @@ -2820,7 +1095,8 @@ impl<'ctx> ConstructorBuilder<'ctx> { ); } - JsObject::from_object_and_vtable(constructor, data.internal_methods) + let internal_methods = constructor.data.internal_methods(); + JsObject::from_object_and_vtable(constructor, internal_methods) }; { diff --git a/boa_engine/src/object/operations.rs b/boa_engine/src/object/operations.rs index fd102893897..b2674f1e17f 100644 --- a/boa_engine/src/object/operations.rs +++ b/boa_engine/src/object/operations.rs @@ -1,7 +1,11 @@ use crate::{ - builtins::{function::ClassFieldDefinition, Array}, + builtins::{ + function::{BoundFunction, ClassFieldDefinition, OrdinaryFunction}, + Array, Proxy, + }, context::intrinsics::{StandardConstructor, StandardConstructors}, error::JsNativeError, + native_function::NativeFunctionObject, object::{JsObject, PrivateElement, PrivateName, CONSTRUCTOR, PROTOTYPE}, property::{PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey, PropertyNameKind}, realm::Realm, @@ -10,7 +14,7 @@ use crate::{ Context, JsResult, JsSymbol, JsValue, }; -use super::{internal_methods::InternalMethodContext, ObjectKind}; +use super::internal_methods::InternalMethodContext; /// Object integrity level. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -746,7 +750,7 @@ impl JsObject { // 3. If argument is a Proxy exotic object, then let object = self.borrow(); - if let Some(proxy) = object.as_proxy() { + if let Some(proxy) = object.downcast_ref::() { // a. If argument.[[ProxyHandler]] is null, throw a TypeError exception. // b. Let target be argument.[[ProxyTarget]]. let (target, _) = proxy.try_data()?; @@ -764,21 +768,21 @@ impl JsObject { /// [spec]: https://tc39.es/ecma262/#sec-getfunctionrealm pub(crate) fn get_function_realm(&self, context: &mut Context) -> JsResult { let constructor = self.borrow(); - if let Some(fun) = constructor.as_function() { + if let Some(fun) = constructor.downcast_ref::() { return Ok(fun.realm().clone()); } - if let ObjectKind::NativeFunction(f) = constructor.kind() { + if let Some(f) = constructor.downcast_ref::() { return Ok(f.realm.clone().unwrap_or_else(|| context.realm().clone())); } - if let Some(bound) = constructor.as_bound_function() { + if let Some(bound) = constructor.downcast_ref::() { let fun = bound.target_function().clone(); drop(constructor); return fun.get_function_realm(context); } - if let Some(proxy) = constructor.as_proxy() { + if let Some(proxy) = constructor.downcast_ref::() { let (fun, _) = proxy.try_data()?; drop(constructor); return fun.get_function_realm(context); @@ -1074,9 +1078,8 @@ impl JsObject { constructor: &Self, context: &mut Context, ) -> JsResult<()> { - let constructor_borrow = constructor.borrow(); - let constructor_function = constructor_borrow - .as_function() + let constructor_function = constructor + .downcast_ref::() .expect("class constructor must be function object"); // 1. Let methods be the value of constructor.[[PrivateMethods]]. @@ -1296,7 +1299,7 @@ impl JsValue { }; // 2. If C has a [[BoundTargetFunction]] internal slot, then - if let Some(bound_function) = function.borrow().as_bound_function() { + if let Some(bound_function) = function.downcast_ref::() { // a. Let BC be C.[[BoundTargetFunction]]. // b. Return ? InstanceofOperator(O, BC). return Self::instance_of( From fa4d682d1ddb370eabb3f1fd746db8784d196da5 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 04:14:52 -0600 Subject: [PATCH 03/15] Add utility macros --- boa_macros/src/lib.rs | 64 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/boa_macros/src/lib.rs b/boa_macros/src/lib.rs index 42b32d4c891..cd4a40796f5 100644 --- a/boa_macros/src/lib.rs +++ b/boa_macros/src/lib.rs @@ -169,13 +169,61 @@ pub fn utf16(input: TokenStream) -> TokenStream { } decl_derive! { - [Trace, attributes(unsafe_ignore_trace)] => + [Trace, attributes(boa_gc, unsafe_ignore_trace)] => /// Derive the `Trace` trait. derive_trace } /// Derives the `Trace` trait. fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { + struct EmptyTrace { + copy: bool, + } + + impl Parse for EmptyTrace { + fn parse(input: ParseStream<'_>) -> syn::Result { + let i: Ident = input.parse()?; + + if i != "empty_trace" && i != "unsafe_empty_trace" { + let msg = format!( + "expected token \"empty_trace\" or \"unsafe_empty_trace\", found {i:?}" + ); + return Err(syn::Error::new_spanned(i.clone(), msg)); + } + + Ok(Self { + copy: i == "empty_trace", + }) + } + } + + for attr in &s.ast().attrs { + if attr.path().is_ident("boa_gc") { + let trace = match attr.parse_args::() { + Ok(t) => t, + Err(e) => return e.into_compile_error(), + }; + + if trace.copy { + s.add_where_predicate(syn::parse_quote!(Self: Copy)); + } + + return s.unsafe_bound_impl( + quote!(::boa_gc::Trace), + quote! { + #[inline(always)] + unsafe fn trace(&self) {} + #[inline(always)] + fn trace_non_roots(&self) {} + #[inline] + fn run_finalizer(&self) { + ::boa_gc::Finalize::finalize(self) + } + }, + ); + } + } + s.filter(|bi| { !bi.ast() .attrs @@ -228,6 +276,8 @@ fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { let drop_impl = s.unbound_impl( quote!(::core::ops::Drop), quote! { + #[allow(clippy::inline_always)] + #[inline(always)] fn drop(&mut self) { if ::boa_gc::finalizer_safe() { ::boa_gc::Finalize::finalize(self); @@ -254,6 +304,18 @@ fn derive_finalize(s: Structure<'_>) -> proc_macro2::TokenStream { s.unbound_impl(quote!(::boa_gc::Finalize), quote!()) } +decl_derive! { + [JsData] => + /// Derive the `JsData` trait. + derive_js_data +} + +/// Derives the `JsData` trait. +#[allow(clippy::needless_pass_by_value)] +fn derive_js_data(s: Structure<'_>) -> proc_macro2::TokenStream { + s.unbound_impl(quote!(::boa_engine::JsData), quote!()) +} + /// Derives the `TryFromJs` trait, with the `#[boa()]` attribute. /// /// # Panics From 0d3c51552b3aa63c67c7e8f164d9d9fcf024e72d Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 04:17:25 -0600 Subject: [PATCH 04/15] Migrate builtins to erased objects --- boa_engine/src/bigint.rs | 7 +- .../src/builtins/array/array_iterator.rs | 20 +- boa_engine/src/builtins/array/mod.rs | 280 ++++- boa_engine/src/builtins/array_buffer/mod.rs | 49 +- .../src/builtins/array_buffer/shared.rs | 36 +- boa_engine/src/builtins/array_buffer/utils.rs | 7 +- .../src/builtins/async_generator/mod.rs | 75 +- boa_engine/src/builtins/atomics/mod.rs | 29 +- boa_engine/src/builtins/bigint/mod.rs | 2 +- boa_engine/src/builtins/boolean/mod.rs | 15 +- boa_engine/src/builtins/builder.rs | 641 ++++++++++ boa_engine/src/builtins/dataview/mod.rs | 51 +- boa_engine/src/builtins/date/mod.rs | 51 +- boa_engine/src/builtins/error/aggregate.rs | 4 +- boa_engine/src/builtins/error/eval.rs | 4 +- boa_engine/src/builtins/error/mod.rs | 10 +- boa_engine/src/builtins/error/range.rs | 4 +- boa_engine/src/builtins/error/reference.rs | 4 +- boa_engine/src/builtins/error/syntax.rs | 4 +- boa_engine/src/builtins/error/type.rs | 6 +- boa_engine/src/builtins/error/uri.rs | 4 +- boa_engine/src/builtins/eval/mod.rs | 4 +- boa_engine/src/builtins/function/arguments.rs | 375 +++++- boa_engine/src/builtins/function/bound.rs | 179 +++ boa_engine/src/builtins/function/mod.rs | 296 +++-- boa_engine/src/builtins/generator/mod.rs | 58 +- boa_engine/src/builtins/intl/collator/mod.rs | 34 +- .../src/builtins/intl/date_time_format.rs | 13 +- .../src/builtins/intl/list_format/mod.rs | 25 +- boa_engine/src/builtins/intl/locale/mod.rs | 25 +- boa_engine/src/builtins/intl/options.rs | 6 +- .../src/builtins/intl/plural_rules/mod.rs | 19 +- .../src/builtins/intl/segmenter/iterator.rs | 24 +- boa_engine/src/builtins/intl/segmenter/mod.rs | 40 +- .../src/builtins/intl/segmenter/segments.rs | 22 +- .../iterable/async_from_sync_iterator.rs | 38 +- boa_engine/src/builtins/iterable/mod.rs | 6 +- boa_engine/src/builtins/json/mod.rs | 18 +- boa_engine/src/builtins/map/map_iterator.rs | 24 +- boa_engine/src/builtins/map/mod.rs | 36 +- boa_engine/src/builtins/map/ordered_map.rs | 19 +- boa_engine/src/builtins/mod.rs | 638 +--------- boa_engine/src/builtins/number/mod.rs | 15 +- .../src/builtins/object/for_in_iterator.rs | 15 +- boa_engine/src/builtins/object/mod.rs | 78 +- boa_engine/src/builtins/promise/mod.rs | 76 +- boa_engine/src/builtins/proxy/mod.rs | 1106 ++++++++++++++++- boa_engine/src/builtins/reflect/mod.rs | 2 +- boa_engine/src/builtins/regexp/mod.rs | 140 +-- .../builtins/regexp/regexp_string_iterator.rs | 15 +- boa_engine/src/builtins/regexp/tests.rs | 6 +- boa_engine/src/builtins/set/mod.rs | 99 +- boa_engine/src/builtins/set/ordered_set.rs | 16 +- boa_engine/src/builtins/set/set_iterator.rs | 25 +- boa_engine/src/builtins/string/mod.rs | 20 +- .../src/builtins/string/string_iterator.rs | 17 +- boa_engine/src/builtins/symbol/mod.rs | 6 +- .../src/builtins/temporal/calendar/mod.rs | 499 ++++---- .../src/builtins/temporal/calendar/object.rs | 9 +- .../src/builtins/temporal/duration/mod.rs | 108 +- .../src/builtins/temporal/instant/mod.rs | 140 ++- .../src/builtins/temporal/plain_date/mod.rs | 28 +- .../builtins/temporal/plain_date_time/mod.rs | 6 +- .../builtins/temporal/plain_month_day/mod.rs | 10 +- .../builtins/temporal/plain_year_month/mod.rs | 10 +- .../src/builtins/temporal/time_zone/mod.rs | 50 +- .../builtins/temporal/zoned_date_time/mod.rs | 5 +- .../src/builtins/typed_array/builtin.rs | 356 +++--- .../typed_array/integer_indexed_object.rs | 458 ++++++- boa_engine/src/builtins/typed_array/mod.rs | 38 +- boa_engine/src/builtins/weak/weak_ref.rs | 23 +- boa_engine/src/builtins/weak_map/mod.rs | 74 +- boa_engine/src/builtins/weak_set/mod.rs | 63 +- boa_engine/src/bytecompiler/function.rs | 2 +- boa_engine/src/class.rs | 6 +- boa_engine/src/context/intrinsics.rs | 26 +- .../runtime/declarative/function.rs | 6 +- boa_engine/src/error.rs | 7 +- boa_engine/src/lib.rs | 26 +- boa_engine/src/module/mod.rs | 61 +- .../namespace.rs} | 135 +- boa_engine/src/native_function.rs | 150 ++- .../src/object/internal_methods/arguments.rs | 249 ---- .../src/object/internal_methods/array.rs | 258 ---- .../object/internal_methods/bound_function.rs | 109 -- .../src/object/internal_methods/function.rs | 354 ------ .../internal_methods/immutable_prototype.rs | 2 +- .../internal_methods/integer_indexed.rs | 439 ------- boa_engine/src/object/internal_methods/mod.rs | 16 +- .../src/object/internal_methods/proxy.rs | 1025 --------------- .../src/object/internal_methods/string.rs | 39 +- .../src/object/shape/shared_shape/template.rs | 19 +- boa_engine/src/string/mod.rs | 11 +- boa_engine/src/symbol.rs | 16 +- boa_engine/src/value/display.rs | 269 ++-- boa_engine/src/value/mod.rs | 27 +- 96 files changed, 4946 insertions(+), 5021 deletions(-) create mode 100644 boa_engine/src/builtins/builder.rs create mode 100644 boa_engine/src/builtins/function/bound.rs rename boa_engine/src/{object/internal_methods/module_namespace.rs => module/namespace.rs} (70%) delete mode 100644 boa_engine/src/object/internal_methods/arguments.rs delete mode 100644 boa_engine/src/object/internal_methods/array.rs delete mode 100644 boa_engine/src/object/internal_methods/bound_function.rs delete mode 100644 boa_engine/src/object/internal_methods/function.rs delete mode 100644 boa_engine/src/object/internal_methods/integer_indexed.rs delete mode 100644 boa_engine/src/object/internal_methods/proxy.rs diff --git a/boa_engine/src/bigint.rs b/boa_engine/src/bigint.rs index 80b3691ed1b..783ab10efe2 100644 --- a/boa_engine/src/bigint.rs +++ b/boa_engine/src/bigint.rs @@ -1,6 +1,7 @@ //! Boa's implementation of ECMAScript's bigint primitive type. -use crate::{builtins::Number, error::JsNativeError, JsResult}; +use crate::{builtins::Number, error::JsNativeError, JsData, JsResult}; +use boa_gc::{Finalize, Trace}; use num_integer::Integer; use num_traits::{pow::Pow, FromPrimitive, One, ToPrimitive, Zero}; use std::{ @@ -17,7 +18,9 @@ use serde::{Deserialize, Serialize}; /// JavaScript bigint primitive rust type. #[cfg_attr(feature = "deser", derive(Serialize, Deserialize))] -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Trace, Finalize, JsData)] +// Safety: `JsBigInt` doesn't contain any traceable types. +#[boa_gc(unsafe_empty_trace)] pub struct JsBigInt { inner: Rc, } diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index ca78d810170..34efd14faf5 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -7,16 +7,17 @@ use crate::{ builtins::{ - iterable::create_iter_result_object, Array, BuiltInBuilder, IntrinsicObject, JsValue, + iterable::create_iter_result_object, typed_array::TypedArray, Array, BuiltInBuilder, + IntrinsicObject, JsValue, }, context::intrinsics::Intrinsics, error::JsNativeError, js_string, - object::{JsObject, ObjectData}, + object::JsObject, property::{Attribute, PropertyNameKind}, realm::Realm, symbol::JsSymbol, - Context, JsResult, + Context, JsData, JsResult, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -27,7 +28,7 @@ use boa_profiler::Profiler; /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects -#[derive(Debug, Clone, Finalize, Trace)] +#[derive(Debug, Clone, Finalize, Trace, JsData)] pub struct ArrayIterator { array: JsObject, next_index: u64, @@ -88,7 +89,7 @@ impl ArrayIterator { let array_iterator = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().objects().iterator_prototypes().array(), - ObjectData::array_iterator(Self::new(array, kind)), + Self::new(array, kind), ); array_iterator.into() } @@ -102,10 +103,9 @@ impl ArrayIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%arrayiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut array_iterator = this.as_object().map(JsObject::borrow_mut); - let array_iterator = array_iterator - .as_mut() - .and_then(|obj| obj.as_array_iterator_mut()) + let mut array_iterator = this + .as_object() + .and_then(|o| o.downcast_mut::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an ArrayIterator"))?; let index = array_iterator.next_index; if array_iterator.done { @@ -116,7 +116,7 @@ impl ArrayIterator { )); } - let len = if let Some(f) = array_iterator.array.borrow().as_typed_array() { + let len = if let Some(f) = array_iterator.array.downcast_ref::() { if f.is_detached() { return Err(JsNativeError::typ() .with_message( diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index d212f8e2136..47b5e90d7db 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -9,6 +9,7 @@ //! [spec]: https://tc39.es/ecma262/#sec-array-objects //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +use boa_gc::{Finalize, Trace}; use boa_macros::utf16; use boa_profiler::Profiler; use thin_vec::ThinVec; @@ -21,10 +22,14 @@ use crate::{ error::JsNativeError, js_string, object::{ - internal_methods::{get_prototype_from_constructor, InternalMethodContext}, - JsObject, ObjectData, CONSTRUCTOR, + internal_methods::{ + get_prototype_from_constructor, ordinary_define_own_property, + ordinary_get_own_property, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, + InternalMethodContext + }, + JsData, JsObject, CONSTRUCTOR, }, - property::{Attribute, PropertyDescriptor, PropertyNameKind}, + property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, realm::Realm, string::common::StaticJsStrings, symbol::JsSymbol, @@ -48,9 +53,27 @@ pub(crate) enum Direction { } /// JavaScript `Array` built-in implementation. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Trace, Finalize)] +#[boa_gc(empty_trace)] pub(crate) struct Array; +/// Definitions of the internal object methods for array exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects +pub(crate) static ARRAY_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { + __define_own_property__: array_exotic_define_own_property, + ..ORDINARY_INTERNAL_METHODS +}; + +impl JsData for Array { + fn internal_methods(&self) -> &'static InternalObjectMethods { + &ARRAY_EXOTIC_INTERNAL_METHODS + } +} + impl IntrinsicObject for Array { fn init(realm: &Realm) { let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); @@ -311,7 +334,7 @@ impl Array { .intrinsics() .templates() .array() - .create(ObjectData::array(), vec![JsValue::new(length)])); + .create(Array, vec![JsValue::new(length)])); } // 7. Return A. @@ -333,17 +356,17 @@ impl Array { .intrinsics() .templates() .array() - .create(ObjectData::array(), vec![JsValue::new(length)])); + .create(Array, vec![JsValue::new(length)])); } let array = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::array(), + Array, ); // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). - crate::object::internal_methods::ordinary_define_own_property( + ordinary_define_own_property( &array, &utf16!("length").into(), PropertyDescriptor::builder() @@ -384,7 +407,7 @@ impl Array { .templates() .array() .create_with_indexed_properties( - ObjectData::array(), + Array, vec![JsValue::new(length)], elements, ) @@ -1036,7 +1059,7 @@ impl Array { if let Some(func) = func.as_callable() { func.call(&array.into(), &[], context) } else { - crate::builtins::object::Object::to_string(&array.into(), &[], context) + crate::builtins::object::OrdinaryObject::to_string(&array.into(), &[], context) } } @@ -3401,3 +3424,240 @@ pub(crate) fn find_via_predicate( // 5. Return the Record { [[Index]]: -1𝔽, [[Value]]: undefined } Ok((JsValue::new(-1), JsValue::undefined())) } + +/// Define an own property for an array exotic object. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc +fn array_exotic_define_own_property( + obj: &JsObject, + key: &PropertyKey, + desc: PropertyDescriptor, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + // 1. Assert: IsPropertyKey(P) is true. + match key { + // 2. If P is "length", then + PropertyKey::String(ref s) if s == utf16!("length") => { + // a. Return ? ArraySetLength(A, Desc). + + array_set_length(obj, desc, context) + } + // 3. Else if P is an array index, then + PropertyKey::Index(index) => { + let index = index.get(); + + // a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length"). + let old_len_desc = ordinary_get_own_property(obj, &utf16!("length").into(), context)? + .expect("the property descriptor must exist"); + + // b. Assert: ! IsDataDescriptor(oldLenDesc) is true. + debug_assert!(old_len_desc.is_data_descriptor()); + + // c. Assert: oldLenDesc.[[Configurable]] is false. + debug_assert!(!old_len_desc.expect_configurable()); + + // d. Let oldLen be oldLenDesc.[[Value]]. + // e. Assert: oldLen is a non-negative integral Number. + // f. Let index be ! ToUint32(P). + let old_len = old_len_desc + .expect_value() + .to_u32(context) + .expect("this ToUint32 call must not fail"); + + // g. If index ≥ oldLen and oldLenDesc.[[Writable]] is false, return false. + if index >= old_len && !old_len_desc.expect_writable() { + return Ok(false); + } + + // h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc). + if ordinary_define_own_property(obj, key, desc, context)? { + // j. If index ≥ oldLen, then + if index >= old_len { + // i. Set oldLenDesc.[[Value]] to index + 1𝔽. + let old_len_desc = PropertyDescriptor::builder() + .value(index + 1) + .maybe_writable(old_len_desc.writable()) + .maybe_enumerable(old_len_desc.enumerable()) + .maybe_configurable(old_len_desc.configurable()); + + // ii. Set succeeded to OrdinaryDefineOwnProperty(A, "length", oldLenDesc). + let succeeded = ordinary_define_own_property( + obj, + &utf16!("length").into(), + old_len_desc.into(), + context, + )?; + + // iii. Assert: succeeded is true. + debug_assert!(succeeded); + } + + // k. Return true. + Ok(true) + } else { + // i. If succeeded is false, return false. + Ok(false) + } + } + // 4. Return OrdinaryDefineOwnProperty(A, P, Desc). + _ => ordinary_define_own_property(obj, key, desc, context), + } +} + +/// Abstract operation `ArraySetLength ( A, Desc )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arraysetlength +fn array_set_length( + obj: &JsObject, + desc: PropertyDescriptor, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + // 1. If Desc.[[Value]] is absent, then + let Some(new_len_val) = desc.value() else { + // a. Return OrdinaryDefineOwnProperty(A, "length", Desc). + return ordinary_define_own_property(obj, &utf16!("length").into(), desc, context); + }; + + // 3. Let newLen be ? ToUint32(Desc.[[Value]]). + let new_len = new_len_val.to_u32(context)?; + + // 4. Let numberLen be ? ToNumber(Desc.[[Value]]). + let number_len = new_len_val.to_number(context)?; + + // 5. If SameValueZero(newLen, numberLen) is false, throw a RangeError exception. + #[allow(clippy::float_cmp)] + if f64::from(new_len) != number_len { + return Err(JsNativeError::range() + .with_message("bad length for array") + .into()); + } + + // 2. Let newLenDesc be a copy of Desc. + // 6. Set newLenDesc.[[Value]] to newLen. + let mut new_len_desc = PropertyDescriptor::builder() + .value(new_len) + .maybe_writable(desc.writable()) + .maybe_enumerable(desc.enumerable()) + .maybe_configurable(desc.configurable()); + + // 7. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length"). + let old_len_desc = ordinary_get_own_property(obj, &utf16!("length").into(), context)? + .expect("the property descriptor must exist"); + + // 8. Assert: ! IsDataDescriptor(oldLenDesc) is true. + debug_assert!(old_len_desc.is_data_descriptor()); + + // 9. Assert: oldLenDesc.[[Configurable]] is false. + debug_assert!(!old_len_desc.expect_configurable()); + + // 10. Let oldLen be oldLenDesc.[[Value]]. + let old_len = old_len_desc.expect_value(); + + // 11. If newLen ≥ oldLen, then + if new_len >= old_len.to_u32(context)? { + // a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc). + return ordinary_define_own_property( + obj, + &utf16!("length").into(), + new_len_desc.build(), + context, + ); + } + + // 12. If oldLenDesc.[[Writable]] is false, return false. + if !old_len_desc.expect_writable() { + return Ok(false); + } + + // 13. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true. + let new_writable = if new_len_desc.inner().writable().unwrap_or(true) { + true + } + // 14. Else, + else { + // a. NOTE: Setting the [[Writable]] attribute to false is deferred in case any + // elements cannot be deleted. + // c. Set newLenDesc.[[Writable]] to true. + new_len_desc = new_len_desc.writable(true); + + // b. Let newWritable be false. + false + }; + + // 15. Let succeeded be ! OrdinaryDefineOwnProperty(A, "length", newLenDesc). + // 16. If succeeded is false, return false. + if !ordinary_define_own_property( + obj, + &utf16!("length").into(), + new_len_desc.clone().build(), + context, + ) + .expect("this OrdinaryDefineOwnProperty call must not fail") + { + return Ok(false); + } + + // 17. For each own property key P of A that is an array index, whose numeric value is + // greater than or equal to newLen, in descending numeric index order, do + let ordered_keys = { + let mut keys: Vec<_> = obj + .borrow() + .properties + .index_property_keys() + .filter(|idx| new_len <= *idx && *idx < u32::MAX) + .collect(); + keys.sort_unstable_by(|x, y| y.cmp(x)); + keys + }; + + for index in ordered_keys { + // a. Let deleteSucceeded be ! A.[[Delete]](P). + // b. If deleteSucceeded is false, then + if !obj.__delete__(&index.into(), context)? { + // i. Set newLenDesc.[[Value]] to ! ToUint32(P) + 1𝔽. + new_len_desc = new_len_desc.value(index + 1); + + // ii. If newWritable is false, set newLenDesc.[[Writable]] to false. + if !new_writable { + new_len_desc = new_len_desc.writable(false); + } + + // iii. Perform ! OrdinaryDefineOwnProperty(A, "length", newLenDesc). + ordinary_define_own_property( + obj, + &utf16!("length").into(), + new_len_desc.build(), + context, + ) + .expect("this OrdinaryDefineOwnProperty call must not fail"); + + // iv. Return false. + return Ok(false); + } + } + + // 18. If newWritable is false, then + if !new_writable { + // a. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length", + // PropertyDescriptor { [[Writable]]: false }). + let succeeded = ordinary_define_own_property( + obj, + &utf16!("length").into(), + PropertyDescriptor::builder().writable(false).build(), + context, + ) + .expect("this OrdinaryDefineOwnProperty call must not fail"); + + // b. Assert: succeeded is true. + debug_assert!(succeeded); + } + + // 19. Return true. + Ok(true) +} diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 00979161f79..6d896d4f211 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -23,20 +23,22 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::common::StaticJsStrings, symbol::JsSymbol, value::IntegerOrInfinity, - Context, JsArgs, JsResult, JsString, JsValue, + Context, JsArgs, JsData, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use self::utils::{SliceRef, SliceRefMut}; -use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; +use super::{ + typed_array::TypedArray, BuiltInBuilder, BuiltInConstructor, DataView, IntrinsicObject, +}; #[derive(Debug, Clone, Copy)] pub(crate) enum BufferRef<'a> { @@ -73,7 +75,7 @@ impl BufferRefMut<'_> { } /// The internal representation of an `ArrayBuffer` object. -#[derive(Debug, Clone, Trace, Finalize)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct ArrayBuffer { /// The `[[ArrayBufferData]]` internal slot. data: Option>, @@ -236,7 +238,7 @@ impl ArrayBuffer { Ok(args .get_or_undefined(0) .as_object() - .map(|obj| obj.borrow().has_viewed_array_buffer()) + .map(|obj| obj.is::() || obj.is::()) .unwrap_or_default() .into()) } @@ -254,14 +256,14 @@ impl ArrayBuffer { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("ArrayBuffer.byteLength called with non-object value") - })?; - let obj = obj.borrow(); // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - let buf = obj.as_array_buffer().ok_or_else(|| { - JsNativeError::typ().with_message("ArrayBuffer.byteLength called with invalid object") - })?; + let buf = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("ArrayBuffer.byteLength called with non `ArrayBuffer` object") + })?; // 4. If IsDetachedBuffer(O) is true, return +0𝔽. // 5. Let length be O.[[ArrayBufferByteLength]]. @@ -278,13 +280,12 @@ impl ArrayBuffer { fn slice(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("ArrayBuffer.slice called with non-object value") })?; - let obj_borrow = obj.borrow(); - // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - let buf = obj_borrow.as_array_buffer().ok_or_else(|| { + let buf = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("ArrayBuffer.slice called with invalid object") })?; @@ -312,15 +313,14 @@ impl ArrayBuffer { let new = ctor.construct(&[new_len.into()], Some(&ctor), context)?; { - let new_obj = new.borrow(); // 17. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). // 18. If IsSharedArrayBuffer(new) is true, throw a TypeError exception. - let new_array_buffer = new_obj.as_array_buffer().ok_or_else(|| { + let new = new.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("ArrayBuffer constructor returned invalid object") })?; // 19. If IsDetachedBuffer(new) is true, throw a TypeError exception. - if new_array_buffer.is_detached() { + if new.is_detached() { return Err(JsNativeError::typ() .with_message("ArrayBuffer constructor returned detached ArrayBuffer") .into()); @@ -338,13 +338,12 @@ impl ArrayBuffer { } { - let mut new_obj_borrow = new.borrow_mut(); - let new_array_buffer = new_obj_borrow - .as_array_buffer_mut() + let mut new = new + .downcast_mut::() .expect("Already checked that `new_obj` was an `ArrayBuffer`"); // 21. If new.[[ArrayBufferByteLength]] < newLen, throw a TypeError exception. - if (new_array_buffer.len() as u64) < new_len { + if (new.len() as u64) < new_len { return Err(JsNativeError::typ() .with_message("new ArrayBuffer length too small") .into()); @@ -360,7 +359,7 @@ impl ArrayBuffer { }; // 25. Let toBuf be new.[[ArrayBufferData]]. - let to_buf = new_array_buffer + let to_buf = new .data .as_mut() .expect("ArrayBuffer cannot be detached here"); @@ -401,10 +400,10 @@ impl ArrayBuffer { let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::array_buffer(Self { + Self { data: Some(block), detach_key: JsValue::Undefined, - }), + }, ); // 5. Return obj. diff --git a/boa_engine/src/builtins/array_buffer/shared.rs b/boa_engine/src/builtins/array_buffer/shared.rs index a40abcdfa1d..c7eb0e48447 100644 --- a/boa_engine/src/builtins/array_buffer/shared.rs +++ b/boa_engine/src/builtins/array_buffer/shared.rs @@ -12,11 +12,11 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, ObjectData}, + object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::common::StaticJsStrings, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use super::{get_slice_range, utils::copy_shared_to_shared, SliceRange}; @@ -25,7 +25,7 @@ use super::{get_slice_range, utils::copy_shared_to_shared, SliceRange}; /// /// This struct implements `Send` and `Sync`, meaning it can be shared between threads /// running different JS code at the same time. -#[derive(Debug, Clone, Trace, Finalize)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct SharedArrayBuffer { /// The `[[ArrayBufferData]]` internal slot. // Shared buffers cannot be detached. @@ -155,16 +155,14 @@ impl SharedArrayBuffer { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). - let obj = this.as_object().ok_or_else(|| { - JsNativeError::typ() - .with_message("SharedArrayBuffer.byteLength called with non-object value") - })?; - let obj = obj.borrow(); // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. - let buf = obj.as_shared_array_buffer().ok_or_else(|| { - JsNativeError::typ() - .with_message("SharedArrayBuffer.byteLength called with invalid object") - })?; + let buf = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("SharedArrayBuffer.byteLength called with invalid value") + })?; // TODO: 4. Let length be ArrayBufferByteLength(O, seq-cst). // 5. Return 𝔽(length). @@ -184,10 +182,9 @@ impl SharedArrayBuffer { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("ArrayBuffer.slice called with non-object value") })?; - let obj_borrow = obj.borrow(); // 3. If IsSharedArrayBuffer(O) is false, throw a TypeError exception. - let buf = obj_borrow.as_shared_array_buffer().ok_or_else(|| { + let buf = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("ArrayBuffer.slice called with invalid object") })?; @@ -210,21 +207,20 @@ impl SharedArrayBuffer { { // 16. Perform ? RequireInternalSlot(new, [[ArrayBufferData]]). // 17. If IsSharedArrayBuffer(new) is false, throw a TypeError exception. - let new_obj = new.borrow(); - let new_buf = new_obj.as_shared_array_buffer().ok_or_else(|| { + let new = new.downcast_ref::().ok_or_else(|| { JsNativeError::typ() .with_message("SharedArrayBuffer constructor returned invalid object") })?; // 18. If new.[[ArrayBufferData]] is O.[[ArrayBufferData]], throw a TypeError exception. - if std::ptr::eq(buf.data().as_ptr(), new_buf.data().as_ptr()) { + if std::ptr::eq(buf.data().as_ptr(), new.data().as_ptr()) { return Err(JsNativeError::typ() .with_message("cannot reuse the same `SharedArrayBuffer` for a slice operation") .into()); } // TODO: 19. If ArrayBufferByteLength(new, seq-cst) < newLen, throw a TypeError exception. - if (new_buf.len() as u64) < new_len { + if (new.len() as u64) < new_len { return Err(JsNativeError::typ() .with_message("invalid size of constructed shared array") .into()); @@ -234,7 +230,7 @@ impl SharedArrayBuffer { let from_buf = buf.data(); // 21. Let toBuf be new.[[ArrayBufferData]]. - let to_buf = new_buf.data(); + let to_buf = new.data(); // 22. Perform CopyDataBlockBytes(toBuf, 0, fromBuf, first, newLen). let first = first as usize; @@ -298,7 +294,7 @@ impl SharedArrayBuffer { let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::shared_array_buffer(Self { data }), + Self { data }, ); // 11. Return obj. diff --git a/boa_engine/src/builtins/array_buffer/utils.rs b/boa_engine/src/builtins/array_buffer/utils.rs index 7a472b5a354..7c381ce3c8f 100644 --- a/boa_engine/src/builtins/array_buffer/utils.rs +++ b/boa_engine/src/builtins/array_buffer/utils.rs @@ -140,11 +140,10 @@ impl SliceRef<'_> { // 4. Let targetBlock be targetBuffer.[[ArrayBufferData]]. { - let mut target_buffer_mut = target_buffer.borrow_mut(); - let target_array_buffer = target_buffer_mut - .as_array_buffer_mut() + let mut target_buffer = target_buffer + .downcast_mut::() .expect("This must be an ArrayBuffer"); - let target_block = target_array_buffer + let target_block = target_buffer .data .as_deref_mut() .expect("ArrayBuffer cannot be detached here"); diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs index 38fe1e3ed9a..2752f82185d 100644 --- a/boa_engine/src/builtins/async_generator/mod.rs +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -21,7 +21,7 @@ use crate::{ symbol::JsSymbol, value::JsValue, vm::{CompletionRecord, GeneratorResumeKind}, - Context, JsArgs, JsError, JsResult, JsString, + Context, JsArgs, JsData, JsError, JsResult, JsString, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -55,7 +55,7 @@ pub(crate) struct AsyncGeneratorRequest { } /// The internal representation of an `AsyncGenerator` object. -#[derive(Debug, Clone, Finalize, Trace)] +#[derive(Debug, Clone, Finalize, Trace, JsData)] pub struct AsyncGenerator { /// The `[[AsyncGeneratorState]]` internal slot. #[unsafe_ignore_trace] @@ -131,26 +131,25 @@ impl AsyncGenerator { // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). // 4. IfAbruptRejectPromise(result, promiseCapability). - let generator_object: JsResult<_> = generator.as_object().ok_or_else(|| { + let result: JsResult<_> = generator.as_object().ok_or_else(|| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); - if_abrupt_reject_promise!(generator_object, promise_capability, context); - let mut generator_obj_mut = generator_object.borrow_mut(); - let generator: JsResult<_> = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { + let generator_object = if_abrupt_reject_promise!(result, promise_capability, context); + let result: JsResult<_> = generator_object.downcast_mut::().ok_or_else(|| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); - if_abrupt_reject_promise!(generator, promise_capability, context); + let mut generator = if_abrupt_reject_promise!(result, promise_capability, context); // 5. Let state be generator.[[AsyncGeneratorState]]. let state = generator.state; // 6. If state is completed, then if state == AsyncGeneratorState::Completed { - drop(generator_obj_mut); + drop(generator); // a. Let iteratorResult be CreateIterResultObject(undefined, true). let iterator_result = create_iter_result_object(JsValue::undefined(), true, context); @@ -181,7 +180,7 @@ impl AsyncGenerator { .take() .expect("generator context cannot be empty here"); - drop(generator_obj_mut); + drop(generator); Self::resume( generator_object, @@ -219,19 +218,18 @@ impl AsyncGenerator { // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). // 4. IfAbruptRejectPromise(result, promiseCapability). - let generator_object: JsResult<_> = generator.as_object().ok_or_else(|| { + let result: JsResult<_> = generator.as_object().ok_or_else(|| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); - if_abrupt_reject_promise!(generator_object, promise_capability, context); - let mut generator_obj_mut = generator_object.borrow_mut(); - let generator: JsResult<_> = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { + let generator_object = if_abrupt_reject_promise!(result, promise_capability, context); + let result: JsResult<_> = generator_object.downcast_mut::().ok_or_else(|| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); - if_abrupt_reject_promise!(generator, promise_capability, context); + let mut generator = if_abrupt_reject_promise!(result, promise_capability, context); // 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. let return_value = args.get_or_undefined(0).clone(); @@ -249,7 +247,7 @@ impl AsyncGenerator { generator.state = AsyncGeneratorState::AwaitingReturn; // b. Perform ! AsyncGeneratorAwaitReturn(generator). - drop(generator_obj_mut); + drop(generator); Self::await_return(generator_object.clone(), return_value, context); } // 9. Else if state is suspendedYield, then @@ -260,7 +258,7 @@ impl AsyncGenerator { .take() .expect("generator context cannot be empty here"); - drop(generator_obj_mut); + drop(generator); Self::resume( generator_object, state, @@ -297,19 +295,18 @@ impl AsyncGenerator { // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). // 4. IfAbruptRejectPromise(result, promiseCapability). - let generator_object: JsResult<_> = generator.as_object().ok_or_else(|| { + let result: JsResult<_> = generator.as_object().ok_or_else(|| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); - if_abrupt_reject_promise!(generator_object, promise_capability, context); - let mut generator_obj_mut = generator_object.borrow_mut(); - let generator: JsResult<_> = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { + let generator_object = if_abrupt_reject_promise!(result, promise_capability, context); + let result: JsResult<_> = generator_object.downcast_mut::().ok_or_else(|| { JsNativeError::typ() .with_message("generator resumed on non generator object") .into() }); - if_abrupt_reject_promise!(generator, promise_capability, context); + let mut generator = if_abrupt_reject_promise!(result, promise_capability, context); // 5. Let state be generator.[[AsyncGeneratorState]]. let mut state = generator.state; @@ -326,7 +323,7 @@ impl AsyncGenerator { // 7. If state is completed, then if state == AsyncGeneratorState::Completed { - drop(generator_obj_mut); + drop(generator); // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »). promise_capability @@ -355,7 +352,7 @@ impl AsyncGenerator { .context .take() .expect("generator context cannot be empty here"); - drop(generator_obj_mut); + drop(generator); // a. Perform AsyncGeneratorResume(generator, completion). Self::resume( @@ -480,8 +477,7 @@ impl AsyncGenerator { // 5. Set generator.[[AsyncGeneratorState]] to executing. generator - .borrow_mut() - .as_async_generator_mut() + .downcast_mut::() .expect("already checked before") .state = AsyncGeneratorState::Executing; @@ -497,8 +493,7 @@ impl AsyncGenerator { // 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation. generator - .borrow_mut() - .as_async_generator_mut() + .downcast_mut::() .expect("already checked before") .context = Some(generator_context); @@ -535,14 +530,13 @@ impl AsyncGenerator { let promise = match promise_completion { Ok(value) => value, Err(value) => { - let mut generator_borrow_mut = generator.borrow_mut(); - let gen = generator_borrow_mut - .as_async_generator_mut() + let mut gen = generator + .downcast_mut::() .expect("already checked before"); gen.state = AsyncGeneratorState::Completed; gen.context = None; let next = gen.queue.pop_front().expect("queue must not be empty"); - drop(generator_borrow_mut); + drop(gen); Self::complete_step(&next, Err(value), true, None, context); Self::drain_queue(&generator, context); return; @@ -556,9 +550,8 @@ impl AsyncGenerator { NativeFunction::from_copy_closure_with_captures( |_this, args, generator, context| { let next = { - let mut generator_borrow_mut = generator.borrow_mut(); - let gen = generator_borrow_mut - .as_async_generator_mut() + let mut gen = generator + .downcast_mut::() .expect("already checked before"); // a. Set generator.[[AsyncGeneratorState]] to completed. @@ -593,9 +586,8 @@ impl AsyncGenerator { context.realm(), NativeFunction::from_copy_closure_with_captures( |_this, args, generator, context| { - let mut generator_borrow_mut = generator.borrow_mut(); - let gen = generator_borrow_mut - .as_async_generator_mut() + let mut gen = generator + .downcast_mut::() .expect("already checked before"); // a. Set generator.[[AsyncGeneratorState]] to completed. @@ -607,7 +599,7 @@ impl AsyncGenerator { // c. Perform AsyncGeneratorCompleteStep(generator, result, true). let next = gen.queue.pop_front().expect("must have one entry"); - drop(generator_borrow_mut); + drop(gen); Self::complete_step(&next, result, true, None, context); // d. Perform AsyncGeneratorDrainQueue(generator). @@ -640,9 +632,8 @@ impl AsyncGenerator { /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue pub(crate) fn drain_queue(generator: &JsObject, context: &mut Context) { - let mut generator_borrow_mut = generator.borrow_mut(); - let gen = generator_borrow_mut - .as_async_generator_mut() + let mut gen = generator + .downcast_mut::() .expect("already checked before"); // 1. Assert: generator.[[AsyncGeneratorState]] is completed. @@ -668,7 +659,7 @@ impl AsyncGenerator { CompletionRecord::Return(val) => { // i. Set generator.[[AsyncGeneratorState]] to awaiting-return. gen.state = AsyncGeneratorState::AwaitingReturn; - drop(generator_borrow_mut); + drop(gen); // ii. Perform ! AsyncGeneratorAwaitReturn(generator). Self::await_return(generator.clone(), val, context); diff --git a/boa_engine/src/builtins/atomics/mod.rs b/boa_engine/src/builtins/atomics/mod.rs index 1bf57715a80..a3b7c72c518 100644 --- a/boa_engine/src/builtins/atomics/mod.rs +++ b/boa_engine/src/builtins/atomics/mod.rs @@ -15,26 +15,17 @@ mod futex; use std::sync::atomic::Ordering; use crate::{ - builtins::BuiltInObject, - context::intrinsics::Intrinsics, - js_string, - object::{JsObject, Object}, - property::Attribute, - realm::Realm, - string::common::StaticJsStrings, - symbol::JsSymbol, - sys::time::Duration, - value::IntegerOrInfinity, - Context, JsArgs, JsNativeError, JsResult, JsString, JsValue, + builtins::BuiltInObject, context::intrinsics::Intrinsics, js_string, object::JsObject, + property::Attribute, realm::Realm, string::common::StaticJsStrings, symbol::JsSymbol, + sys::time::Duration, value::IntegerOrInfinity, Context, JsArgs, JsNativeError, JsResult, + JsString, JsValue, }; use boa_gc::GcRef; use boa_profiler::Profiler; use super::{ - array_buffer::BufferRef, - typed_array::{ - Atomic, ContentType, Element, IntegerIndexed, TypedArrayElement, TypedArrayKind, - }, + array_buffer::{BufferRef, SharedArrayBuffer}, + typed_array::{Atomic, ContentType, Element, TypedArray, TypedArrayElement, TypedArrayKind}, BuiltInBuilder, IntrinsicObject, }; @@ -507,7 +498,7 @@ impl Atomics { // 6. If IsSharedArrayBuffer(buffer) is false, return +0𝔽. let buffer = ii.viewed_array_buffer(); let buffer = buffer.borrow(); - let Some(shared) = buffer.as_shared_array_buffer() else { + let Some(shared) = buffer.downcast_ref::() else { return Ok(0.into()); }; @@ -525,12 +516,12 @@ impl Atomics { fn validate_integer_typed_array( array: &JsValue, waitable: bool, -) -> JsResult> { +) -> JsResult> { // 1. If waitable is not present, set waitable to false. // 2. Perform ? ValidateTypedArray(typedArray). let ii = array .as_object() - .and_then(|o| GcRef::try_map(o.borrow(), Object::as_typed_array)) + .and_then(|o| o.downcast_ref::()) .ok_or_else(|| JsNativeError::typ().with_message("value is not a typed array object"))?; if ii.is_detached() { return Err(JsNativeError::typ() @@ -570,7 +561,7 @@ fn validate_integer_typed_array( /// /// [spec]: https://tc39.es/ecma262/#sec-validateatomicaccess fn validate_atomic_access( - array: &IntegerIndexed, + array: &TypedArray, request_index: &JsValue, context: &mut Context, ) -> JsResult { diff --git a/boa_engine/src/builtins/bigint/mod.rs b/boa_engine/src/builtins/bigint/mod.rs index 50132544bef..3dc75356b18 100644 --- a/boa_engine/src/builtins/bigint/mod.rs +++ b/boa_engine/src/builtins/bigint/mod.rs @@ -146,7 +146,7 @@ impl BigInt { .or_else(|| { value .as_object() - .and_then(|obj| obj.borrow().as_bigint().cloned()) + .and_then(|obj| obj.downcast_ref::().as_deref().cloned()) }) // 3. Throw a TypeError exception. .ok_or_else(|| { diff --git a/boa_engine/src/builtins/boolean/mod.rs b/boa_engine/src/builtins/boolean/mod.rs index 5325baca5ff..c72e7e3aecd 100644 --- a/boa_engine/src/builtins/boolean/mod.rs +++ b/boa_engine/src/builtins/boolean/mod.rs @@ -17,7 +17,7 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, realm::Realm, string::common::StaticJsStrings, Context, JsResult, JsString, JsValue, @@ -70,11 +70,8 @@ impl BuiltInConstructor for Boolean { } let prototype = get_prototype_from_constructor(new_target, StandardConstructors::boolean, context)?; - let boolean = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::boolean(data), - ); + let boolean = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, data); Ok(boolean.into()) } @@ -90,7 +87,11 @@ impl Boolean { fn this_boolean_value(value: &JsValue) -> JsResult { value .as_boolean() - .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_boolean())) + .or_else(|| { + value + .as_object() + .and_then(|obj| obj.downcast_ref::().as_deref().copied()) + }) .ok_or_else(|| { JsNativeError::typ() .with_message("'this' is not a boolean") diff --git a/boa_engine/src/builtins/builder.rs b/boa_engine/src/builtins/builder.rs new file mode 100644 index 00000000000..a565744b567 --- /dev/null +++ b/boa_engine/src/builtins/builder.rs @@ -0,0 +1,641 @@ +use boa_macros::utf16; + +use crate::{ + js_string, + native_function::{NativeFunctionObject, NativeFunctionPointer}, + object::{ + shape::{property_table::PropertyTableInner, slot::SlotAttributes}, + FunctionBinding, JsFunction, JsPrototype, CONSTRUCTOR, PROTOTYPE, + }, + property::{Attribute, PropertyDescriptor, PropertyKey}, + realm::Realm, + JsObject, JsString, JsValue, NativeFunction, +}; + +use super::{function::ConstructorKind, BuiltInConstructor, IntrinsicObject}; + +/// Marker for a constructor function. +pub(crate) struct Constructor { + prototype: JsObject, + inherits: JsPrototype, + attributes: Attribute, +} + +/// Marker for a constructor function without a custom prototype for its instances. +pub(crate) struct ConstructorNoProto; + +/// Marker for an ordinary function. +pub(crate) struct OrdinaryFunction; + +/// Indicates if the marker is a constructor. +trait IsConstructor { + const IS_CONSTRUCTOR: bool; +} + +impl IsConstructor for Constructor { + const IS_CONSTRUCTOR: bool = true; +} + +impl IsConstructor for ConstructorNoProto { + const IS_CONSTRUCTOR: bool = true; +} + +impl IsConstructor for OrdinaryFunction { + const IS_CONSTRUCTOR: bool = false; +} + +/// Marker for a callable object. +pub(crate) struct Callable { + function: NativeFunctionPointer, + name: JsString, + length: usize, + kind: Kind, + realm: Realm, +} + +/// Marker for an ordinary object. +pub(crate) struct OrdinaryObject; + +/// Applies the pending builder data to the object. +pub(crate) trait ApplyToObject { + fn apply_to(self, object: &JsObject); +} + +impl ApplyToObject for Constructor { + fn apply_to(self, object: &JsObject) { + object.insert( + PROTOTYPE, + PropertyDescriptor::builder() + .value(self.prototype.clone()) + .writable(false) + .enumerable(false) + .configurable(false), + ); + + { + let mut prototype = self.prototype.borrow_mut(); + prototype.set_prototype(self.inherits); + prototype.insert( + CONSTRUCTOR, + PropertyDescriptor::builder() + .value(object.clone()) + .writable(self.attributes.writable()) + .enumerable(self.attributes.enumerable()) + .configurable(self.attributes.configurable()), + ); + } + } +} + +impl ApplyToObject for ConstructorNoProto { + fn apply_to(self, _: &JsObject) {} +} + +impl ApplyToObject for OrdinaryFunction { + fn apply_to(self, _: &JsObject) {} +} + +impl ApplyToObject for Callable { + fn apply_to(self, object: &JsObject) { + { + let mut function = object + .downcast_mut::() + .expect("Builtin must be a function object"); + function.f = NativeFunction::from_fn_ptr(self.function); + function.constructor = S::IS_CONSTRUCTOR.then_some(ConstructorKind::Base); + function.realm = Some(self.realm); + } + object.insert( + utf16!("length"), + PropertyDescriptor::builder() + .value(self.length) + .writable(false) + .enumerable(false) + .configurable(true), + ); + object.insert( + utf16!("name"), + PropertyDescriptor::builder() + .value(self.name) + .writable(false) + .enumerable(false) + .configurable(true), + ); + + self.kind.apply_to(object); + } +} + +impl ApplyToObject for OrdinaryObject { + fn apply_to(self, _: &JsObject) {} +} + +/// Builder for creating built-in objects, like `Array`. +/// +/// The marker `ObjectType` restricts the methods that can be called depending on the +/// type of object that is being constructed. +#[derive(Debug)] +#[must_use = "You need to call the `build` method in order for this to correctly assign the inner data"] +pub(crate) struct BuiltInBuilder<'ctx, Kind> { + realm: &'ctx Realm, + object: JsObject, + kind: Kind, + prototype: JsObject, +} + +impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { + pub(crate) fn with_intrinsic( + realm: &'ctx Realm, + ) -> BuiltInBuilder<'ctx, OrdinaryObject> { + BuiltInBuilder { + realm, + object: I::get(realm.intrinsics()), + kind: OrdinaryObject, + prototype: realm.intrinsics().constructors().object().prototype(), + } + } +} + +pub(crate) struct BuiltInConstructorWithPrototype<'ctx> { + realm: &'ctx Realm, + function: NativeFunctionPointer, + name: JsString, + length: usize, + + object_property_table: PropertyTableInner, + object_storage: Vec, + object: JsObject, + + prototype_property_table: PropertyTableInner, + prototype_storage: Vec, + prototype: JsObject, + __proto__: JsPrototype, + inherits: Option, + attributes: Attribute, +} + +#[allow(dead_code)] +impl BuiltInConstructorWithPrototype<'_> { + /// Specify how many arguments the constructor function takes. + /// + /// Default is `0`. + #[inline] + pub(crate) const fn length(mut self, length: usize) -> Self { + self.length = length; + self + } + + /// Specify the name of the constructor function. + /// + /// Default is `""` + pub(crate) fn name(mut self, name: JsString) -> Self { + self.name = name; + self + } + + /// Adds a new static method to the builtin object. + pub(crate) fn static_method( + mut self, + function: NativeFunctionPointer, + binding: B, + length: usize, + ) -> Self + where + B: Into, + { + let binding = binding.into(); + let function = BuiltInBuilder::callable(self.realm, function) + .name(binding.name) + .length(length) + .build(); + + debug_assert!(self + .object_property_table + .map + .get(&binding.binding) + .is_none()); + self.object_property_table.insert( + binding.binding, + SlotAttributes::WRITABLE | SlotAttributes::CONFIGURABLE, + ); + self.object_storage.push(function.into()); + self + } + + /// Adds a new static data property to the builtin object. + pub(crate) fn static_property(mut self, key: K, value: V, attribute: Attribute) -> Self + where + K: Into, + V: Into, + { + let key = key.into(); + + debug_assert!(self.object_property_table.map.get(&key).is_none()); + self.object_property_table + .insert(key, SlotAttributes::from_bits_truncate(attribute.bits())); + self.object_storage.push(value.into()); + self + } + + /// Adds a new static accessor property to the builtin object. + pub(crate) fn static_accessor( + mut self, + key: K, + get: Option, + set: Option, + attribute: Attribute, + ) -> Self + where + K: Into, + { + let mut attributes = SlotAttributes::from_bits_truncate(attribute.bits()); + debug_assert!(!attributes.contains(SlotAttributes::WRITABLE)); + attributes.set(SlotAttributes::GET, get.is_some()); + attributes.set(SlotAttributes::SET, set.is_some()); + + let key = key.into(); + + debug_assert!(self.object_property_table.map.get(&key).is_none()); + self.object_property_table.insert(key, attributes); + self.object_storage.extend([ + get.map(JsValue::new).unwrap_or_default(), + set.map(JsValue::new).unwrap_or_default(), + ]); + self + } + + /// Specify the `[[Prototype]]` internal field of the builtin object. + /// + /// Default is `Function.prototype` for constructors and `Object.prototype` for statics. + pub(crate) fn prototype(mut self, prototype: JsObject) -> Self { + self.__proto__ = Some(prototype); + self + } + + /// Adds a new method to the constructor's prototype. + pub(crate) fn method( + mut self, + function: NativeFunctionPointer, + binding: B, + length: usize, + ) -> Self + where + B: Into, + { + let binding = binding.into(); + let function = BuiltInBuilder::callable(self.realm, function) + .name(binding.name) + .length(length) + .build(); + + debug_assert!(self + .prototype_property_table + .map + .get(&binding.binding) + .is_none()); + self.prototype_property_table.insert( + binding.binding, + SlotAttributes::WRITABLE | SlotAttributes::CONFIGURABLE, + ); + self.prototype_storage.push(function.into()); + self + } + + /// Adds a new data property to the constructor's prototype. + pub(crate) fn property(mut self, key: K, value: V, attribute: Attribute) -> Self + where + K: Into, + V: Into, + { + let key = key.into(); + + debug_assert!(self.prototype_property_table.map.get(&key).is_none()); + self.prototype_property_table + .insert(key, SlotAttributes::from_bits_truncate(attribute.bits())); + self.prototype_storage.push(value.into()); + self + } + + /// Adds new accessor property to the constructor's prototype. + pub(crate) fn accessor( + mut self, + key: K, + get: Option, + set: Option, + attribute: Attribute, + ) -> Self + where + K: Into, + { + let mut attributes = SlotAttributes::from_bits_truncate(attribute.bits()); + debug_assert!(!attributes.contains(SlotAttributes::WRITABLE)); + attributes.set(SlotAttributes::GET, get.is_some()); + attributes.set(SlotAttributes::SET, set.is_some()); + + let key = key.into(); + + debug_assert!(self.prototype_property_table.map.get(&key).is_none()); + self.prototype_property_table.insert(key, attributes); + self.prototype_storage.extend([ + get.map(JsValue::new).unwrap_or_default(), + set.map(JsValue::new).unwrap_or_default(), + ]); + self + } + + /// Specifies the parent prototype which objects created by this constructor inherit from. + /// + /// Default is `Object.prototype`. + #[allow(clippy::missing_const_for_fn)] + pub(crate) fn inherits(mut self, prototype: JsPrototype) -> Self { + self.inherits = prototype; + self + } + + /// Specifies the property attributes of the prototype's "constructor" property. + pub(crate) const fn constructor_attributes(mut self, attributes: Attribute) -> Self { + self.attributes = attributes; + self + } + + pub(crate) fn build(mut self) { + let length = self.length; + let name = self.name.clone(); + let prototype = self.prototype.clone(); + self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE); + self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); + self = self.static_property(PROTOTYPE, prototype, Attribute::empty()); + + let attributes = self.attributes; + let object = self.object.clone(); + self = self.property(CONSTRUCTOR, object, attributes); + + { + let mut prototype = self.prototype.borrow_mut(); + prototype + .properties_mut() + .shape + .as_unique() + .expect("The object should have a unique shape") + .override_internal(self.prototype_property_table, self.inherits); + + let prototype_old_storage = std::mem::replace( + &mut prototype.properties_mut().storage, + self.prototype_storage, + ); + + debug_assert_eq!(prototype_old_storage.len(), 0); + } + + let mut object = self.object.borrow_mut(); + let function = object + .downcast_mut::() + .expect("Builtin must be a function object"); + function.f = NativeFunction::from_fn_ptr(self.function); + function.constructor = Some(ConstructorKind::Base); + function.realm = Some(self.realm.clone()); + object + .properties_mut() + .shape + .as_unique() + .expect("The object should have a unique shape") + .override_internal(self.object_property_table, self.__proto__); + + let object_old_storage = + std::mem::replace(&mut object.properties_mut().storage, self.object_storage); + + debug_assert_eq!(object_old_storage.len(), 0); + } + + pub(crate) fn build_without_prototype(mut self) { + let length = self.length; + let name = self.name.clone(); + self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE); + self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); + + let mut object = self.object.borrow_mut(); + let function = object + .downcast_mut::() + .expect("Builtin must be a function object"); + function.f = NativeFunction::from_fn_ptr(self.function); + function.constructor = Some(ConstructorKind::Base); + function.realm = Some(self.realm.clone()); + object + .properties_mut() + .shape + .as_unique() + .expect("The object should have a unique shape") + .override_internal(self.object_property_table, self.__proto__); + + let object_old_storage = + std::mem::replace(&mut object.properties_mut().storage, self.object_storage); + + debug_assert_eq!(object_old_storage.len(), 0); + } +} + +pub(crate) struct BuiltInCallable<'ctx> { + realm: &'ctx Realm, + function: NativeFunctionPointer, + name: JsString, + length: usize, +} + +impl BuiltInCallable<'_> { + /// Specify how many arguments the constructor function takes. + /// + /// Default is `0`. + #[inline] + pub(crate) const fn length(mut self, length: usize) -> Self { + self.length = length; + self + } + + /// Specify the name of the constructor function. + /// + /// Default is `""` + pub(crate) fn name(mut self, name: JsString) -> Self { + self.name = name; + self + } + + pub(crate) fn build(self) -> JsFunction { + let object = self.realm.intrinsics().templates().function().create( + NativeFunctionObject { + f: NativeFunction::from_fn_ptr(self.function), + constructor: None, + realm: Some(self.realm.clone()), + }, + vec![JsValue::new(self.length), JsValue::new(self.name)], + ); + + JsFunction::from_object_unchecked(object) + } +} + +impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { + pub(crate) fn callable( + realm: &'ctx Realm, + function: NativeFunctionPointer, + ) -> BuiltInCallable<'ctx> { + BuiltInCallable { + realm, + function, + length: 0, + name: js_string!(""), + } + } + + pub(crate) fn callable_with_intrinsic( + realm: &'ctx Realm, + function: NativeFunctionPointer, + ) -> BuiltInBuilder<'ctx, Callable> { + BuiltInBuilder { + realm, + object: I::get(realm.intrinsics()), + kind: Callable { + function, + name: js_string!(""), + length: 0, + kind: OrdinaryFunction, + realm: realm.clone(), + }, + prototype: realm.intrinsics().constructors().function().prototype(), + } + } + + pub(crate) fn callable_with_object( + realm: &'ctx Realm, + object: JsObject, + function: NativeFunctionPointer, + ) -> BuiltInBuilder<'ctx, Callable> { + BuiltInBuilder { + realm, + object, + kind: Callable { + function, + name: js_string!(""), + length: 0, + kind: OrdinaryFunction, + realm: realm.clone(), + }, + prototype: realm.intrinsics().constructors().function().prototype(), + } + } +} + +impl<'ctx> BuiltInBuilder<'ctx, Callable> { + pub(crate) fn from_standard_constructor( + realm: &'ctx Realm, + ) -> BuiltInConstructorWithPrototype<'ctx> { + let constructor = SC::STANDARD_CONSTRUCTOR(realm.intrinsics().constructors()); + BuiltInConstructorWithPrototype { + realm, + function: SC::constructor, + name: js_string!(SC::NAME), + length: SC::LENGTH, + object_property_table: PropertyTableInner::default(), + object_storage: Vec::default(), + object: constructor.constructor(), + prototype_property_table: PropertyTableInner::default(), + prototype_storage: Vec::default(), + prototype: constructor.prototype(), + __proto__: Some(realm.intrinsics().constructors().function().prototype()), + inherits: Some(realm.intrinsics().constructors().object().prototype()), + attributes: Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, + } + } +} + +impl BuiltInBuilder<'_, T> { + /// Adds a new static method to the builtin object. + pub(crate) fn static_method( + self, + function: NativeFunctionPointer, + binding: B, + length: usize, + ) -> Self + where + B: Into, + { + let binding = binding.into(); + let function = BuiltInBuilder::callable(self.realm, function) + .name(binding.name) + .length(length) + .build(); + + self.object.insert( + binding.binding, + PropertyDescriptor::builder() + .value(function) + .writable(true) + .enumerable(false) + .configurable(true), + ); + self + } + + /// Adds a new static data property to the builtin object. + pub(crate) fn static_property(self, key: K, value: V, attribute: Attribute) -> Self + where + K: Into, + V: Into, + { + let property = PropertyDescriptor::builder() + .value(value) + .writable(attribute.writable()) + .enumerable(attribute.enumerable()) + .configurable(attribute.configurable()); + self.object.insert(key, property); + self + } + + /// Specify the `[[Prototype]]` internal field of the builtin object. + /// + /// Default is `Function.prototype` for constructors and `Object.prototype` for statics. + pub(crate) fn prototype(mut self, prototype: JsObject) -> Self { + self.prototype = prototype; + self + } +} + +impl BuiltInBuilder<'_, Callable> { + /// Specify how many arguments the constructor function takes. + /// + /// Default is `0`. + #[inline] + pub(crate) const fn length(mut self, length: usize) -> Self { + self.kind.length = length; + self + } + + /// Specify the name of the constructor function. + /// + /// Default is `""` + pub(crate) fn name(mut self, name: JsString) -> Self { + self.kind.name = name; + self + } +} + +impl BuiltInBuilder<'_, OrdinaryObject> { + /// Build the builtin object. + pub(crate) fn build(self) -> JsObject { + self.kind.apply_to(&self.object); + + self.object.set_prototype(Some(self.prototype)); + + self.object + } +} + +impl BuiltInBuilder<'_, Callable> { + /// Build the builtin callable. + pub(crate) fn build(self) -> JsFunction { + self.kind.apply_to(&self.object); + + self.object.set_prototype(Some(self.prototype)); + + JsFunction::from_object_unchecked(self.object) + } +} diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 0d37c5a0188..05ad748a529 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -14,13 +14,13 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::common::StaticJsStrings, symbol::JsSymbol, value::JsValue, - Context, JsArgs, JsResult, JsString, + Context, JsArgs, JsData, JsResult, JsString, }; use boa_gc::{Finalize, Trace}; use bytemuck::{bytes_of, bytes_of_mut}; @@ -32,7 +32,7 @@ use super::{ }; /// The internal representation of a `DataView` object. -#[derive(Debug, Clone, Trace, Finalize)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct DataView { pub(crate) viewed_array_buffer: JsObject, pub(crate) byte_length: u64, @@ -209,14 +209,14 @@ impl BuiltInConstructor for DataView { let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::data_view(Self { + Self { // 11. Set O.[[ViewedArrayBuffer]] to buffer. viewed_array_buffer: buffer_obj.clone(), // 12. Set O.[[ByteLength]] to viewByteLength. byte_length: view_byte_length, // 13. Set O.[[ByteOffset]] to offset. byte_offset: offset, - }), + }, ); // 14. Return O. @@ -243,14 +243,13 @@ impl DataView { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). - let dataview = this.as_object().map(JsObject::borrow); - let dataview = dataview - .as_ref() - .and_then(|obj| obj.as_data_view()) + let view = this + .as_object() + .and_then(|o| o.downcast_ref::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. - let buffer = dataview.viewed_array_buffer.clone(); + let buffer = view.viewed_array_buffer.clone(); // 5. Return buffer. Ok(buffer.into()) } @@ -272,14 +271,13 @@ impl DataView { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). - let dataview = this.as_object().map(JsObject::borrow); - let dataview = dataview - .as_ref() - .and_then(|obj| obj.as_data_view()) + let view = this + .as_object() + .and_then(|o| o.downcast_ref::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. - let buffer_borrow = dataview.viewed_array_buffer.borrow(); + let buffer_borrow = view.viewed_array_buffer.borrow(); let borrow = buffer_borrow .as_buffer() .expect("DataView must be constructed with a Buffer"); @@ -290,7 +288,7 @@ impl DataView { .into()); } // 6. Let size be O.[[ByteLength]]. - let size = dataview.byte_length; + let size = view.byte_length; // 7. Return 𝔽(size). Ok(size.into()) } @@ -313,14 +311,13 @@ impl DataView { ) -> JsResult { // 1. Let O be the this value. // 2. Perform ? RequireInternalSlot(O, [[DataView]]). - let dataview = this.as_object().map(JsObject::borrow); - let dataview = dataview - .as_ref() - .and_then(|obj| obj.as_data_view()) + let view = this + .as_object() + .and_then(|o| o.downcast_ref::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. - let buffer_borrow = dataview.viewed_array_buffer.borrow(); + let buffer_borrow = view.viewed_array_buffer.borrow(); let borrow = buffer_borrow .as_buffer() .expect("DataView must be constructed with a Buffer"); @@ -331,7 +328,7 @@ impl DataView { .into()); } // 6. Let offset be O.[[ByteOffset]]. - let offset = dataview.byte_offset; + let offset = view.byte_offset; // 7. Return 𝔽(offset). Ok(offset.into()) } @@ -354,10 +351,9 @@ impl DataView { ) -> JsResult { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. - let view = view.as_object().map(JsObject::borrow); let view = view - .as_ref() - .and_then(|obj| obj.as_data_view()) + .as_object() + .and_then(|o| o.downcast_ref::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; @@ -666,10 +662,9 @@ impl DataView { ) -> JsResult { // 1. Perform ? RequireInternalSlot(view, [[DataView]]). // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. - let view = view.as_object().map(JsObject::borrow); let view = view - .as_ref() - .and_then(|obj| obj.as_data_view()) + .as_object() + .and_then(|o| o.downcast_ref::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index c1f8c99e362..ca6cca1d911 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -8,6 +8,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date pub(crate) mod utils; +use boa_gc::{Finalize, Trace}; use utils::{make_date, make_day, make_time, replace_params, time_clip, DateParameters}; #[cfg(test)] @@ -21,13 +22,13 @@ use crate::{ }, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, symbol::JsSymbol, value::{IntegerOrNan, JsValue, PreferredType}, - Context, JsArgs, JsError, JsResult, JsString, + Context, JsArgs, JsData, JsError, JsResult, JsString, }; use boa_profiler::Profiler; use chrono::prelude::*; @@ -44,17 +45,13 @@ macro_rules! some_or_nan { }; } -/// Gets a mutable reference to the inner `Date` object of `val` and stores it on `var`, or returns +/// Gets a mutable reference to the inner `Date` object of `val`, or returns /// a `TypeError` if `val` is not a `Date` object. macro_rules! get_mut_date { - (let $var:ident = $val:expr) => { - let mut $var = $val - .as_object() - .map(JsObject::borrow_mut) - .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; - let $var = $var - .as_date_mut() - .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))?; + ($val:expr) => { + $val.as_object() + .and_then(|o| o.downcast_mut::()) + .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? }; } @@ -64,13 +61,14 @@ macro_rules! get_mut_date { pub(super) fn this_time_value(value: &JsValue) -> JsResult> { Ok(value .as_object() - .and_then(|obj| obj.borrow().as_date().copied()) + .and_then(|obj| obj.downcast_ref::().as_deref().copied()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? .0) } /// The internal representation of a `Date` object. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Trace, Finalize, JsData)] +#[boa_gc(empty_trace)] pub struct Date(Option); impl Date { @@ -258,7 +256,7 @@ impl BuiltInConstructor for Date { // a. Let value be values[0]. [value] => match value .as_object() - .and_then(|obj| obj.borrow().as_date().copied()) + .and_then(|obj| obj.downcast_ref::().as_deref().copied()) { // b. If value is an Object and value has a [[DateValue]] internal slot, then Some(dt) => { @@ -313,11 +311,8 @@ impl BuiltInConstructor for Date { get_prototype_from_constructor(new_target, StandardConstructors::date, context)?; // 7. Set O.[[DateValue]] to dv. - let obj = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::date(dv), - ); + let obj = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, dv); // 8. Return O. Ok(obj.into()) @@ -798,7 +793,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be LocalTime(? thisTimeValue(this value)). - get_mut_date!(let t = this); + let mut t = get_mut_date!(this); // 2. Let dt be ? ToNumber(date). let date = args.get_or_undefined(0).to_integer_or_nan(context)?; @@ -839,7 +834,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - get_mut_date!(let t = this); + let mut t = get_mut_date!(this); // 2. If t is NaN, set t to +0𝔽; otherwise, set t to LocalTime(t). let datetime = @@ -907,7 +902,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - get_mut_date!(let t = this); + let mut t = get_mut_date!(this); // 2. Let h be ? ToNumber(hour). let hour = args.get_or_undefined(0).to_integer_or_nan(context)?; @@ -972,7 +967,7 @@ impl Date { ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). // 1. Let t be LocalTime(? thisTimeValue(this value)). - get_mut_date!(let t = this); + let mut t = get_mut_date!(this); // 2. Set ms to ? ToNumber(ms). let ms = args.get_or_undefined(0).to_integer_or_nan(context)?; @@ -1012,7 +1007,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - get_mut_date!(let t = this); + let mut t = get_mut_date!(this); // 2. Let m be ? ToNumber(min). let minute = args.get_or_undefined(0).to_integer_or_nan(context)?; @@ -1069,7 +1064,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - get_mut_date!(let t = this); + let mut t = get_mut_date!(this); // 2. Let m be ? ToNumber(month). let month = args.get_or_undefined(0).to_integer_or_nan(context)?; @@ -1117,7 +1112,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - get_mut_date!(let t = this); + let mut t = get_mut_date!(this); // 2. Let s be ? ToNumber(sec). let second = args.get_or_undefined(0).to_integer_or_nan(context)?; @@ -1172,7 +1167,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Let t be ? thisTimeValue(this value). - get_mut_date!(let t = this); + let mut t = get_mut_date!(this); // 2. Let y be ? ToNumber(year). // 5. Let yi be ! ToIntegerOrInfinity(y). @@ -1239,7 +1234,7 @@ impl Date { context: &mut Context, ) -> JsResult { // 1. Perform ? thisTimeValue(this value). - get_mut_date!(let t = this); + let mut t = get_mut_date!(this); // 2. Let t be ? ToNumber(time). // 3. Let v be TimeClip(t). diff --git a/boa_engine/src/builtins/error/aggregate.rs b/boa_engine/src/builtins/error/aggregate.rs index d34176fec85..7f57618c170 100644 --- a/boa_engine/src/builtins/error/aggregate.rs +++ b/boa_engine/src/builtins/error/aggregate.rs @@ -14,7 +14,7 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::{Attribute, PropertyDescriptorBuilder}, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -85,7 +85,7 @@ impl BuiltInConstructor for AggregateError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::error(ErrorKind::Aggregate), + ErrorKind::Aggregate, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/eval.rs b/boa_engine/src/builtins/error/eval.rs index d10629a882c..fda408fd08b 100644 --- a/boa_engine/src/builtins/error/eval.rs +++ b/boa_engine/src/builtins/error/eval.rs @@ -15,7 +15,7 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -84,7 +84,7 @@ impl BuiltInConstructor for EvalError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::error(ErrorKind::Eval), + ErrorKind::Eval, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index 21a7a0aa291..fdeffb5520f 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -15,12 +15,13 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, - Context, JsArgs, JsResult, JsString, JsValue, + Context, JsArgs, JsResult, JsString, JsValue, JsData, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; pub(crate) mod aggregate; @@ -56,7 +57,8 @@ use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; /// [`JsNativeErrorKind`][crate::error::JsNativeErrorKind]. /// /// [spec]: https://tc39.es/ecma262/#sec-error-objects -#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Trace, Finalize, JsData)] +#[boa_gc(empty_trace)] pub enum ErrorKind { /// The `AggregateError` object type. /// @@ -178,7 +180,7 @@ impl BuiltInConstructor for Error { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::error(ErrorKind::Error), + ErrorKind::Error, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/range.rs b/boa_engine/src/builtins/error/range.rs index c57f2de3fe6..c784b1f4508 100644 --- a/boa_engine/src/builtins/error/range.rs +++ b/boa_engine/src/builtins/error/range.rs @@ -13,7 +13,7 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -82,7 +82,7 @@ impl BuiltInConstructor for RangeError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::error(ErrorKind::Range), + ErrorKind::Range, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/reference.rs b/boa_engine/src/builtins/error/reference.rs index 28906c7d728..1b741f98338 100644 --- a/boa_engine/src/builtins/error/reference.rs +++ b/boa_engine/src/builtins/error/reference.rs @@ -13,7 +13,7 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -84,7 +84,7 @@ impl BuiltInConstructor for ReferenceError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::error(ErrorKind::Reference), + ErrorKind::Reference, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/syntax.rs b/boa_engine/src/builtins/error/syntax.rs index 93df4836ca6..19240208a88 100644 --- a/boa_engine/src/builtins/error/syntax.rs +++ b/boa_engine/src/builtins/error/syntax.rs @@ -15,7 +15,7 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -87,7 +87,7 @@ impl BuiltInConstructor for SyntaxError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::error(ErrorKind::Syntax), + ErrorKind::Syntax, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index eeee6010e09..4cca5876588 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -21,7 +21,7 @@ use crate::{ error::JsNativeError, js_string, native_function::NativeFunctionObject, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -90,7 +90,7 @@ impl BuiltInConstructor for TypeError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::error(ErrorKind::Type), + ErrorKind::Type, ); // 3. If message is not undefined, then @@ -124,7 +124,7 @@ impl IntrinsicObject for ThrowTypeError { let mut obj = obj.borrow_mut(); - *obj.as_native_function_mut() + *obj.downcast_mut::() .expect("`%ThrowTypeError%` must be a function") = NativeFunctionObject { f: NativeFunction::from_fn_ptr(|_, _, _| { Err(JsNativeError::typ() diff --git a/boa_engine/src/builtins/error/uri.rs b/boa_engine/src/builtins/error/uri.rs index 174fef61d2a..c96fc0fb6df 100644 --- a/boa_engine/src/builtins/error/uri.rs +++ b/boa_engine/src/builtins/error/uri.rs @@ -14,7 +14,7 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -83,7 +83,7 @@ impl BuiltInConstructor for UriError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::error(ErrorKind::Uri), + ErrorKind::Uri, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 3d1a9833a1c..8ed4078eeca 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -10,7 +10,7 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval use crate::{ - builtins::BuiltInObject, + builtins::{function::OrdinaryFunction, BuiltInObject}, bytecompiler::ByteCompiler, context::intrinsics::Intrinsics, environments::Environment, @@ -146,7 +146,7 @@ impl Eval { } let function_object = function_object - .as_function() + .downcast_ref::() .expect("must be function object"); // iv. If F.[[ConstructorKind]] is derived, set inDerivedConstructor to true. diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index ceb3c33b573..8bf1e1ca70a 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -1,78 +1,31 @@ use crate::{ environments::DeclarativeEnvironment, - object::{JsObject, ObjectData}, - Context, JsValue, + object::{ + internal_methods::{ + ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property, + ordinary_set, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, InternalMethodContext, + }, + JsObject, + }, + property::{DescriptorKind, PropertyDescriptor, PropertyKey}, + Context, JsData, JsResult, JsValue, }; use boa_ast::{function::FormalParameterList, operations::bound_names}; use boa_gc::{Finalize, Gc, Trace}; use rustc_hash::FxHashMap; -/// `ParameterMap` represents the `[[ParameterMap]]` internal slot on a Arguments exotic object. -/// -/// This struct stores all the data to access mapped function parameters in their environment. -#[derive(Debug, Clone, Trace, Finalize)] -pub struct ParameterMap { - binding_indices: Vec>, - environment: Gc, -} - -impl ParameterMap { - /// Deletes the binding with the given index from the parameter map. - pub(crate) fn delete(&mut self, index: u32) { - if let Some(binding) = self.binding_indices.get_mut(index as usize) { - *binding = None; - } - } - - /// Get the value of the binding at the given index from the function environment. - /// - /// Note: This function is the abstract getter closure described in 10.4.4.7.1 `MakeArgGetter ( name, env )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-makearggetter - pub(crate) fn get(&self, index: u32) -> Option { - let binding_index = self - .binding_indices - .get(index as usize) - .copied() - .flatten()?; - self.environment.get(binding_index) - } - - /// Set the value of the binding at the given index in the function environment. - /// - /// Note: This function is the abstract setter closure described in 10.4.4.7.2 `MakeArgSetter ( name, env )` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-makeargsetter - pub(crate) fn set(&self, index: u32, value: &JsValue) { - if let Some(binding_index) = self.binding_indices.get(index as usize).copied().flatten() { - self.environment.set(binding_index, value.clone()); - } - } -} - -#[derive(Debug, Clone, Trace, Finalize)] -pub enum Arguments { - Unmapped, - Mapped(ParameterMap), -} +#[derive(Debug, Copy, Clone, Trace, Finalize, JsData)] +#[boa_gc(empty_trace)] +pub struct UnmappedArguments; -impl Arguments { +impl UnmappedArguments { /// Creates a new unmapped Arguments ordinary object. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-createunmappedargumentsobject - pub(crate) fn create_unmapped_arguments_object( - arguments_list: &[JsValue], - context: &mut Context, - ) -> JsObject { + pub(crate) fn new(arguments_list: &[JsValue], context: &mut Context) -> JsObject { // 1. Let len be the number of elements in argumentsList. let len = arguments_list.len(); @@ -87,7 +40,7 @@ impl Arguments { .templates() .unmapped_arguments() .create( - ObjectData::arguments(Self::Unmapped), + Self, vec![ // 4. Perform DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). @@ -116,11 +69,77 @@ impl Arguments { // 9. Return obj. obj } +} + +/// `MappedArguments` represents an Arguments exotic object. +/// +/// This struct stores all the data to access mapped function parameters in their environment. +#[derive(Debug, Clone, Trace, Finalize)] +pub struct MappedArguments { + binding_indices: Vec>, + environment: Gc, +} + +impl JsData for MappedArguments { + fn internal_methods(&self) -> &'static InternalObjectMethods { + static METHODS: InternalObjectMethods = InternalObjectMethods { + __get_own_property__: arguments_exotic_get_own_property, + __define_own_property__: arguments_exotic_define_own_property, + __get__: arguments_exotic_get, + __set__: arguments_exotic_set, + __delete__: arguments_exotic_delete, + ..ORDINARY_INTERNAL_METHODS + }; + + &METHODS + } +} + +impl MappedArguments { + /// Deletes the binding with the given index from the parameter map. + pub(crate) fn delete(&mut self, index: u32) { + if let Some(binding) = self.binding_indices.get_mut(index as usize) { + *binding = None; + } + } + + /// Get the value of the binding at the given index from the function environment. + /// + /// Note: This function is the abstract getter closure described in 10.4.4.7.1 `MakeArgGetter ( name, env )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-makearggetter + pub(crate) fn get(&self, index: u32) -> Option { + let binding_index = self + .binding_indices + .get(index as usize) + .copied() + .flatten()?; + self.environment.get(binding_index) + } + + /// Set the value of the binding at the given index in the function environment. + /// + /// Note: This function is the abstract setter closure described in 10.4.4.7.2 `MakeArgSetter ( name, env )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-makeargsetter + pub(crate) fn set(&self, index: u32, value: &JsValue) { + if let Some(binding_index) = self.binding_indices.get(index as usize).copied().flatten() { + self.environment.set(binding_index, value.clone()); + } + } +} +impl MappedArguments { /// Creates a new mapped Arguments exotic object. /// /// - pub(crate) fn create_mapped_arguments_object( + pub(crate) fn new( func: &JsObject, formals: &FormalParameterList, arguments_list: &[JsValue], @@ -194,7 +213,7 @@ impl Arguments { property_index += 1; } - let mut map = ParameterMap { + let mut map = MappedArguments { binding_indices: vec![None; property_index], environment: env.clone(), }; @@ -208,7 +227,7 @@ impl Arguments { // 11. Set obj.[[ParameterMap]] to map. let obj = context.intrinsics().templates().mapped_arguments().create( - ObjectData::arguments(Self::Mapped(map)), + map, vec![ // 16. Perform ! DefinePropertyOrThrow(obj, "length", PropertyDescriptor { [[Value]]: 𝔽(len), // [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true }). @@ -238,3 +257,229 @@ impl Arguments { obj } } + +/// `[[GetOwnProperty]]` for arguments exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-getownproperty-p +pub(crate) fn arguments_exotic_get_own_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + // 1. Let desc be OrdinaryGetOwnProperty(args, P). + // 2. If desc is undefined, return desc. + let Some(desc) = ordinary_get_own_property(obj, key, context)? else { + return Ok(None); + }; + + // 3. Let map be args.[[ParameterMap]]. + // 4. Let isMapped be ! HasOwnProperty(map, P). + // 5. If isMapped is true, then + if let PropertyKey::Index(index) = key { + if let Some(value) = obj + .downcast_ref::() + .expect("arguments exotic method must only be callable from arguments objects") + .get(index.get()) + { + // a. Set desc.[[Value]] to Get(map, P). + return Ok(Some( + PropertyDescriptor::builder() + .value(value) + .maybe_writable(desc.writable()) + .maybe_enumerable(desc.enumerable()) + .maybe_configurable(desc.configurable()) + .build(), + )); + } + } + + // 6. Return desc. + Ok(Some(desc)) +} + +/// `[[DefineOwnProperty]]` for arguments exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-defineownproperty-p-desc +#[allow(clippy::needless_pass_by_value)] +pub(crate) fn arguments_exotic_define_own_property( + obj: &JsObject, + key: &PropertyKey, + desc: PropertyDescriptor, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + // 2. Let isMapped be HasOwnProperty(map, P). + let mapped = if let &PropertyKey::Index(index) = &key { + // 1. Let map be args.[[ParameterMap]]. + obj.downcast_ref::() + .expect("arguments exotic method must only be callable from arguments objects") + .get(index.get()) + .map(|value| (index, value)) + } else { + None + }; + + let new_arg_desc = match desc.kind() { + // 4. If isMapped is true and IsDataDescriptor(Desc) is true, then + // a. If Desc.[[Value]] is not present and Desc.[[Writable]] is present and its + // value is false, then + DescriptorKind::Data { + writable: Some(false), + value: None, + } => + // i. Set newArgDesc to a copy of Desc. + // ii. Set newArgDesc.[[Value]] to Get(map, P). + { + if let Some((_, value)) = &mapped { + PropertyDescriptor::builder() + .value(value.clone()) + .writable(false) + .maybe_enumerable(desc.enumerable()) + .maybe_configurable(desc.configurable()) + .build() + } else { + desc.clone() + } + } + + // 3. Let newArgDesc be Desc. + _ => desc.clone(), + }; + + // 5. Let allowed be ? OrdinaryDefineOwnProperty(args, P, newArgDesc). + // 6. If allowed is false, return false. + if !ordinary_define_own_property(obj, key, new_arg_desc, context)? { + return Ok(false); + } + + // 7. If isMapped is true, then + if let Some((index, _)) = mapped { + // 1. Let map be args.[[ParameterMap]]. + let mut map = obj + .downcast_mut::() + .expect("arguments exotic method must only be callable from arguments objects"); + + // a. If IsAccessorDescriptor(Desc) is true, then + if desc.is_accessor_descriptor() { + // i. Call map.[[Delete]](P). + map.delete(index.get()); + } + // b. Else, + else { + // i. If Desc.[[Value]] is present, then + if let Some(value) = desc.value() { + // 1. Let setStatus be Set(map, P, Desc.[[Value]], false). + // 2. Assert: setStatus is true because formal parameters mapped by argument objects are always writable. + map.set(index.get(), value); + } + + // ii. If Desc.[[Writable]] is present and its value is false, then + if desc.writable() == Some(false) { + // 1. Call map.[[Delete]](P). + map.delete(index.get()); + } + } + } + + // 8. Return true. + Ok(true) +} + +/// `[[Get]]` for arguments exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-get-p-receiver +pub(crate) fn arguments_exotic_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + if let PropertyKey::Index(index) = key { + // 1. Let map be args.[[ParameterMap]]. + // 2. Let isMapped be ! HasOwnProperty(map, P). + if let Some(value) = obj + .downcast_ref::() + .expect("arguments exotic method must only be callable from arguments objects") + .get(index.get()) + { + // a. Assert: map contains a formal parameter mapping for P. + // b. Return Get(map, P). + return Ok(value); + } + } + + // 3. If isMapped is false, then + // a. Return ? OrdinaryGet(args, P, Receiver). + ordinary_get(obj, key, receiver, context) +} + +/// `[[Set]]` for arguments exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-set-p-v-receiver +pub(crate) fn arguments_exotic_set( + obj: &JsObject, + key: PropertyKey, + value: JsValue, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + // 1. If SameValue(args, Receiver) is false, then + // a. Let isMapped be false. + // 2. Else, + if let PropertyKey::Index(index) = &key { + if JsValue::same_value(&obj.clone().into(), &receiver) { + // a. Let map be args.[[ParameterMap]]. + // b. Let isMapped be ! HasOwnProperty(map, P). + // 3. If isMapped is true, then + // a. Let setStatus be Set(map, P, V, false). + // b. Assert: setStatus is true because formal parameters mapped by argument objects are always writable. + obj.downcast_ref::() + .expect("arguments exotic method must only be callable from arguments objects") + .set(index.get(), &value); + } + } + + // 4. Return ? OrdinarySet(args, P, V, Receiver). + ordinary_set(obj, key, value, receiver, context) +} + +/// `[[Delete]]` for arguments exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-delete-p +pub(crate) fn arguments_exotic_delete( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + // 3. Let result be ? OrdinaryDelete(args, P). + let result = ordinary_delete(obj, key, context)?; + + if result { + if let PropertyKey::Index(index) = key { + // 1. Let map be args.[[ParameterMap]]. + // 2. Let isMapped be ! HasOwnProperty(map, P). + // 4. If result is true and isMapped is true, then + // a. Call map.[[Delete]](P). + obj.downcast_mut::() + .expect("arguments exotic method must only be callable from arguments objects") + .delete(index.get()); + } + } + + // 5. Return result. + Ok(result) +} diff --git a/boa_engine/src/builtins/function/bound.rs b/boa_engine/src/builtins/function/bound.rs new file mode 100644 index 00000000000..a45ffde1501 --- /dev/null +++ b/boa_engine/src/builtins/function/bound.rs @@ -0,0 +1,179 @@ +use boa_gc::{Finalize, Trace}; + +use crate::{ + object::{ + internal_methods::{CallValue, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, + JsData, + }, + Context, JsObject, JsResult, JsValue, +}; + +/// Binds a `Function Object` when `bind` is called. +#[derive(Debug, Trace, Finalize)] +pub struct BoundFunction { + target_function: JsObject, + this: JsValue, + args: Vec, +} + +impl JsData for BoundFunction { + fn internal_methods(&self) -> &'static InternalObjectMethods { + static CONSTRUCTOR_METHODS: InternalObjectMethods = InternalObjectMethods { + __call__: bound_function_exotic_call, + __construct__: bound_function_exotic_construct, + ..ORDINARY_INTERNAL_METHODS + }; + + static FUNCTION_METHODS: InternalObjectMethods = InternalObjectMethods { + __call__: bound_function_exotic_call, + ..ORDINARY_INTERNAL_METHODS + }; + + if self.target_function.is_constructor() { + &CONSTRUCTOR_METHODS + } else { + &FUNCTION_METHODS + } + } +} + +impl BoundFunction { + /// Abstract operation `BoundFunctionCreate` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-boundfunctioncreate + pub fn create( + target_function: JsObject, + this: JsValue, + args: Vec, + context: &mut Context, + ) -> JsResult { + // 1. Let proto be ? targetFunction.[[GetPrototypeOf]](). + let proto = target_function.__get_prototype_of__(context)?; + + // 2. Let internalSlotsList be the internal slots listed in Table 35, plus [[Prototype]] and [[Extensible]]. + // 3. Let obj be ! MakeBasicObject(internalSlotsList). + // 4. Set obj.[[Prototype]] to proto. + // 5. Set obj.[[Call]] as described in 10.4.1.1. + // 6. If IsConstructor(targetFunction) is true, then + // a. Set obj.[[Construct]] as described in 10.4.1.2. + // 7. Set obj.[[BoundTargetFunction]] to targetFunction. + // 8. Set obj.[[BoundThis]] to boundThis. + // 9. Set obj.[[BoundArguments]] to boundArgs. + // 10. Return obj. + Ok(JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + proto, + Self { + target_function, + this, + args, + }, + )) + } + + /// Get a reference to the bound function's this. + #[must_use] + pub const fn this(&self) -> &JsValue { + &self.this + } + + /// Get a reference to the bound function's target function. + #[must_use] + pub const fn target_function(&self) -> &JsObject { + &self.target_function + } + + /// Get a reference to the bound function's args. + #[must_use] + pub fn args(&self) -> &[JsValue] { + self.args.as_slice() + } +} + +/// Internal method `[[Call]]` for Bound Function Exotic Objects +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist +#[allow(clippy::unnecessary_wraps)] +fn bound_function_exotic_call( + obj: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + let bound_function = obj + .downcast_ref::() + .expect("bound function exotic method should only be callable from bound function objects"); + + let arguments_start_index = context.vm.stack.len() - argument_count; + + // 1. Let target be F.[[BoundTargetFunction]]. + let target = bound_function.target_function(); + context.vm.stack[arguments_start_index - 1] = target.clone().into(); + + // 2. Let boundThis be F.[[BoundThis]]. + let bound_this = bound_function.this(); + context.vm.stack[arguments_start_index - 2] = bound_this.clone(); + + // 3. Let boundArgs be F.[[BoundArguments]]. + let bound_args = bound_function.args(); + + // 4. Let args be the list-concatenation of boundArgs and argumentsList. + context + .vm + .insert_values_at(bound_args, arguments_start_index); + + // 5. Return ? Call(target, boundThis, args). + Ok(target.__call__(bound_args.len() + argument_count)) +} + +/// Internal method `[[Construct]]` for Bound Function Exotic Objects +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget +#[allow(clippy::unnecessary_wraps)] +fn bound_function_exotic_construct( + function_object: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + let new_target = context.vm.pop(); + + debug_assert!(new_target.is_object(), "new.target should be an object"); + + let bound_function = function_object + .downcast_ref::() + .expect("bound function exotic method should only be callable from bound function objects"); + + // 1. Let target be F.[[BoundTargetFunction]]. + let target = bound_function.target_function(); + + // 2. Assert: IsConstructor(target) is true. + + // 3. Let boundArgs be F.[[BoundArguments]]. + let bound_args = bound_function.args(); + + // 4. Let args be the list-concatenation of boundArgs and argumentsList. + let arguments_start_index = context.vm.stack.len() - argument_count; + context + .vm + .insert_values_at(bound_args, arguments_start_index); + + // 5. If SameValue(F, newTarget) is true, set newTarget to target. + let function_object: JsValue = function_object.clone().into(); + let new_target = if JsValue::same_value(&function_object, &new_target) { + target.clone().into() + } else { + new_target + }; + + // 6. Return ? Construct(target, args, newTarget). + context.vm.push(new_target); + Ok(target.__construct__(bound_args.len() + argument_count)) +} diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 92f79402f44..f2ee9c4041d 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -12,20 +12,26 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function use crate::{ - builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, + builtins::{ + BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, + }, bytecompiler::FunctionCompiler, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - environments::{EnvironmentStack, PrivateEnvironment}, + environments::{EnvironmentStack, FunctionSlots, PrivateEnvironment, ThisBindingStatus}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, - object::{JsFunction, PrivateElement, PrivateName}, + native_function::NativeFunctionObject, + object::{internal_methods::get_prototype_from_constructor, JsObject}, + object::{ + internal_methods::{CallValue, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, + JsData, JsFunction, PrivateElement, PrivateName, + }, property::{Attribute, PropertyDescriptor, PropertyKey}, realm::Realm, string::{common::StaticJsStrings, utf16}, symbol::JsSymbol, value::IntegerOrInfinity, - vm::{ActiveRunnable, CodeBlock}, + vm::{ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock}, Context, JsArgs, JsResult, JsString, JsValue, }; use boa_ast::{ @@ -42,7 +48,12 @@ use boa_profiler::Profiler; use std::io::Read; use thin_vec::ThinVec; +use super::Proxy; + pub(crate) mod arguments; +mod bound; + +pub use bound::BoundFunction; #[cfg(test)] mod tests; @@ -173,6 +184,27 @@ pub struct OrdinaryFunction { private_methods: ThinVec<(PrivateName, PrivateElement)>, } +impl JsData for OrdinaryFunction { + fn internal_methods(&self) -> &'static InternalObjectMethods { + static FUNCTION_METHODS: InternalObjectMethods = InternalObjectMethods { + __call__: function_call, + ..ORDINARY_INTERNAL_METHODS + }; + + static CONSTRUCTOR_METHODS: InternalObjectMethods = InternalObjectMethods { + __call__: function_call, + __construct__: function_construct, + ..ORDINARY_INTERNAL_METHODS + }; + + if self.code.has_prototype_property() { + &CONSTRUCTOR_METHODS + } else { + &FUNCTION_METHODS + } + } +} + impl OrdinaryFunction { pub(crate) fn new( code: Gc, @@ -752,15 +784,12 @@ impl BuiltInFunctionObject { return Err(JsNativeError::typ().with_message("not a function").into()); }; - let object = object.borrow(); - if object.is_native_function() { + let object_borrow = object.borrow(); + if object_borrow.is::() { let name = { // Is there a case here where if there is no name field on a value // name should default to None? Do all functions have names set? - let value = this - .as_object() - .expect("checked that `this` was an object above") - .get(utf16!("name"), &mut *context)?; + let value = object.get(utf16!("name"), &mut *context)?; if value.is_null_or_undefined() { js_string!() } else { @@ -770,12 +799,12 @@ impl BuiltInFunctionObject { return Ok( js_string!(utf16!("function "), &name, utf16!("() { [native code] }")).into(), ); - } else if object.is_proxy() || object.is_bound_function() { + } else if object_borrow.is::() || object_borrow.is::() { return Ok(js_string!(utf16!("function () { [native code] }")).into()); } - let function = object - .as_function() + let function = object_borrow + .downcast_ref::() .ok_or_else(|| JsNativeError::typ().with_message("not a function"))?; let code = function.codeblock(); @@ -865,70 +894,191 @@ pub(crate) fn set_function_name( .expect("defining the `name` property must not fail per the spec"); } -/// Binds a `Function Object` when `bind` is called. -#[derive(Debug, Trace, Finalize)] -pub struct BoundFunction { - target_function: JsObject, - this: JsValue, - args: Vec, +/// Call this object. +/// +/// # Panics +/// +/// Panics if the object is currently mutably borrowed. +// +// +pub(crate) fn function_call( + function_object: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + context.check_runtime_limits()?; + + let function = function_object + .downcast_ref::() + .expect("not a function"); + let realm = function.realm().clone(); + + if function.code.is_class_constructor() { + debug_assert!( + function.is_ordinary(), + "only ordinary functions can be classes" + ); + return Err(JsNativeError::typ() + .with_message("class constructor cannot be invoked without 'new'") + .with_realm(realm) + .into()); + } + + let code = function.code.clone(); + let environments = function.environments.clone(); + let script_or_module = function.script_or_module.clone(); + + drop(function); + + let env_fp = environments.len() as u32; + + let frame = CallFrame::new(code.clone(), script_or_module, environments, realm) + .with_argument_count(argument_count as u32) + .with_env_fp(env_fp); + + context.vm.push_frame(frame); + + let fp = context.vm.stack.len() - argument_count - CallFrame::FUNCTION_PROLOGUE; + context.vm.frame_mut().fp = fp as u32; + + let this = context.vm.stack[fp + CallFrame::THIS_POSITION].clone(); + + let lexical_this_mode = code.this_mode == ThisMode::Lexical; + + let this = if lexical_this_mode { + ThisBindingStatus::Lexical + } else if code.strict() { + ThisBindingStatus::Initialized(this.clone()) + } else if this.is_null_or_undefined() { + ThisBindingStatus::Initialized(context.realm().global_this().clone().into()) + } else { + ThisBindingStatus::Initialized( + this.to_object(context) + .expect("conversion cannot fail") + .into(), + ) + }; + + let mut last_env = 0; + + if code.has_binding_identifier() { + let index = context + .vm + .environments + .push_lexical(code.constant_compile_time_environment(last_env)); + context + .vm + .environments + .put_lexical_value(index, 0, function_object.clone().into()); + last_env += 1; + } + + context.vm.environments.push_function( + code.constant_compile_time_environment(last_env), + FunctionSlots::new(this, function_object.clone(), None), + ); + + Ok(CallValue::Ready) } -impl BoundFunction { - /// Abstract operation `BoundFunctionCreate` - /// - /// More information: - /// - [ECMAScript reference][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-boundfunctioncreate - pub fn create( - target_function: JsObject, - this: JsValue, - args: Vec, - context: &mut Context, - ) -> JsResult { - // 1. Let proto be ? targetFunction.[[GetPrototypeOf]](). - let proto = target_function.__get_prototype_of__(context)?; - let is_constructor = target_function.is_constructor(); - - // 2. Let internalSlotsList be the internal slots listed in Table 35, plus [[Prototype]] and [[Extensible]]. - // 3. Let obj be ! MakeBasicObject(internalSlotsList). - // 4. Set obj.[[Prototype]] to proto. - // 5. Set obj.[[Call]] as described in 10.4.1.1. - // 6. If IsConstructor(targetFunction) is true, then - // a. Set obj.[[Construct]] as described in 10.4.1.2. - // 7. Set obj.[[BoundTargetFunction]] to targetFunction. - // 8. Set obj.[[BoundThis]] to boundThis. - // 9. Set obj.[[BoundArguments]] to boundArgs. - // 10. Return obj. - Ok(JsObject::from_proto_and_data_with_shared_shape( +/// Construct an instance of this object with the specified arguments. +/// +/// # Panics +/// +/// Panics if the object is currently mutably borrowed. +// +fn function_construct( + this_function_object: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + context.check_runtime_limits()?; + + let function = this_function_object + .downcast_ref::() + .expect("not a function"); + let realm = function.realm().clone(); + + debug_assert!( + function.is_ordinary(), + "only ordinary functions can be constructed" + ); + + let code = function.code.clone(); + let environments = function.environments.clone(); + let script_or_module = function.script_or_module.clone(); + drop(function); + + let env_fp = environments.len() as u32; + + let new_target = context.vm.pop(); + + let at = context.vm.stack.len() - argument_count; + + let this = if code.is_derived_constructor() { + None + } else { + // If the prototype of the constructor is not an object, then use the default object + // prototype as prototype for the new object + // see + // see + let prototype = + get_prototype_from_constructor(&new_target, StandardConstructors::object, context)?; + let this = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), - proto, - ObjectData::bound_function( - Self { - target_function, - this, - args, - }, - is_constructor, - ), - )) - } + prototype, + OrdinaryObject, + ); - /// Get a reference to the bound function's this. - #[must_use] - pub const fn this(&self) -> &JsValue { - &self.this - } + this.initialize_instance_elements(this_function_object, context)?; - /// Get a reference to the bound function's target function. - #[must_use] - pub const fn target_function(&self) -> &JsObject { - &self.target_function - } + Some(this) + }; - /// Get a reference to the bound function's args. - #[must_use] - pub fn args(&self) -> &[JsValue] { - self.args.as_slice() + let frame = CallFrame::new(code.clone(), script_or_module, environments, realm) + .with_argument_count(argument_count as u32) + .with_env_fp(env_fp) + .with_flags(CallFrameFlags::CONSTRUCT); + + context.vm.push_frame(frame); + + context.vm.frame_mut().fp = at as u32 - 1; + + let mut last_env = 0; + + if code.has_binding_identifier() { + let index = context + .vm + .environments + .push_lexical(code.constant_compile_time_environment(last_env)); + context + .vm + .environments + .put_lexical_value(index, 0, this_function_object.clone().into()); + last_env += 1; } + + context.vm.environments.push_function( + code.constant_compile_time_environment(last_env), + FunctionSlots::new( + this.clone().map_or(ThisBindingStatus::Uninitialized, |o| { + ThisBindingStatus::Initialized(o.into()) + }), + this_function_object.clone(), + Some( + new_target + .as_object() + .expect("new.target should be an object") + .clone(), + ), + ), + ); + + // Insert `this` value + context + .vm + .stack + .insert(at - 1, this.map(JsValue::new).unwrap_or_default()); + + Ok(CallValue::Ready) } diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index 1b26793e9bf..1d4b5f9029f 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -21,7 +21,7 @@ use crate::{ symbol::JsSymbol, value::JsValue, vm::{CallFrame, CompletionRecord, GeneratorResumeKind}, - Context, JsArgs, JsError, JsResult, JsString, + Context, JsArgs, JsData, JsError, JsResult, JsString, }; use boa_gc::{custom_trace, Finalize, Trace}; use boa_profiler::Profiler; @@ -118,7 +118,7 @@ impl GeneratorContext { } /// The internal representation of a `Generator` object. -#[derive(Debug, Finalize, Trace)] +#[derive(Debug, Finalize, Trace, JsData)] pub struct Generator { /// The `[[GeneratorState]]` internal slot. pub(crate) state: GeneratorState, @@ -248,19 +248,16 @@ impl Generator { .with_message("Generator method called on non generator") .into()); }; - let mut generator_obj_mut = generator_obj.borrow_mut(); - let Some(generator) = generator_obj_mut.as_generator_mut() else { - return Err(JsNativeError::typ() - .with_message("generator resumed on non generator object") - .into()); - }; + let mut gen = generator_obj.downcast_mut::().ok_or_else(|| { + JsNativeError::typ().with_message("generator resumed on non generator object") + })?; // 4. Let genContext be generator.[[GeneratorContext]]. // 5. Let methodContext be the running execution context. // 6. Suspend methodContext. // 7. Set generator.[[GeneratorState]] to executing. let (mut generator_context, first_execution) = - match std::mem::replace(&mut generator.state, GeneratorState::Executing) { + match std::mem::replace(&mut gen.state, GeneratorState::Executing) { GeneratorState::Executing => { return Err(JsNativeError::typ() .with_message("Generator should not be executing") @@ -268,7 +265,7 @@ impl Generator { } // 2. If state is completed, return CreateIterResultObject(undefined, true). GeneratorState::Completed => { - generator.state = GeneratorState::Completed; + gen.state = GeneratorState::Completed; return Ok(create_iter_result_object( JsValue::undefined(), true, @@ -280,7 +277,7 @@ impl Generator { GeneratorState::SuspendedYield { context } => (context, false), }; - drop(generator_obj_mut); + drop(gen); let record = generator_context.resume( (!first_execution).then_some(value), @@ -288,9 +285,8 @@ impl Generator { context, ); - let mut generator_obj_mut = generator_obj.borrow_mut(); - let generator = generator_obj_mut - .as_generator_mut() + let mut gen = generator_obj + .downcast_mut::() .expect("already checked this object type"); // 8. Push genContext onto the execution context stack; genContext is now the running execution context. @@ -299,17 +295,17 @@ impl Generator { // 11. Return Completion(result). match record { CompletionRecord::Return(value) => { - generator.state = GeneratorState::SuspendedYield { + gen.state = GeneratorState::SuspendedYield { context: generator_context, }; Ok(value) } CompletionRecord::Normal(value) => { - generator.state = GeneratorState::Completed; + gen.state = GeneratorState::Completed; Ok(create_iter_result_object(value, true, context)) } CompletionRecord::Throw(err) => { - generator.state = GeneratorState::Completed; + gen.state = GeneratorState::Completed; Err(err) } } @@ -332,12 +328,9 @@ impl Generator { .with_message("Generator method called on non generator") .into()); }; - let mut generator_obj_mut = generator_obj.borrow_mut(); - let Some(generator) = generator_obj_mut.as_generator_mut() else { - return Err(JsNativeError::typ() - .with_message("generator resumed on non generator object") - .into()); - }; + let mut gen = generator_obj.downcast_mut::().ok_or_else(|| { + JsNativeError::typ().with_message("generator resumed on non generator object") + })?; // 4. Assert: state is suspendedYield. // 5. Let genContext be generator.[[GeneratorContext]]. @@ -345,7 +338,7 @@ impl Generator { // 7. Suspend methodContext. // 8. Set generator.[[GeneratorState]] to executing. let mut generator_context = - match std::mem::replace(&mut generator.state, GeneratorState::Executing) { + match std::mem::replace(&mut gen.state, GeneratorState::Executing) { GeneratorState::Executing => { return Err(JsNativeError::typ() .with_message("Generator should not be executing") @@ -355,7 +348,7 @@ impl Generator { // 3. If state is completed, then GeneratorState::SuspendedStart { .. } | GeneratorState::Completed => { // a. Set generator.[[GeneratorState]] to completed. - generator.state = GeneratorState::Completed; + gen.state = GeneratorState::Completed; // b. Once a generator enters the completed state it never leaves it and its // associated execution context is never resumed. Any execution state associated @@ -378,7 +371,7 @@ impl Generator { // 10. Resume the suspended evaluation of genContext using abruptCompletion as the result of the operation that suspended it. Let result be the completion record returned by the resumed computation. // 11. Assert: When we return here, genContext has already been removed from the execution context stack and methodContext is the currently running execution context. // 12. Return Completion(result). - drop(generator_obj_mut); + drop(gen); let (value, resume_kind) = match abrupt_completion { Ok(value) => (value, GeneratorResumeKind::Return), @@ -387,24 +380,23 @@ impl Generator { let record = generator_context.resume(Some(value), resume_kind, context); - let mut generator_obj_mut = generator_obj.borrow_mut(); - let generator = generator_obj_mut - .as_generator_mut() - .expect("already checked this object type"); + let mut gen = generator_obj.downcast_mut::().ok_or_else(|| { + JsNativeError::typ().with_message("generator resumed on non generator object") + })?; match record { CompletionRecord::Return(value) => { - generator.state = GeneratorState::SuspendedYield { + gen.state = GeneratorState::SuspendedYield { context: generator_context, }; Ok(value) } CompletionRecord::Normal(value) => { - generator.state = GeneratorState::Completed; + gen.state = GeneratorState::Completed; Ok(create_iter_result_object(value, true, context)) } CompletionRecord::Throw(err) => { - generator.state = GeneratorState::Completed; + gen.state = GeneratorState::Completed; Err(err) } } diff --git a/boa_engine/src/builtins/intl/collator/mod.rs b/boa_engine/src/builtins/intl/collator/mod.rs index 92bc74b830c..9f95dd17055 100644 --- a/boa_engine/src/builtins/intl/collator/mod.rs +++ b/boa_engine/src/builtins/intl/collator/mod.rs @@ -14,6 +14,7 @@ use icu_provider::DataLocale; use crate::{ builtins::{ options::get_option, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + OrdinaryObject, }, context::{ intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -23,13 +24,13 @@ use crate::{ native_function::NativeFunction, object::{ internal_methods::get_prototype_from_constructor, FunctionObjectBuilder, JsFunction, - JsObject, ObjectData, + JsObject, }, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, symbol::JsSymbol, - Context, JsArgs, JsNativeError, JsResult, JsString, JsValue, + Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsValue, }; use super::{ @@ -41,7 +42,7 @@ use super::{ mod options; pub(crate) use options::*; -#[derive(Debug)] +#[derive(Debug, Finalize, JsData)] pub(crate) struct Collator { locale: Locale, collation: Value, @@ -54,8 +55,6 @@ pub(crate) struct Collator { bound_compare: Option, } -impl Finalize for Collator {} - // SAFETY: only `bound_compare` is a traceable object. unsafe impl Trace for Collator { custom_trace!(this, mark(&this.bound_compare)); @@ -350,7 +349,7 @@ impl BuiltInConstructor for Collator { let collator = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::native_object(Self { + Self { locale, collation, numeric, @@ -360,7 +359,7 @@ impl BuiltInConstructor for Collator { ignore_punctuation, collator, bound_compare: None, - }), + }, ); // 31. Return collator. @@ -413,8 +412,7 @@ impl Collator { .with_message("`resolvedOptions` can only be called on a `Collator` object") })?; let collator_obj = this.clone(); - let mut collator = this.borrow_mut(); - let collator = collator.downcast_mut::().ok_or_else(|| { + let mut collator = this.downcast_mut::().ok_or_else(|| { JsNativeError::typ() .with_message("`resolvedOptions` can only be called on a `Collator` object") })?; @@ -434,7 +432,6 @@ impl Collator { |_, args, collator, context| { // 1. Let collator be F.[[Collator]]. // 2. Assert: Type(collator) is Object and collator has an [[InitializedCollator]] internal slot. - let collator = collator.borrow(); let collator = collator .downcast_ref::() .expect("checked above that the object was a collator object"); @@ -479,21 +476,20 @@ impl Collator { fn resolved_options(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let collator be the this value. // 2. Perform ? RequireInternalSlot(collator, [[InitializedCollator]]). - let collator = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ() - .with_message("`resolvedOptions` can only be called on a `Collator` object") - })?; - let collator = collator.downcast_ref::().ok_or_else(|| { - JsNativeError::typ() - .with_message("`resolvedOptions` can only be called on a `Collator` object") - })?; + let collator = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("`resolvedOptions` can only be called on a `Collator` object") + })?; // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). let options = context .intrinsics() .templates() .ordinary_object() - .create(ObjectData::ordinary(), vec![]); + .create(OrdinaryObject, vec![]); // 4. For each row of Table 4, except the header row, in table order, do // a. Let p be the Property value of the current row. diff --git a/boa_engine/src/builtins/intl/date_time_format.rs b/boa_engine/src/builtins/intl/date_time_format.rs index ed6a866fa7b..0a466f08d91 100644 --- a/boa_engine/src/builtins/intl/date_time_format.rs +++ b/boa_engine/src/builtins/intl/date_time_format.rs @@ -10,14 +10,15 @@ use crate::{ builtins::{ options::OptionType, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + OrdinaryObject, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, realm::Realm, string::{common::StaticJsStrings, utf16}, - Context, JsResult, JsString, JsValue, + Context, JsData, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; @@ -39,7 +40,7 @@ impl OptionType for HourCycle { } /// JavaScript `Intl.DateTimeFormat` object. -#[derive(Debug, Clone, Trace, Finalize)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] pub(crate) struct DateTimeFormat { initialized_date_time_format: bool, locale: JsString, @@ -123,7 +124,7 @@ impl BuiltInConstructor for DateTimeFormat { let date_time_format = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::native_object(Self { + Self { initialized_date_time_format: true, locale: js_string!("en-US"), calendar: js_string!("gregory"), @@ -143,7 +144,7 @@ impl BuiltInConstructor for DateTimeFormat { hour_cycle: js_string!("h24"), pattern: js_string!("{hour}:{minute}"), bound_format: js_string!("undefined"), - }), + }, ); // TODO 3. Perform ? InitializeDateTimeFormat(dateTimeFormat, locales, options). @@ -194,7 +195,7 @@ pub(crate) fn to_date_time_options( let options = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), options, - ObjectData::ordinary(), + OrdinaryObject, ); // 3. Let needDefaults be true. diff --git a/boa_engine/src/builtins/intl/list_format/mod.rs b/boa_engine/src/builtins/intl/list_format/mod.rs index 82c040a319c..f126706818d 100644 --- a/boa_engine/src/builtins/intl/list_format/mod.rs +++ b/boa_engine/src/builtins/intl/list_format/mod.rs @@ -1,6 +1,6 @@ use std::fmt::Write; -use boa_gc::{empty_trace, Finalize, Trace}; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use icu_list::{provider::AndListV1Marker, ListFormatter, ListLength}; use icu_locid::Locale; @@ -9,16 +9,16 @@ use icu_provider::DataLocale; use crate::{ builtins::{ options::{get_option, get_options_object}, - Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, + Array, BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject, OrdinaryObject, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, symbol::JsSymbol, - Context, JsArgs, JsNativeError, JsResult, JsString, JsValue, + Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsValue, }; use super::{ @@ -30,7 +30,9 @@ use super::{ mod options; pub(crate) use options::*; -#[derive(Debug, Finalize)] +#[derive(Debug, Trace, Finalize, JsData)] +// Safety: `ListFormat` only contains non-traceable types. +#[boa_gc(unsafe_empty_trace)] pub(crate) struct ListFormat { locale: Locale, typ: ListFormatType, @@ -38,11 +40,6 @@ pub(crate) struct ListFormat { native: ListFormatter, } -// SAFETY: `ListFormat` doesn't contain traceable data. -unsafe impl Trace for ListFormat { - empty_trace!(); -} - impl Service for ListFormat { type LangMarker = AndListV1Marker; @@ -170,12 +167,12 @@ impl BuiltInConstructor for ListFormat { let list_format = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::native_object(Self { + Self { locale, typ, style, native: formatter, - }), + }, ); // 19. Return listFormat. @@ -380,7 +377,7 @@ impl ListFormat { .intrinsics() .templates() .ordinary_object() - .create(ObjectData::ordinary(), vec![]); + .create(OrdinaryObject, vec![]); // b. Perform ! CreateDataPropertyOrThrow(O, "type", part.[[Type]]). o.create_data_property_or_throw(utf16!("type"), js_string!(part.typ()), context) @@ -429,7 +426,7 @@ impl ListFormat { .intrinsics() .templates() .ordinary_object() - .create(ObjectData::ordinary(), vec![]); + .create(OrdinaryObject, vec![]); // 4. For each row of Table 11, except the header row, in table order, do // a. Let p be the Property value of the current row. diff --git a/boa_engine/src/builtins/intl/locale/mod.rs b/boa_engine/src/builtins/intl/locale/mod.rs index c6adeb8201d..15fd7f1b667 100644 --- a/boa_engine/src/builtins/intl/locale/mod.rs +++ b/boa_engine/src/builtins/intl/locale/mod.rs @@ -22,7 +22,7 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, symbol::JsSymbol, Context, JsArgs, JsNativeError, JsResult, JsString, JsValue, @@ -368,11 +368,8 @@ impl BuiltInConstructor for Locale { // 6. Let locale be ? OrdinaryCreateFromConstructor(NewTarget, "%Locale.prototype%", internalSlotsList). let prototype = get_prototype_from_constructor(new_target, StandardConstructors::locale, context)?; - let locale = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::native_object(tag), - ); + let locale = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, tag); // 37. Return locale. Ok(locale.into()) @@ -410,12 +407,10 @@ impl Locale { // 4. Return ! Construct(%Locale%, maximal). let prototype = context.intrinsics().constructors().locale().prototype(); - Ok(JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::native_object(loc), + Ok( + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, loc) + .into(), ) - .into()) } /// [`Intl.Locale.prototype.minimize ( )`][spec] @@ -448,12 +443,10 @@ impl Locale { // 4. Return ! Construct(%Locale%, minimal). let prototype = context.intrinsics().constructors().locale().prototype(); - Ok(JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::native_object(loc), + Ok( + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, loc) + .into(), ) - .into()) } /// [`Intl.Locale.prototype.toString ( )`][spec]. diff --git a/boa_engine/src/builtins/intl/options.rs b/boa_engine/src/builtins/intl/options.rs index 2a9f17d9d7b..bef7c8b3326 100644 --- a/boa_engine/src/builtins/intl/options.rs +++ b/boa_engine/src/builtins/intl/options.rs @@ -3,8 +3,8 @@ use std::{fmt::Display, str::FromStr}; use num_traits::FromPrimitive; use crate::{ - builtins::options::ParsableOptionType, - object::{JsObject, ObjectData}, + builtins::{options::ParsableOptionType, OrdinaryObject}, + object::JsObject, Context, JsNativeError, JsResult, JsValue, }; @@ -131,7 +131,7 @@ pub(super) fn coerce_options_to_object( return Ok(JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), None, - ObjectData::ordinary(), + OrdinaryObject, )); } diff --git a/boa_engine/src/builtins/intl/plural_rules/mod.rs b/boa_engine/src/builtins/intl/plural_rules/mod.rs index b79ca9a106a..02aa1cf9a25 100644 --- a/boa_engine/src/builtins/intl/plural_rules/mod.rs +++ b/boa_engine/src/builtins/intl/plural_rules/mod.rs @@ -1,6 +1,6 @@ mod options; -use boa_gc::{empty_trace, Finalize, Trace}; +use boa_gc::{Finalize, Trace}; use boa_macros::utf16; use boa_profiler::Profiler; use fixed_decimal::FixedDecimal; @@ -18,11 +18,11 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, ObjectData, ObjectInitializer}, + object::{internal_methods::get_prototype_from_constructor, ObjectInitializer}, property::Attribute, realm::Realm, string::common::StaticJsStrings, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use super::{ @@ -35,7 +35,9 @@ use super::{ Service, }; -#[derive(Debug, Finalize)] +#[derive(Debug, Trace, Finalize, JsData)] +// SAFETY: `PluralRules` doesn't contain any traceable data. +#[boa_gc(unsafe_empty_trace)] pub(crate) struct PluralRules { locale: Locale, native: PluralRulesWithRanges, @@ -43,11 +45,6 @@ pub(crate) struct PluralRules { format_options: DigitFormatOptions, } -// SAFETY: `PluralRules` doesn't contain any traceable data. -unsafe impl Trace for PluralRules { - empty_trace!(); -} - impl Service for PluralRules { type LangMarker = CardinalV1Marker; @@ -170,12 +167,12 @@ impl BuiltInConstructor for PluralRules { Ok(JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), proto, - ObjectData::native_object(Self { + Self { locale, native, rule_type, format_options, - }), + }, ) .into()) } diff --git a/boa_engine/src/builtins/intl/segmenter/iterator.rs b/boa_engine/src/builtins/intl/segmenter/iterator.rs index 55e4753102c..2777e78ede5 100644 --- a/boa_engine/src/builtins/intl/segmenter/iterator.rs +++ b/boa_engine/src/builtins/intl/segmenter/iterator.rs @@ -8,10 +8,9 @@ use crate::{ builtins::{iterable::create_iter_result_object, BuiltInBuilder, IntrinsicObject}, context::intrinsics::Intrinsics, js_string, - object::ObjectData, property::Attribute, realm::Realm, - Context, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use super::{create_segment_data_object, Segmenter}; @@ -46,7 +45,7 @@ impl NativeSegmentIterator<'_, '_> { } } -#[derive(Debug, Trace, Finalize)] +#[derive(Debug, Trace, Finalize, JsData)] pub(crate) struct SegmentIterator { segmenter: JsObject, string: JsString, @@ -90,11 +89,11 @@ impl SegmentIterator { .objects() .iterator_prototypes() .segment(), - ObjectData::native_object(Self { + Self { segmenter, string, next_segment_index: 0, - }), + }, ) } /// [`%SegmentIteratorPrototype%.next ( )`][spec] @@ -103,14 +102,13 @@ impl SegmentIterator { fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let iterator be the this value. // 2. Perform ? RequireInternalSlot(iterator, [[IteratingSegmenter]]). - let mut iter = this.as_object().map(JsObject::borrow_mut).ok_or_else(|| { - JsNativeError::typ() - .with_message("`next` can only be called on a `Segment Iterator` object") - })?; - let iter = iter.downcast_mut::().ok_or_else(|| { - JsNativeError::typ() - .with_message("`next` can only be called on a `Segment Iterator` object") - })?; + let mut iter = this + .as_object() + .and_then(|o| o.downcast_mut::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("`next` can only be called on a `Segment Iterator` object") + })?; // 5. Let startIndex be iterator.[[IteratedStringNextSegmentCodeUnitIndex]]. let start = iter.next_segment_index; diff --git a/boa_engine/src/builtins/intl/segmenter/mod.rs b/boa_engine/src/builtins/intl/segmenter/mod.rs index 547d7891046..0271510f538 100644 --- a/boa_engine/src/builtins/intl/segmenter/mod.rs +++ b/boa_engine/src/builtins/intl/segmenter/mod.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use boa_gc::{empty_trace, Finalize, Trace}; +use boa_gc::{Finalize, Trace}; use boa_macros::utf16; use boa_profiler::Profiler; use icu_locid::Locale; @@ -15,13 +15,11 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{ - internal_methods::get_prototype_from_constructor, JsObject, ObjectData, ObjectInitializer, - }, + object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectInitializer}, property::Attribute, realm::Realm, string::common::StaticJsStrings, - Context, JsArgs, JsNativeError, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsResult, JsString, JsSymbol, JsValue, }; mod iterator; @@ -37,17 +35,14 @@ use super::{ Service, }; -#[derive(Debug, Finalize)] +#[derive(Debug, Trace, Finalize, JsData)] +// SAFETY: `Segmenter` doesn't contain any traceable data. +#[boa_gc(unsafe_empty_trace)] pub(crate) struct Segmenter { locale: Locale, native: NativeSegmenter, } -// SAFETY: `Segmenter` doesn't contain any traceable data. -unsafe impl Trace for Segmenter { - empty_trace!(); -} - #[derive(Debug)] pub(crate) enum NativeSegmenter { Grapheme(Box), @@ -180,11 +175,8 @@ impl BuiltInConstructor for Segmenter { let proto = get_prototype_from_constructor(new_target, StandardConstructors::segmenter, context)?; - let segmenter = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - proto, - ObjectData::native_object(segmenter), - ); + let segmenter = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, segmenter); // 14. Return segmenter. Ok(segmenter.into()) @@ -232,14 +224,14 @@ impl Segmenter { fn resolved_options(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let segmenter be the this value. // 2. Perform ? RequireInternalSlot(segmenter, [[InitializedSegmenter]]). - let segmenter = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ() - .with_message("`resolved_options` can only be called on an `Intl.Segmenter` object") - })?; - let segmenter = segmenter.downcast_ref::().ok_or_else(|| { - JsNativeError::typ() - .with_message("`resolved_options` can only be called on an `Intl.Segmenter` object") - })?; + let segmenter = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message( + "`resolved_options` can only be called on an `Intl.Segmenter` object", + ) + })?; // 3. Let options be OrdinaryObjectCreate(%Object.prototype%). // 4. For each row of Table 19, except the header row, in table order, do diff --git a/boa_engine/src/builtins/intl/segmenter/segments.rs b/boa_engine/src/builtins/intl/segmenter/segments.rs index 680c4a8647c..4209c366386 100644 --- a/boa_engine/src/builtins/intl/segmenter/segments.rs +++ b/boa_engine/src/builtins/intl/segmenter/segments.rs @@ -6,14 +6,13 @@ use crate::{ builtins::{BuiltInBuilder, IntrinsicObject}, context::intrinsics::Intrinsics, js_string, - object::ObjectData, realm::Realm, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; use super::{create_segment_data_object, SegmentIterator, Segmenter}; -#[derive(Debug, Trace, Finalize)] +#[derive(Debug, Trace, Finalize, JsData)] pub(crate) struct Segments { segmenter: JsObject, string: JsString, @@ -47,7 +46,7 @@ impl Segments { JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().objects().segments_prototype(), - ObjectData::native_object(Self { segmenter, string }), + Self { segmenter, string }, ) } @@ -57,14 +56,13 @@ impl Segments { fn containing(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let segments be the this value. // 2. Perform ? RequireInternalSlot(segments, [[SegmentsSegmenter]]). - let segments = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ() - .with_message("`containing` can only be called on a `Segments` object") - })?; - let segments = segments.downcast_ref::().ok_or_else(|| { - JsNativeError::typ() - .with_message("`containing` can only be called on a `Segments` object") - })?; + let segments = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("`containing` can only be called on a `Segments` object") + })?; // 3. Let segmenter be segments.[[SegmentsSegmenter]]. let segmenter = segments.segmenter.borrow(); diff --git a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs index 7f6ab6e0c58..68221e7db67 100644 --- a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -7,10 +7,10 @@ use crate::{ context::intrinsics::Intrinsics, js_string, native_function::NativeFunction, - object::{FunctionObjectBuilder, JsObject, ObjectData}, + object::{FunctionObjectBuilder, JsObject}, realm::Realm, string::utf16, - Context, JsArgs, JsResult, JsValue, + Context, JsArgs, JsData, JsResult, JsValue, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -21,7 +21,7 @@ use boa_profiler::Profiler; /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-properties-of-async-from-sync-iterator-instances -#[derive(Clone, Debug, Finalize, Trace)] +#[derive(Clone, Debug, Finalize, Trace, JsData)] pub struct AsyncFromSyncIterator { // The [[SyncIteratorRecord]] internal slot. sync_iterator_record: IteratorRecord, @@ -73,9 +73,9 @@ impl AsyncFromSyncIterator { .objects() .iterator_prototypes() .async_from_sync_iterator(), - ObjectData::async_from_sync_iterator(Self { + Self { sync_iterator_record, - }), + }, ); // 3. Let nextMethod be ! Get(asyncIterator, "next"). @@ -100,9 +100,7 @@ impl AsyncFromSyncIterator { // 4. Let syncIteratorRecord be O.[[SyncIteratorRecord]]. let sync_iterator_record = this .as_object() - .expect("async from sync iterator prototype must be object") - .borrow() - .as_async_from_sync_iterator() + .and_then(|o| o.downcast_ref::()) .expect("async from sync iterator prototype must be object") .sync_iterator_record .clone(); @@ -129,7 +127,7 @@ impl AsyncFromSyncIterator { .and_then(IteratorResult::from_value); // 7. IfAbruptRejectPromise(result, promiseCapability). - if_abrupt_reject_promise!(result, promise_capability, context); + let result = if_abrupt_reject_promise!(result, promise_capability, context); // 8. Return AsyncFromSyncIteratorContinuation(result, promiseCapability). Self::continuation(&result, &promise_capability, context) @@ -147,9 +145,7 @@ impl AsyncFromSyncIterator { // 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]]. let sync_iterator = this .as_object() - .expect("async from sync iterator prototype must be object") - .borrow() - .as_async_from_sync_iterator() + .and_then(|o| o.downcast_ref::()) .expect("async from sync iterator prototype must be object") .sync_iterator_record .iterator() @@ -166,7 +162,7 @@ impl AsyncFromSyncIterator { let r#return = sync_iterator.get_method(utf16!("return"), context); // 6. IfAbruptRejectPromise(return, promiseCapability). - if_abrupt_reject_promise!(r#return, promise_capability, context); + let r#return = if_abrupt_reject_promise!(r#return, promise_capability, context); let result = match (r#return, args.get(0)) { // 7. If return is undefined, then @@ -201,7 +197,7 @@ impl AsyncFromSyncIterator { let result = result.and_then(IteratorResult::from_value); // 10. IfAbruptRejectPromise(result, promiseCapability). - if_abrupt_reject_promise!(result, promise_capability, context); + let result = if_abrupt_reject_promise!(result, promise_capability, context); // 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability). Self::continuation(&result, &promise_capability, context) @@ -219,9 +215,7 @@ impl AsyncFromSyncIterator { // 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]]. let sync_iterator = this .as_object() - .expect("async from sync iterator prototype must be object") - .borrow() - .as_async_from_sync_iterator() + .and_then(|o| o.downcast_ref::()) .expect("async from sync iterator prototype must be object") .sync_iterator_record .iterator() @@ -238,7 +232,7 @@ impl AsyncFromSyncIterator { let throw = sync_iterator.get_method(utf16!("throw"), context); // 6. IfAbruptRejectPromise(throw, promiseCapability). - if_abrupt_reject_promise!(throw, promise_capability, context); + let throw = if_abrupt_reject_promise!(throw, promise_capability, context); let result = match (throw, args.get(0)) { // 7. If throw is undefined, then @@ -273,7 +267,7 @@ impl AsyncFromSyncIterator { let result = result.and_then(IteratorResult::from_value); // 10. IfAbruptRejectPromise(result, promiseCapability). - if_abrupt_reject_promise!(result, promise_capability, context); + let result = if_abrupt_reject_promise!(result, promise_capability, context); // 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability). Self::continuation(&result, &promise_capability, context) @@ -298,13 +292,13 @@ impl AsyncFromSyncIterator { let done = result.complete(context); // 3. IfAbruptRejectPromise(done, promiseCapability). - if_abrupt_reject_promise!(done, promise_capability, context); + let done = if_abrupt_reject_promise!(done, promise_capability, context); // 4. Let value be Completion(IteratorValue(result)). let value = result.value(context); // 5. IfAbruptRejectPromise(value, promiseCapability). - if_abrupt_reject_promise!(value, promise_capability, context); + let value = if_abrupt_reject_promise!(value, promise_capability, context); // 6. Let valueWrapper be Completion(PromiseResolve(%Promise%, value)). let value_wrapper = Promise::promise_resolve( @@ -314,7 +308,7 @@ impl AsyncFromSyncIterator { ); // 7. IfAbruptRejectPromise(valueWrapper, promiseCapability). - if_abrupt_reject_promise!(value_wrapper, promise_capability, context); + let value_wrapper = if_abrupt_reject_promise!(value_wrapper, promise_capability, context); // 8. Let unwrap be a new Abstract Closure with parameters (value) // that captures done and performs the following steps when called: diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index 6b0c723063c..2d15325171e 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -5,7 +5,7 @@ use crate::{ context::intrinsics::Intrinsics, error::JsNativeError, js_string, - object::{JsObject, ObjectData}, + object::JsObject, realm::Realm, symbol::JsSymbol, Context, JsResult, JsValue, @@ -39,6 +39,8 @@ macro_rules! if_abrupt_close_iterator { // Export macro to crate level pub(crate) use if_abrupt_close_iterator; +use super::OrdinaryObject; + /// The built-in iterator prototypes. #[derive(Debug, Default, Trace, Finalize)] pub struct IteratorPrototypes { @@ -206,7 +208,7 @@ pub fn create_iter_result_object(value: JsValue, done: bool, context: &mut Conte .intrinsics() .templates() .iterator_result() - .create(ObjectData::ordinary(), vec![value, done.into()]); + .create(OrdinaryObject, vec![value, done.into()]); // 5. Return obj. obj.into() diff --git a/boa_engine/src/builtins/json/mod.rs b/boa_engine/src/builtins/json/mod.rs index 633fd6e599e..ca79b2b56c9 100644 --- a/boa_engine/src/builtins/json/mod.rs +++ b/boa_engine/src/builtins/json/mod.rs @@ -30,7 +30,7 @@ use crate::{ symbol::JsSymbol, value::IntegerOrInfinity, vm::{CallFrame, CallFrameFlags}, - Context, JsArgs, JsResult, JsString, JsValue, + Context, JsArgs, JsBigInt, JsResult, JsString, JsValue, }; use boa_gc::Gc; use boa_parser::{Parser, Source}; @@ -315,7 +315,7 @@ impl Json { ); } else if let Some(obj) = v.as_object() { // i. If v has a [[StringData]] or [[NumberData]] internal slot, set item to ? ToString(v). - if obj.is_string() || obj.is_number() { + if obj.is::() || obj.is::() { property_set.insert(v.to_string(context)?); } } @@ -333,12 +333,12 @@ impl Json { // 5. If Type(space) is Object, then if let Some(space_obj) = space.as_object() { // a. If space has a [[NumberData]] internal slot, then - if space_obj.is_number() { + if space_obj.is::() { // i. Set space to ? ToNumber(space). space = space.to_number(context)?.into(); } // b. Else if space has a [[StringData]] internal slot, then - else if space_obj.is_string() { + else if space_obj.is::() { // i. Set space to ? ToString(space). space = space.to_string(context)?.into(); } @@ -438,22 +438,22 @@ impl Json { // 4. If Type(value) is Object, then if let Some(obj) = value.as_object().cloned() { // a. If value has a [[NumberData]] internal slot, then - if obj.is_number() { + if obj.is::() { // i. Set value to ? ToNumber(value). value = value.to_number(context)?.into(); } // b. Else if value has a [[StringData]] internal slot, then - else if obj.is_string() { + else if obj.is::() { // i. Set value to ? ToString(value). value = value.to_string(context)?.into(); } // c. Else if value has a [[BooleanData]] internal slot, then - else if let Some(boolean) = obj.borrow().as_boolean() { + else if let Some(boolean) = obj.downcast_ref::() { // i. Set value to value.[[BooleanData]]. - value = boolean.into(); + value = (*boolean).into(); } // d. Else if value has a [[BigIntData]] internal slot, then - else if let Some(bigint) = obj.borrow().as_bigint() { + else if let Some(bigint) = obj.downcast_ref::() { // i. Set value to value.[[BigIntData]]. value = bigint.clone().into(); } diff --git a/boa_engine/src/builtins/map/map_iterator.rs b/boa_engine/src/builtins/map/map_iterator.rs index 5a694742903..a10a08e7f40 100644 --- a/boa_engine/src/builtins/map/map_iterator.rs +++ b/boa_engine/src/builtins/map/map_iterator.rs @@ -5,7 +5,7 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-map-iterator-objects -use super::ordered_map::MapLock; +use super::ordered_map::{MapLock, OrderedMap}; use crate::{ builtins::{ iterable::create_iter_result_object, Array, BuiltInBuilder, IntrinsicObject, JsValue, @@ -13,11 +13,11 @@ use crate::{ context::intrinsics::Intrinsics, error::JsNativeError, js_string, - object::{JsObject, ObjectData}, + object::JsObject, property::{Attribute, PropertyNameKind}, realm::Realm, symbol::JsSymbol, - Context, JsResult, + Context, JsData, JsResult, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -28,7 +28,7 @@ use boa_profiler::Profiler; /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-map-iterator-objects -#[derive(Debug, Finalize, Trace)] +#[derive(Debug, Finalize, Trace, JsData)] pub struct MapIterator { iterated_map: Option, map_next_index: usize, @@ -78,7 +78,7 @@ impl MapIterator { context: &mut Context, ) -> JsResult { if let Some(map_obj) = map.as_object() { - if let Some(map) = map_obj.borrow_mut().as_map_mut() { + if let Some(mut map) = map_obj.downcast_mut::>() { let lock = map.lock(map_obj.clone()); let iter = Self { iterated_map: Some(map_obj.clone()), @@ -89,7 +89,7 @@ impl MapIterator { let map_iterator = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().objects().iterator_prototypes().map(), - ObjectData::map_iterator(iter), + iter, ); return Ok(map_iterator.into()); } @@ -108,18 +108,18 @@ impl MapIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%mapiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut map_iterator = this.as_object().map(JsObject::borrow_mut); - let map_iterator = map_iterator - .as_mut() - .and_then(|obj| obj.as_map_iterator_mut()) + let mut map_iterator = this + .as_object() + .and_then(|o| o.downcast_mut::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a MapIterator"))?; let item_kind = map_iterator.map_iteration_kind; if let Some(obj) = map_iterator.iterated_map.take() { let e = { - let map = obj.borrow(); - let entries = map.as_map().expect("iterator should only iterate maps"); + let entries = obj + .downcast_ref::>() + .expect("iterator should only iterate maps"); let len = entries.full_len(); loop { let element = entries diff --git a/boa_engine/src/builtins/map/mod.rs b/boa_engine/src/builtins/map/mod.rs index 6efd04acaa4..15275e62aa0 100644 --- a/boa_engine/src/builtins/map/mod.rs +++ b/boa_engine/src/builtins/map/mod.rs @@ -15,7 +15,7 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::{Attribute, PropertyNameKind}, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -141,7 +141,7 @@ impl BuiltInConstructor for Map { let map = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::map(OrderedMap::new()), + >::new(), ); // 4. If iterable is either undefined or null, return map. @@ -229,7 +229,7 @@ impl Map { if let Some(object) = this.as_object() { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. - if let Some(map) = object.borrow_mut().as_map_mut() { + if let Some(mut map) = object.downcast_mut::>() { let key = match key { JsValue::Rational(r) => { // 5. If key is -0𝔽, set key to +0𝔽. @@ -273,7 +273,7 @@ impl Map { if let Some(object) = this.as_object() { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. - if let Some(map) = object.borrow_mut().as_map_mut() { + if let Some(map) = object.downcast_mut::>() { // 4. Let count be 0. // 5. For each Record { [[Key]], [[Value]] } p of entries, do // a. If p.[[Key]] is not empty, set count to count + 1. @@ -309,7 +309,7 @@ impl Map { if let Some(object) = this.as_object() { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. - if let Some(map) = object.borrow_mut().as_map_mut() { + if let Some(mut map) = object.downcast_mut::>() { // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, then // i. Set p.[[Key]] to empty. // ii. Set p.[[Value]] to empty. @@ -345,7 +345,7 @@ impl Map { if let JsValue::Object(ref object) = this { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. - if let Some(map) = object.borrow().as_map() { + if let Some(map) = object.downcast_ref::>() { // 4. For each Record { [[Key]], [[Value]] } p of entries, do // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return p.[[Value]]. // 5. Return undefined. @@ -373,7 +373,7 @@ impl Map { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). if let Some(object) = this.as_object() { // 3. Let entries be the List that is M.[[MapData]]. - if let Some(map) = object.borrow_mut().as_map_mut() { + if let Some(mut map) = object.downcast_mut::>() { // 4. For each Record { [[Key]], [[Value]] } p of entries, do // a. Set p.[[Key]] to empty. // b. Set p.[[Value]] to empty. @@ -410,7 +410,7 @@ impl Map { if let JsValue::Object(ref object) = this { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). // 3. Let entries be the List that is M.[[MapData]]. - if let Some(map) = object.borrow().as_map() { + if let Some(map) = object.downcast_ref::>() { // 4. For each Record { [[Key]], [[Value]] } p of entries, do // a. If p.[[Key]] is not empty and SameValueZero(p.[[Key]], key) is true, return true. // 5. Return false. @@ -442,7 +442,7 @@ impl Map { // 2. Perform ? RequireInternalSlot(M, [[MapData]]). let map = this .as_object() - .filter(|obj| obj.is_map()) + .filter(|obj| obj.is::>()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a Map"))?; // 3. If IsCallable(callbackfn) is false, throw a TypeError exception. @@ -463,8 +463,7 @@ impl Map { // Keys that are deleted after the call to forEach begins and before being visited // are not visited unless the key is added again before the forEach call completes. let _lock = map - .borrow_mut() - .as_map_mut() + .downcast_mut::>() .expect("checked that `this` was a map") .lock(map.clone()); @@ -473,8 +472,9 @@ impl Map { let mut index = 0; loop { let arguments = { - let map = map.borrow(); - let map = map.as_map().expect("checked that `this` was a map"); + let map = map + .downcast_ref::>() + .expect("checked that `this` was a map"); if index < map.full_len() { map.get_index(index) .map(|(k, v)| [v.clone(), k.clone(), this.clone()]) @@ -602,7 +602,7 @@ impl Map { } // 2. Let map be ! Construct(%Map%). - let mut map = OrderedMap::new(); + let mut map: OrderedMap = OrderedMap::new(); // 3. For each Record { [[Key]], [[Elements]] } g of groups, do for (key, elements) in groups { @@ -617,12 +617,10 @@ impl Map { let proto = context.intrinsics().constructors().map().prototype(); // 4. Return map. - Ok(JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - proto, - ObjectData::map(map), + Ok( + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, map) + .into(), ) - .into()) } } diff --git a/boa_engine/src/builtins/map/ordered_map.rs b/boa_engine/src/builtins/map/ordered_map.rs index 8a9b262ed3c..4cab5f1ee69 100644 --- a/boa_engine/src/builtins/map/ordered_map.rs +++ b/boa_engine/src/builtins/map/ordered_map.rs @@ -1,12 +1,11 @@ //! Implements a map type that preserves insertion order. -use crate::{object::JsObject, JsValue}; +use crate::{object::JsObject, JsData, JsValue}; use boa_gc::{custom_trace, Finalize, Trace}; use indexmap::{Equivalent, IndexMap}; use std::{ - collections::hash_map::RandomState, fmt::Debug, - hash::{BuildHasher, Hash, Hasher}, + hash::{Hash, Hasher}, }; #[derive(PartialEq, Eq, Clone, Debug)] @@ -35,15 +34,14 @@ impl Equivalent for JsValue { } /// A structure wrapping `indexmap::IndexMap`. -#[derive(Clone)] -pub struct OrderedMap { - map: IndexMap, S>, +#[derive(Clone, Finalize, JsData)] +pub struct OrderedMap { + map: IndexMap>, lock: u32, empty_count: usize, } -impl Finalize for OrderedMap {} -unsafe impl Trace for OrderedMap { +unsafe impl Trace for OrderedMap { custom_trace!(this, { for (k, v) in &this.map { if let MapKey::Key(key) = k { @@ -224,7 +222,8 @@ impl Finalize for MapLock { let Ok(mut map) = self.0.try_borrow_mut() else { return; }; - let map = map.as_map_mut().expect("MapLock does not point to a map"); - map.unlock(); + map.downcast_mut::>() + .expect("MapLock does not point to a map") + .unlock(); } } diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 239eb87b519..d20452a5f99 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -34,6 +34,10 @@ pub mod weak; pub mod weak_map; pub mod weak_set; +mod builder; + +use builder::BuiltInBuilder; + #[cfg(feature = "annex-b")] pub mod escape; @@ -47,9 +51,6 @@ pub(crate) mod options; #[cfg(feature = "temporal")] pub mod temporal; -use boa_gc::GcRefMut; - -use self::function::ConstructorKind; pub(crate) use self::{ array::Array, async_function::AsyncFunction, @@ -67,7 +68,7 @@ pub(crate) use self::{ map::Map, math::Math, number::{IsFinite, IsNaN, Number, ParseFloat, ParseInt}, - object::Object as BuiltInObjectObject, + object::OrdinaryObject, promise::Promise, proxy::Proxy, reflect::Reflect, @@ -104,14 +105,8 @@ use crate::{ weak_set::WeakSet, }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - js_string, - native_function::{NativeFunction, NativeFunctionObject, NativeFunctionPointer}, - object::{ - shape::{property_table::PropertyTableInner, slot::SlotAttributes}, - FunctionBinding, JsFunction, JsObject, JsPrototype, Object, ObjectData, CONSTRUCTOR, - PROTOTYPE, - }, - property::{Attribute, PropertyDescriptor, PropertyKey}, + object::JsObject, + property::{Attribute, PropertyDescriptor}, realm::Realm, string::utf16, Context, JsResult, JsString, JsValue, @@ -199,7 +194,7 @@ impl Realm { /// [spec]: https://tc39.es/ecma262/#sec-createintrinsics pub(crate) fn initialize(&self) { BuiltInFunctionObject::init(self); - BuiltInObjectObject::init(self); + OrdinaryObject::init(self); Iterator::init(self); AsyncIterator::init(self); AsyncFromSyncIterator::init(self); @@ -340,7 +335,7 @@ pub(crate) fn set_default_global_bindings(context: &mut Context) -> JsResult<()> )?; global_binding::(context)?; - global_binding::(context)?; + global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; global_binding::(context)?; @@ -409,618 +404,3 @@ pub(crate) fn set_default_global_bindings(context: &mut Context) -> JsResult<()> Ok(()) } - -// === Builder typestate === - -/// Marker for a constructor function. -struct Constructor { - prototype: JsObject, - inherits: JsPrototype, - attributes: Attribute, -} - -/// Marker for a constructor function without a custom prototype for its instances. -struct ConstructorNoProto; - -/// Marker for an ordinary function. -struct OrdinaryFunction; - -/// Indicates if the marker is a constructor. -trait IsConstructor { - const IS_CONSTRUCTOR: bool; -} - -impl IsConstructor for Constructor { - const IS_CONSTRUCTOR: bool = true; -} - -impl IsConstructor for ConstructorNoProto { - const IS_CONSTRUCTOR: bool = true; -} - -impl IsConstructor for OrdinaryFunction { - const IS_CONSTRUCTOR: bool = false; -} - -/// Marker for a callable object. -struct Callable { - function: NativeFunctionPointer, - name: JsString, - length: usize, - kind: Kind, - realm: Realm, -} - -/// Marker for an ordinary object. -struct OrdinaryObject; - -/// Applies the pending builder data to the object. -trait ApplyToObject { - fn apply_to(self, object: &JsObject); -} - -impl ApplyToObject for Constructor { - fn apply_to(self, object: &JsObject) { - object.insert( - PROTOTYPE, - PropertyDescriptor::builder() - .value(self.prototype.clone()) - .writable(false) - .enumerable(false) - .configurable(false), - ); - - { - let mut prototype = self.prototype.borrow_mut(); - prototype.set_prototype(self.inherits); - prototype.insert( - CONSTRUCTOR, - PropertyDescriptor::builder() - .value(object.clone()) - .writable(self.attributes.writable()) - .enumerable(self.attributes.enumerable()) - .configurable(self.attributes.configurable()), - ); - } - } -} - -impl ApplyToObject for ConstructorNoProto { - fn apply_to(self, _: &JsObject) {} -} - -impl ApplyToObject for OrdinaryFunction { - fn apply_to(self, _: &JsObject) {} -} - -impl ApplyToObject for Callable { - fn apply_to(self, object: &JsObject) { - { - let mut function = - GcRefMut::try_map(object.borrow_mut(), Object::as_native_function_mut) - .expect("Builtin must be a function object"); - function.f = NativeFunction::from_fn_ptr(self.function); - function.constructor = S::IS_CONSTRUCTOR.then_some(ConstructorKind::Base); - function.realm = Some(self.realm); - } - object.insert( - utf16!("length"), - PropertyDescriptor::builder() - .value(self.length) - .writable(false) - .enumerable(false) - .configurable(true), - ); - object.insert( - utf16!("name"), - PropertyDescriptor::builder() - .value(self.name) - .writable(false) - .enumerable(false) - .configurable(true), - ); - - self.kind.apply_to(object); - } -} - -impl ApplyToObject for OrdinaryObject { - fn apply_to(self, _: &JsObject) {} -} - -/// Builder for creating built-in objects, like `Array`. -/// -/// The marker `ObjectType` restricts the methods that can be called depending on the -/// type of object that is being constructed. -#[derive(Debug)] -#[must_use = "You need to call the `build` method in order for this to correctly assign the inner data"] -struct BuiltInBuilder<'ctx, Kind> { - realm: &'ctx Realm, - object: JsObject, - kind: Kind, - prototype: JsObject, -} - -impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { - fn with_intrinsic( - realm: &'ctx Realm, - ) -> BuiltInBuilder<'ctx, OrdinaryObject> { - BuiltInBuilder { - realm, - object: I::get(realm.intrinsics()), - kind: OrdinaryObject, - prototype: realm.intrinsics().constructors().object().prototype(), - } - } -} - -struct BuiltInConstructorWithPrototype<'ctx> { - realm: &'ctx Realm, - function: NativeFunctionPointer, - name: JsString, - length: usize, - - object_property_table: PropertyTableInner, - object_storage: Vec, - object: JsObject, - - prototype_property_table: PropertyTableInner, - prototype_storage: Vec, - prototype: JsObject, - __proto__: JsPrototype, - inherits: Option, - attributes: Attribute, -} - -#[allow(dead_code)] -impl BuiltInConstructorWithPrototype<'_> { - /// Specify how many arguments the constructor function takes. - /// - /// Default is `0`. - #[inline] - const fn length(mut self, length: usize) -> Self { - self.length = length; - self - } - - /// Specify the name of the constructor function. - /// - /// Default is `""` - fn name(mut self, name: JsString) -> Self { - self.name = name; - self - } - - /// Adds a new static method to the builtin object. - fn static_method( - mut self, - function: NativeFunctionPointer, - binding: B, - length: usize, - ) -> Self - where - B: Into, - { - let binding = binding.into(); - let function = BuiltInBuilder::callable(self.realm, function) - .name(binding.name) - .length(length) - .build(); - - debug_assert!(self - .object_property_table - .map - .get(&binding.binding) - .is_none()); - self.object_property_table.insert( - binding.binding, - SlotAttributes::WRITABLE | SlotAttributes::CONFIGURABLE, - ); - self.object_storage.push(function.into()); - self - } - - /// Adds a new static data property to the builtin object. - fn static_property(mut self, key: K, value: V, attribute: Attribute) -> Self - where - K: Into, - V: Into, - { - let key = key.into(); - - debug_assert!(self.object_property_table.map.get(&key).is_none()); - self.object_property_table - .insert(key, SlotAttributes::from_bits_truncate(attribute.bits())); - self.object_storage.push(value.into()); - self - } - - /// Adds a new static accessor property to the builtin object. - fn static_accessor( - mut self, - key: K, - get: Option, - set: Option, - attribute: Attribute, - ) -> Self - where - K: Into, - { - let mut attributes = SlotAttributes::from_bits_truncate(attribute.bits()); - debug_assert!(!attributes.contains(SlotAttributes::WRITABLE)); - attributes.set(SlotAttributes::GET, get.is_some()); - attributes.set(SlotAttributes::SET, set.is_some()); - - let key = key.into(); - - debug_assert!(self.object_property_table.map.get(&key).is_none()); - self.object_property_table.insert(key, attributes); - self.object_storage.extend([ - get.map(JsValue::new).unwrap_or_default(), - set.map(JsValue::new).unwrap_or_default(), - ]); - self - } - - /// Specify the `[[Prototype]]` internal field of the builtin object. - /// - /// Default is `Function.prototype` for constructors and `Object.prototype` for statics. - fn prototype(mut self, prototype: JsObject) -> Self { - self.__proto__ = Some(prototype); - self - } - - /// Adds a new method to the constructor's prototype. - fn method(mut self, function: NativeFunctionPointer, binding: B, length: usize) -> Self - where - B: Into, - { - let binding = binding.into(); - let function = BuiltInBuilder::callable(self.realm, function) - .name(binding.name) - .length(length) - .build(); - - debug_assert!(self - .prototype_property_table - .map - .get(&binding.binding) - .is_none()); - self.prototype_property_table.insert( - binding.binding, - SlotAttributes::WRITABLE | SlotAttributes::CONFIGURABLE, - ); - self.prototype_storage.push(function.into()); - self - } - - /// Adds a new data property to the constructor's prototype. - fn property(mut self, key: K, value: V, attribute: Attribute) -> Self - where - K: Into, - V: Into, - { - let key = key.into(); - - debug_assert!(self.prototype_property_table.map.get(&key).is_none()); - self.prototype_property_table - .insert(key, SlotAttributes::from_bits_truncate(attribute.bits())); - self.prototype_storage.push(value.into()); - self - } - - /// Adds new accessor property to the constructor's prototype. - fn accessor( - mut self, - key: K, - get: Option, - set: Option, - attribute: Attribute, - ) -> Self - where - K: Into, - { - let mut attributes = SlotAttributes::from_bits_truncate(attribute.bits()); - debug_assert!(!attributes.contains(SlotAttributes::WRITABLE)); - attributes.set(SlotAttributes::GET, get.is_some()); - attributes.set(SlotAttributes::SET, set.is_some()); - - let key = key.into(); - - debug_assert!(self.prototype_property_table.map.get(&key).is_none()); - self.prototype_property_table.insert(key, attributes); - self.prototype_storage.extend([ - get.map(JsValue::new).unwrap_or_default(), - set.map(JsValue::new).unwrap_or_default(), - ]); - self - } - - /// Specifies the parent prototype which objects created by this constructor inherit from. - /// - /// Default is `Object.prototype`. - #[allow(clippy::missing_const_for_fn)] - fn inherits(mut self, prototype: JsPrototype) -> Self { - self.inherits = prototype; - self - } - - /// Specifies the property attributes of the prototype's "constructor" property. - const fn constructor_attributes(mut self, attributes: Attribute) -> Self { - self.attributes = attributes; - self - } - - fn build(mut self) { - let length = self.length; - let name = self.name.clone(); - let prototype = self.prototype.clone(); - self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE); - self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); - self = self.static_property(PROTOTYPE, prototype, Attribute::empty()); - - let attributes = self.attributes; - let object = self.object.clone(); - self = self.property(CONSTRUCTOR, object, attributes); - - { - let mut prototype = self.prototype.borrow_mut(); - prototype - .properties_mut() - .shape - .as_unique() - .expect("The object should have a unique shape") - .override_internal(self.prototype_property_table, self.inherits); - - let prototype_old_storage = std::mem::replace( - &mut prototype.properties_mut().storage, - self.prototype_storage, - ); - - debug_assert_eq!(prototype_old_storage.len(), 0); - } - - let mut object = self.object.borrow_mut(); - let function = object - .as_native_function_mut() - .expect("Builtin must be a function object"); - function.f = NativeFunction::from_fn_ptr(self.function); - function.constructor = Some(ConstructorKind::Base); - function.realm = Some(self.realm.clone()); - object - .properties_mut() - .shape - .as_unique() - .expect("The object should have a unique shape") - .override_internal(self.object_property_table, self.__proto__); - - let object_old_storage = - std::mem::replace(&mut object.properties_mut().storage, self.object_storage); - - debug_assert_eq!(object_old_storage.len(), 0); - } - - fn build_without_prototype(mut self) { - let length = self.length; - let name = self.name.clone(); - self = self.static_property(js_string!("length"), length, Attribute::CONFIGURABLE); - self = self.static_property(js_string!("name"), name, Attribute::CONFIGURABLE); - - let mut object = self.object.borrow_mut(); - let function = object - .as_native_function_mut() - .expect("Builtin must be a function object"); - function.f = NativeFunction::from_fn_ptr(self.function); - function.constructor = Some(ConstructorKind::Base); - function.realm = Some(self.realm.clone()); - object - .properties_mut() - .shape - .as_unique() - .expect("The object should have a unique shape") - .override_internal(self.object_property_table, self.__proto__); - - let object_old_storage = - std::mem::replace(&mut object.properties_mut().storage, self.object_storage); - - debug_assert_eq!(object_old_storage.len(), 0); - } -} - -struct BuiltInCallable<'ctx> { - realm: &'ctx Realm, - function: NativeFunctionPointer, - name: JsString, - length: usize, -} - -impl BuiltInCallable<'_> { - /// Specify how many arguments the constructor function takes. - /// - /// Default is `0`. - #[inline] - const fn length(mut self, length: usize) -> Self { - self.length = length; - self - } - - /// Specify the name of the constructor function. - /// - /// Default is `""` - fn name(mut self, name: JsString) -> Self { - self.name = name; - self - } - - fn build(self) -> JsFunction { - let object = self.realm.intrinsics().templates().function().create( - ObjectData::native_function(NativeFunctionObject { - f: NativeFunction::from_fn_ptr(self.function), - constructor: None, - realm: Some(self.realm.clone()), - }), - vec![JsValue::new(self.length), JsValue::new(self.name)], - ); - - JsFunction::from_object_unchecked(object) - } -} - -impl<'ctx> BuiltInBuilder<'ctx, OrdinaryObject> { - fn callable(realm: &'ctx Realm, function: NativeFunctionPointer) -> BuiltInCallable<'ctx> { - BuiltInCallable { - realm, - function, - length: 0, - name: js_string!(""), - } - } - - fn callable_with_intrinsic( - realm: &'ctx Realm, - function: NativeFunctionPointer, - ) -> BuiltInBuilder<'ctx, Callable> { - BuiltInBuilder { - realm, - object: I::get(realm.intrinsics()), - kind: Callable { - function, - name: js_string!(""), - length: 0, - kind: OrdinaryFunction, - realm: realm.clone(), - }, - prototype: realm.intrinsics().constructors().function().prototype(), - } - } - - fn callable_with_object( - realm: &'ctx Realm, - object: JsObject, - function: NativeFunctionPointer, - ) -> BuiltInBuilder<'ctx, Callable> { - BuiltInBuilder { - realm, - object, - kind: Callable { - function, - name: js_string!(""), - length: 0, - kind: OrdinaryFunction, - realm: realm.clone(), - }, - prototype: realm.intrinsics().constructors().function().prototype(), - } - } -} - -impl<'ctx> BuiltInBuilder<'ctx, Callable> { - fn from_standard_constructor( - realm: &'ctx Realm, - ) -> BuiltInConstructorWithPrototype<'ctx> { - let constructor = SC::STANDARD_CONSTRUCTOR(realm.intrinsics().constructors()); - BuiltInConstructorWithPrototype { - realm, - function: SC::constructor, - name: js_string!(SC::NAME), - length: SC::LENGTH, - object_property_table: PropertyTableInner::default(), - object_storage: Vec::default(), - object: constructor.constructor(), - prototype_property_table: PropertyTableInner::default(), - prototype_storage: Vec::default(), - prototype: constructor.prototype(), - __proto__: Some(realm.intrinsics().constructors().function().prototype()), - inherits: Some(realm.intrinsics().constructors().object().prototype()), - attributes: Attribute::WRITABLE | Attribute::CONFIGURABLE | Attribute::NON_ENUMERABLE, - } - } -} - -impl BuiltInBuilder<'_, T> { - /// Adds a new static method to the builtin object. - fn static_method(self, function: NativeFunctionPointer, binding: B, length: usize) -> Self - where - B: Into, - { - let binding = binding.into(); - let function = BuiltInBuilder::callable(self.realm, function) - .name(binding.name) - .length(length) - .build(); - - self.object.insert( - binding.binding, - PropertyDescriptor::builder() - .value(function) - .writable(true) - .enumerable(false) - .configurable(true), - ); - self - } - - /// Adds a new static data property to the builtin object. - fn static_property(self, key: K, value: V, attribute: Attribute) -> Self - where - K: Into, - V: Into, - { - let property = PropertyDescriptor::builder() - .value(value) - .writable(attribute.writable()) - .enumerable(attribute.enumerable()) - .configurable(attribute.configurable()); - self.object.insert(key, property); - self - } - - /// Specify the `[[Prototype]]` internal field of the builtin object. - /// - /// Default is `Function.prototype` for constructors and `Object.prototype` for statics. - fn prototype(mut self, prototype: JsObject) -> Self { - self.prototype = prototype; - self - } -} - -impl BuiltInBuilder<'_, Callable> { - /// Specify how many arguments the constructor function takes. - /// - /// Default is `0`. - #[inline] - const fn length(mut self, length: usize) -> Self { - self.kind.length = length; - self - } - - /// Specify the name of the constructor function. - /// - /// Default is `""` - fn name(mut self, name: JsString) -> Self { - self.kind.name = name; - self - } -} - -impl BuiltInBuilder<'_, OrdinaryObject> { - /// Build the builtin object. - fn build(self) -> JsObject { - self.kind.apply_to(&self.object); - - self.object.set_prototype(Some(self.prototype)); - - self.object - } -} - -impl BuiltInBuilder<'_, Callable> { - /// Build the builtin callable. - fn build(self) -> JsFunction { - self.kind.apply_to(&self.object); - - self.object.set_prototype(Some(self.prototype)); - - JsFunction::from_object_unchecked(self.object) - } -} diff --git a/boa_engine/src/builtins/number/mod.rs b/boa_engine/src/builtins/number/mod.rs index 5999f2aa6a6..25e8c9560f8 100644 --- a/boa_engine/src/builtins/number/mod.rs +++ b/boa_engine/src/builtins/number/mod.rs @@ -18,7 +18,7 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::Attribute, realm::Realm, string::common::StaticJsStrings, @@ -126,11 +126,8 @@ impl BuiltInConstructor for Number { } let prototype = get_prototype_from_constructor(new_target, StandardConstructors::number, context)?; - let this = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::number(data), - ); + let this = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, data); Ok(this.into()) } } @@ -194,7 +191,11 @@ impl Number { fn this_number_value(value: &JsValue) -> JsResult { value .as_number() - .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_number())) + .or_else(|| { + value + .as_object() + .and_then(|obj| obj.downcast_ref::().as_deref().copied()) + }) .ok_or_else(|| { JsNativeError::typ() .with_message("'this' is not a number") diff --git a/boa_engine/src/builtins/object/for_in_iterator.rs b/boa_engine/src/builtins/object/for_in_iterator.rs index 8e36baf05b9..c6b290358fe 100644 --- a/boa_engine/src/builtins/object/for_in_iterator.rs +++ b/boa_engine/src/builtins/object/for_in_iterator.rs @@ -13,10 +13,10 @@ use crate::{ context::intrinsics::Intrinsics, error::JsNativeError, js_string, - object::{internal_methods::InternalMethodContext, JsObject, ObjectData}, + object::{internal_methods::InternalMethodContext, JsObject}, property::PropertyKey, realm::Realm, - Context, JsResult, JsString, JsValue, + Context, JsData, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -30,7 +30,7 @@ use std::collections::VecDeque; /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-for-in-iterator-objects -#[derive(Debug, Clone, Finalize, Trace)] +#[derive(Debug, Clone, Finalize, Trace, JsData)] pub struct ForInIterator { object: JsValue, visited_keys: FxHashSet, @@ -85,7 +85,7 @@ impl ForInIterator { .objects() .iterator_prototypes() .for_in(), - ObjectData::for_in_iterator(Self::new(object)), + Self::new(object), ) } @@ -98,10 +98,9 @@ impl ForInIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%foriniteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut iterator = this.as_object().map(JsObject::borrow_mut); - let iterator = iterator - .as_mut() - .and_then(|obj| obj.as_for_in_iterator_mut()) + let mut iterator = this + .as_object() + .and_then(|o| o.downcast_mut::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a ForInIterator"))?; let mut object = iterator.object.to_object(context)?; loop { diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index a4951a4a4ab..6372d90c1e2 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -15,7 +15,9 @@ use std::ops::Deref; -use super::{Array, BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; +use super::{ + error::ErrorKind, Array, BuiltInBuilder, BuiltInConstructor, Date, IntrinsicObject, RegExp, +}; use crate::{ builtins::{map, BuiltInObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, @@ -23,16 +25,17 @@ use crate::{ js_string, native_function::NativeFunction, object::{ - internal_methods::{get_prototype_from_constructor, InternalMethodContext}, - FunctionObjectBuilder, IntegrityLevel, JsObject, ObjectData, ObjectKind, +internal_methods::{get_prototype_from_constructor, InternalMethodContext}, FunctionObjectBuilder, IntegrityLevel, + JsObject, }, property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, realm::Realm, string::{common::StaticJsStrings, utf16}, symbol::JsSymbol, value::JsValue, - Context, JsArgs, JsResult, JsString, + Context, JsArgs, JsData, JsResult, JsString, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use tap::{Conv, Pipe}; @@ -40,11 +43,12 @@ pub(crate) mod for_in_iterator; #[cfg(test)] mod tests; -/// The global JavaScript object. -#[derive(Debug, Clone, Copy)] -pub struct Object; +/// An ordinary Javascript `Object`. +#[derive(Debug, Default, Clone, Copy, Trace, Finalize, JsData)] +#[boa_gc(empty_trace)] +pub struct OrdinaryObject; -impl IntrinsicObject for Object { +impl IntrinsicObject for OrdinaryObject { fn init(realm: &Realm) { let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); @@ -144,11 +148,11 @@ impl IntrinsicObject for Object { } } -impl BuiltInObject for Object { +impl BuiltInObject for OrdinaryObject { const NAME: JsString = StaticJsStrings::OBJECT; } -impl BuiltInConstructor for Object { +impl BuiltInConstructor for OrdinaryObject { const LENGTH: usize = 1; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = @@ -173,7 +177,7 @@ impl BuiltInConstructor for Object { let object = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::ordinary(), + OrdinaryObject, ); return Ok(object.into()); } @@ -190,7 +194,7 @@ impl BuiltInConstructor for Object { } } -impl Object { +impl OrdinaryObject { /// `get Object.prototype.__proto__` /// /// The `__proto__` getter function exposes the value of the @@ -457,7 +461,7 @@ impl Object { JsValue::Object(_) | JsValue::Null => JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype.as_object().cloned(), - ObjectData::ordinary(), + OrdinaryObject, ), _ => { return Err(JsNativeError::typ() @@ -838,25 +842,35 @@ impl Object { let builtin_tag = if o.is_array_abstract()? { utf16!("Array") } else { - // 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments". - // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function". - // 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error". - // 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean". - // 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number". - // 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String". - // 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date". - // 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp". - // 14. Else, let builtinTag be "Object". - match o.borrow().kind() { - ObjectKind::Arguments(_) => utf16!("Arguments"), - _ if o.is_callable() => utf16!("Function"), - ObjectKind::Error(_) => utf16!("Error"), - ObjectKind::Boolean(_) => utf16!("Boolean"), - ObjectKind::Number(_) => utf16!("Number"), - ObjectKind::String(_) => utf16!("String"), - ObjectKind::Date(_) => utf16!("Date"), - ObjectKind::RegExp(_) => utf16!("RegExp"), - _ => utf16!("Object"), + let o_borrow = o.borrow(); + + if o_borrow.is_arguments() { + // 6. Else if O has a [[ParameterMap]] internal slot, let builtinTag be "Arguments". + utf16!("Arguments") + } else if o.is_callable() { + // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function". + utf16!("Function") + } else if o.is::() { + // 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error". + utf16!("Error") + } else if o.is::() { + // 9. Else if O has a [[BooleanData]] internal slot, let builtinTag be "Boolean". + utf16!("Boolean") + } else if o.is::() { + // 10. Else if O has a [[NumberData]] internal slot, let builtinTag be "Number". + utf16!("Number") + } else if o.is::() { + // 11. Else if O has a [[StringData]] internal slot, let builtinTag be "String". + utf16!("String") + } else if o.is::() { + // 12. Else if O has a [[DateValue]] internal slot, let builtinTag be "Date". + utf16!("Date") + } else if o.is::() { + // 13. Else if O has a [[RegExpMatcher]] internal slot, let builtinTag be "RegExp". + utf16!("RegExp") + } else { + // 14. Else, let builtinTag be "Object". + utf16!("Object") } }; diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 4526726e959..1aa96d5d010 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -13,7 +13,7 @@ use crate::{ native_function::NativeFunction, object::{ internal_methods::get_prototype_from_constructor, FunctionObjectBuilder, JsFunction, - JsObject, ObjectData, CONSTRUCTOR, + JsObject, CONSTRUCTOR, }, property::Attribute, realm::Realm, @@ -23,6 +23,7 @@ use crate::{ Context, JsArgs, JsError, JsResult, JsString, }; use boa_gc::{custom_trace, Finalize, Gc, GcRefCell, Trace}; +use boa_macros::JsData; use boa_profiler::Profiler; use std::{cell::Cell, rc::Rc}; use tap::{Conv, Pipe}; @@ -72,7 +73,7 @@ impl PromiseState { } /// The internal representation of a `Promise` object. -#[derive(Debug, Trace, Finalize)] +#[derive(Debug, Trace, Finalize, JsData)] pub struct Promise { state: PromiseState, fulfill_reactions: Vec, @@ -137,8 +138,8 @@ unsafe impl Trace for ResolvingFunctions { /// /// [spec]: https://tc39.es/ecma262/#sec-ifabruptrejectpromise macro_rules! if_abrupt_reject_promise { - ($value:ident, $capability:expr, $context: expr) => { - let $value = match $value { + ($value:expr, $capability:expr, $context: expr) => { + match $value { // 1. If value is an abrupt completion, then Err(err) => { let err = err.to_opaque($context); @@ -152,7 +153,7 @@ macro_rules! if_abrupt_reject_promise { } // 2. Else if value is a Completion Record, set value to value.[[Value]]. Ok(value) => value, - }; + } }; } @@ -419,7 +420,7 @@ impl BuiltInConstructor for Promise { // 5. Set promise.[[PromiseFulfillReactions]] to a new empty List. // 6. Set promise.[[PromiseRejectReactions]] to a new empty List. // 7. Set promise.[[PromiseIsHandled]] to false. - ObjectData::promise(Self::new()), + Self::new(), ); // 8. Let resolvingFunctions be CreateResolvingFunctions(promise). @@ -478,6 +479,8 @@ impl Promise { context: &mut Context, ) -> JsResult { // 1. Let C be the this value. + + use super::OrdinaryObject; let c = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Promise.all() called on a non-object") })?; @@ -493,7 +496,7 @@ impl Promise { // 5. Perform ! CreateDataPropertyOrThrow(obj, "resolve", promiseCapability.[[Resolve]]). // 6. Perform ! CreateDataPropertyOrThrow(obj, "reject", promiseCapability.[[Reject]]). let obj = context.intrinsics().templates().with_resolvers().create( - ObjectData::ordinary(), + OrdinaryObject, vec![promise.into(), resolve.into(), reject.into()], ); @@ -526,14 +529,15 @@ impl Promise { let promise_resolve = Self::get_promise_resolve(c, context); // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). - if_abrupt_reject_promise!(promise_resolve, promise_capability, context); + let promise_resolve = + if_abrupt_reject_promise!(promise_resolve, promise_capability, context); // 5. Let iteratorRecord be Completion(GetIterator(iterable)). let iterator_record = args.get_or_undefined(0).get_iterator(context, None, None); // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). - if_abrupt_reject_promise!(iterator_record, promise_capability, context); - let mut iterator_record = iterator_record; + let mut iterator_record = + if_abrupt_reject_promise!(iterator_record, promise_capability, context); // 7. Let result be Completion(PerformPromiseAll(iteratorRecord, C, promiseCapability, promiseResolve)). let mut result = Self::perform_promise_all( @@ -553,7 +557,7 @@ impl Promise { } // b. IfAbruptRejectPromise(result, promiseCapability). - if_abrupt_reject_promise!(result, promise_capability, context); + let result = if_abrupt_reject_promise!(result, promise_capability, context); return Ok(result); } @@ -753,14 +757,15 @@ impl Promise { let promise_resolve = Self::get_promise_resolve(c, context); // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). - if_abrupt_reject_promise!(promise_resolve, promise_capability, context); + let promise_resolve = + if_abrupt_reject_promise!(promise_resolve, promise_capability, context); // 5. Let iteratorRecord be Completion(GetIterator(iterable)). let iterator_record = args.get_or_undefined(0).get_iterator(context, None, None); // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). - if_abrupt_reject_promise!(iterator_record, promise_capability, context); - let mut iterator_record = iterator_record; + let mut iterator_record = + if_abrupt_reject_promise!(iterator_record, promise_capability, context); // 7. Let result be Completion(PerformPromiseAllSettled(iteratorRecord, C, promiseCapability, promiseResolve)). let mut result = Self::perform_promise_all_settled( @@ -780,7 +785,7 @@ impl Promise { } // b. IfAbruptRejectPromise(result, promiseCapability). - if_abrupt_reject_promise!(result, promise_capability, context); + let result = if_abrupt_reject_promise!(result, promise_capability, context); return Ok(result); } @@ -1088,14 +1093,15 @@ impl Promise { let promise_resolve = Self::get_promise_resolve(c, context); // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). - if_abrupt_reject_promise!(promise_resolve, promise_capability, context); + let promise_resolve = + if_abrupt_reject_promise!(promise_resolve, promise_capability, context); // 5. Let iteratorRecord be Completion(GetIterator(iterable)). let iterator_record = args.get_or_undefined(0).get_iterator(context, None, None); // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). - if_abrupt_reject_promise!(iterator_record, promise_capability, context); - let mut iterator_record = iterator_record; + let mut iterator_record = + if_abrupt_reject_promise!(iterator_record, promise_capability, context); // 7. Let result be Completion(PerformPromiseAny(iteratorRecord, C, promiseCapability, promiseResolve)). let mut result = Self::perform_promise_any( @@ -1115,7 +1121,7 @@ impl Promise { } // b. IfAbruptRejectPromise(result, promiseCapability). - if_abrupt_reject_promise!(result, promise_capability, context); + let result = if_abrupt_reject_promise!(result, promise_capability, context); return Ok(result); } @@ -1331,14 +1337,15 @@ impl Promise { let promise_resolve = Self::get_promise_resolve(c, context); // 4. IfAbruptRejectPromise(promiseResolve, promiseCapability). - if_abrupt_reject_promise!(promise_resolve, promise_capability, context); + let promise_resolve = + if_abrupt_reject_promise!(promise_resolve, promise_capability, context); // 5. Let iteratorRecord be Completion(GetIterator(iterable)). let iterator_record = iterable.get_iterator(context, None, None); // 6. IfAbruptRejectPromise(iteratorRecord, promiseCapability). - if_abrupt_reject_promise!(iterator_record, promise_capability, context); - let mut iterator_record = iterator_record; + let mut iterator_record = + if_abrupt_reject_promise!(iterator_record, promise_capability, context); // 7. Let result be Completion(PerformPromiseRace(iteratorRecord, C, promiseCapability, promiseResolve)). let mut result = Self::perform_promise_race( @@ -1358,7 +1365,7 @@ impl Promise { } // b. IfAbruptRejectPromise(result, promiseCapability). - if_abrupt_reject_promise!(result, promise_capability, context); + let result = if_abrupt_reject_promise!(result, promise_capability, context); Ok(result) } else { @@ -1849,17 +1856,17 @@ impl Promise { }; let (state, handled) = { - let promise = promise.borrow(); - let promise = promise.as_promise().expect("IsPromise(promise) is false"); + let promise = promise + .downcast_ref::() + .expect("IsPromise(promise) is false"); (promise.state.clone(), promise.handled) }; match state { // 9. If promise.[[PromiseState]] is pending, then PromiseState::Pending => { - let mut promise = promise.borrow_mut(); - let promise = promise - .as_promise_mut() + let mut promise = promise + .downcast_mut::() .expect("IsPromise(promise) is false"); // a. Append fulfillReaction as the last element of the List that is promise.[[PromiseFulfillReactions]]. promise.fulfill_reactions.push(fulfill_reaction); @@ -1902,8 +1909,7 @@ impl Promise { // 12. Set promise.[[PromiseIsHandled]] to true. promise - .borrow_mut() - .as_promise_mut() + .downcast_mut::() .expect("IsPromise(promise) is false") .handled = true; } @@ -1994,9 +2000,8 @@ impl Promise { /// /// Panics if `Promise` is not pending. fn fulfill_promise(promise: &JsObject, value: JsValue, context: &mut Context) { - let mut promise = promise.borrow_mut(); - let promise = promise - .as_promise_mut() + let mut promise = promise + .downcast_mut::() .expect("IsPromise(promise) is false"); // 1. Assert: The value of promise.[[PromiseState]] is pending. @@ -2039,9 +2044,8 @@ impl Promise { /// Panics if `Promise` is not pending. fn reject_promise(promise: &JsObject, reason: JsValue, context: &mut Context) { let handled = { - let mut promise = promise.borrow_mut(); - let promise = promise - .as_promise_mut() + let mut promise = promise + .downcast_mut::() .expect("IsPromise(promise) is false"); // 1. Assert: The value of promise.[[PromiseState]] is pending. diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index e546ac0cc11..53e9888ee9d 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -11,20 +11,30 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy use crate::{ - builtins::BuiltInObject, + builtins::{array, BuiltInObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, native_function::NativeFunction, - object::{FunctionObjectBuilder, JsFunction, JsObject, ObjectData}, + object::{ + internal_methods::{ + is_compatible_property_descriptor, CallValue, InternalMethodContext, + InternalObjectMethods, ORDINARY_INTERNAL_METHODS, + }, + shape::slot::SlotAttributes, + JsData, JsFunction, JsObject, JsPrototype, + }, + property::{PropertyDescriptor, PropertyKey}, realm::Realm, string::{common::StaticJsStrings, utf16}, + value::Type, Context, JsArgs, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, GcRefCell, Trace}; use boa_profiler::Profiler; +use rustc_hash::FxHashSet; -use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; +use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject, OrdinaryObject}; /// Javascript `Proxy` object. #[derive(Debug, Clone, Trace, Finalize)] pub struct Proxy { @@ -32,6 +42,48 @@ pub struct Proxy { data: Option<(JsObject, JsObject)>, } +impl JsData for Proxy { + fn internal_methods(&self) -> &'static InternalObjectMethods { + static BASIC: InternalObjectMethods = InternalObjectMethods { + __get_prototype_of__: proxy_exotic_get_prototype_of, + __set_prototype_of__: proxy_exotic_set_prototype_of, + __is_extensible__: proxy_exotic_is_extensible, + __prevent_extensions__: proxy_exotic_prevent_extensions, + __get_own_property__: proxy_exotic_get_own_property, + __define_own_property__: proxy_exotic_define_own_property, + __has_property__: proxy_exotic_has_property, + __get__: proxy_exotic_get, + __set__: proxy_exotic_set, + __delete__: proxy_exotic_delete, + __own_property_keys__: proxy_exotic_own_property_keys, + ..ORDINARY_INTERNAL_METHODS + }; + + static CALLABLE: InternalObjectMethods = InternalObjectMethods { + __call__: proxy_exotic_call, + ..BASIC + }; + + static CONSTRUCTOR: InternalObjectMethods = InternalObjectMethods { + __call__: proxy_exotic_call, + __construct__: proxy_exotic_construct, + ..BASIC + }; + + let Some(data) = &self.data else { + return &BASIC; + }; + + if data.0.is_constructor() { + &CONSTRUCTOR + } else if data.0.is_callable() { + &CALLABLE + } else { + &BASIC + } + } +} + impl IntrinsicObject for Proxy { fn init(realm: &Realm) { let _timer = Profiler::global().start_event(std::any::type_name::(), "init"); @@ -129,11 +181,7 @@ impl Proxy { let p = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().constructors().object().prototype(), - ObjectData::proxy( - Self::new(target.clone(), handler.clone()), - target.is_callable(), - target.is_constructor(), - ), + Self::new(target.clone(), handler.clone()), ); // 8. Return P. @@ -143,31 +191,28 @@ impl Proxy { pub(crate) fn revoker(proxy: JsObject, context: &mut Context) -> JsFunction { // 3. Let revoker be ! CreateBuiltinFunction(revokerClosure, 0, "", « [[RevocableProxy]] »). // 4. Set revoker.[[RevocableProxy]] to p. - FunctionObjectBuilder::new( - context.realm(), - NativeFunction::from_copy_closure_with_captures( - |_, _, revocable_proxy, _| { - // a. Let F be the active function object. - // b. Let p be F.[[RevocableProxy]]. - // d. Set F.[[RevocableProxy]] to null. - if let Some(p) = std::mem::take(&mut *revocable_proxy.borrow_mut()) { - // e. Assert: p is a Proxy object. - // f. Set p.[[ProxyTarget]] to null. - // g. Set p.[[ProxyHandler]] to null. - p.borrow_mut() - .as_proxy_mut() - .expect("[[RevocableProxy]] must be a proxy object") - .data = None; - } - // c. If p is null, return undefined. - // h. Return undefined. - Ok(JsValue::undefined()) - }, - GcRefCell::new(Some(proxy)), - ), + NativeFunction::from_copy_closure_with_captures( + |_, _, revocable_proxy, _| { + // a. Let F be the active function object. + // b. Let p be F.[[RevocableProxy]]. + // d. Set F.[[RevocableProxy]] to null. + if let Some(p) = std::mem::take(&mut *revocable_proxy.borrow_mut()) { + // e. Assert: p is a Proxy object. + // f. Set p.[[ProxyTarget]] to null. + // g. Set p.[[ProxyHandler]] to null. + p.downcast_mut::() + .expect("[[RevocableProxy]] must be a proxy object") + .data = None; + } + + // c. If p is null, return undefined. + // h. Return undefined. + Ok(JsValue::undefined()) + }, + GcRefCell::new(Some(proxy)), ) - .build() + .to_js_function(context.realm()) } /// `28.2.2.1 Proxy.revocable ( target, handler )` @@ -200,3 +245,1002 @@ impl Proxy { Ok(result.into()) } } + +/// Definitions of the internal object methods for array exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects +pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods = + InternalObjectMethods { + __get_prototype_of__: proxy_exotic_get_prototype_of, + __set_prototype_of__: proxy_exotic_set_prototype_of, + __is_extensible__: proxy_exotic_is_extensible, + __prevent_extensions__: proxy_exotic_prevent_extensions, + __get_own_property__: proxy_exotic_get_own_property, + __define_own_property__: proxy_exotic_define_own_property, + __has_property__: proxy_exotic_has_property, + __get__: proxy_exotic_get, + __set__: proxy_exotic_set, + __delete__: proxy_exotic_delete, + __own_property_keys__: proxy_exotic_own_property_keys, + ..ORDINARY_INTERNAL_METHODS + }; + +pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods = + InternalObjectMethods { + __call__: proxy_exotic_call, + ..PROXY_EXOTIC_INTERNAL_METHODS_BASIC + }; + +pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods = + InternalObjectMethods { + __call__: proxy_exotic_call, + __construct__: proxy_exotic_construct, + ..PROXY_EXOTIC_INTERNAL_METHODS_BASIC + }; + +/// `10.5.1 [[GetPrototypeOf]] ( )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof +pub(crate) fn proxy_exotic_get_prototype_of( + obj: &JsObject, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "getPrototypeOf"). + let Some(trap) = handler.get_method(utf16!("getPrototypeOf"), context)? else { + // 6. If trap is undefined, then + // a. Return ? target.[[GetPrototypeOf]](). + return target.__get_prototype_of__(context); + }; + + // 7. Let handlerProto be ? Call(trap, handler, « target »). + let handler_proto = trap.call(&handler.into(), &[target.clone().into()], context)?; + + // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception. + let handler_proto = match &handler_proto { + JsValue::Object(obj) => Some(obj.clone()), + JsValue::Null => None, + _ => { + return Err(JsNativeError::typ() + .with_message("Proxy trap result is neither object nor null") + .into()) + } + }; + + // 9. Let extensibleTarget be ? IsExtensible(target). + // 10. If extensibleTarget is true, return handlerProto. + if target.is_extensible(context)? { + return Ok(handler_proto); + } + + // 11. Let targetProto be ? target.[[GetPrototypeOf]](). + let target_proto = target.__get_prototype_of__(context)?; + + // 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError exception. + if handler_proto != target_proto { + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected prototype") + .into()); + } + + // 13. Return handlerProto. + Ok(handler_proto) +} + +/// `10.5.2 [[SetPrototypeOf]] ( V )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v +pub(crate) fn proxy_exotic_set_prototype_of( + obj: &JsObject, + val: JsPrototype, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "setPrototypeOf"). + let Some(trap) = handler.get_method(utf16!("setPrototypeOf"), context)? else { + // 6. If trap is undefined, then + // a. Return ? target.[[SetPrototypeOf]](V). + return target.__set_prototype_of__(val, context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, V »)). + // 8. If booleanTrapResult is false, return false. + if !trap + .call( + &handler.into(), + &[ + target.clone().into(), + val.clone().map_or(JsValue::Null, Into::into), + ], + context, + )? + .to_boolean() + { + return Ok(false); + } + + // 9. Let extensibleTarget be ? IsExtensible(target). + // 10. If extensibleTarget is true, return true. + if target.is_extensible(context)? { + return Ok(true); + } + + // 11. Let targetProto be ? target.[[GetPrototypeOf]](). + let target_proto = target.__get_prototype_of__(context)?; + + // 12. If SameValue(V, targetProto) is false, throw a TypeError exception. + if val != target_proto { + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to set prototype") + .into()); + } + + // 13. Return true. + Ok(true) +} + +/// `10.5.3 [[IsExtensible]] ( )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-isextensible +pub(crate) fn proxy_exotic_is_extensible(obj: &JsObject, context: &mut Context) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "isExtensible"). + let Some(trap) = handler.get_method(utf16!("isExtensible"), context)? else { + // 6. If trap is undefined, then + // a. Return ? IsExtensible(target). + return target.is_extensible(context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)). + let boolean_trap_result = trap + .call(&handler.into(), &[target.clone().into()], context)? + .to_boolean(); + + // 8. Let targetResult be ? IsExtensible(target). + let target_result = target.is_extensible(context)?; + + // 9. If SameValue(booleanTrapResult, targetResult) is false, throw a TypeError exception. + if boolean_trap_result != target_result { + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected extensible value") + .into()); + } + + // 10. Return booleanTrapResult. + Ok(boolean_trap_result) +} + +/// `10.5.4 [[PreventExtensions]] ( )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions +pub(crate) fn proxy_exotic_prevent_extensions( + obj: &JsObject, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "preventExtensions"). + let Some(trap) = handler.get_method(utf16!("preventExtensions"), context)? else { + // 6. If trap is undefined, then + // a. Return ? target.[[PreventExtensions]](). + return target.__prevent_extensions__(context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)). + let boolean_trap_result = trap + .call(&handler.into(), &[target.clone().into()], context)? + .to_boolean(); + + // 8. If booleanTrapResult is true, then + if boolean_trap_result && target.is_extensible(context)? { + // a. Let extensibleTarget be ? IsExtensible(target). + // b. If extensibleTarget is true, throw a TypeError exception. + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to set extensible") + .into()); + } + + // 9. Return booleanTrapResult. + Ok(boolean_trap_result) +} + +/// `10.5.5 [[GetOwnProperty]] ( P )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p +pub(crate) fn proxy_exotic_get_own_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + context.slot().attributes |= SlotAttributes::NOT_CACHABLE; + + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor"). + let Some(trap) = handler.get_method(utf16!("getOwnPropertyDescriptor"), context)? else { + // 6. If trap is undefined, then + // a. Return ? target.[[GetOwnProperty]](P). + return target.__get_own_property__(key, context); + }; + + // 7. Let trapResultObj be ? Call(trap, handler, « target, P »). + let trap_result_obj = trap.call( + &handler.into(), + &[target.clone().into(), key.clone().into()], + context, + )?; + + // 8. If Type(trapResultObj) is neither Object nor Undefined, throw a TypeError exception. + if !trap_result_obj.is_object() && !trap_result_obj.is_undefined() { + return Err(JsNativeError::typ() + .with_message("Proxy trap result is neither object nor undefined") + .into()); + } + + // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). + let target_desc = target.__get_own_property__(key, context)?; + + // 10. If trapResultObj is undefined, then + if trap_result_obj.is_undefined() { + if let Some(desc) = target_desc { + // b. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + if !desc.expect_configurable() { + return Err(JsNativeError::typ() + .with_message( + "Proxy trap result is undefined adn target result is not configurable", + ) + .into()); + } + + // c. Let extensibleTarget be ? IsExtensible(target). + // d. If extensibleTarget is false, throw a TypeError exception. + if !target.is_extensible(context)? { + return Err(JsNativeError::typ() + .with_message("Proxy trap result is undefined and target is not extensible") + .into()); + } + // e. Return undefined. + return Ok(None); + } + + // a. If targetDesc is undefined, return undefined. + return Ok(None); + } + + // 11. Let extensibleTarget be ? IsExtensible(target). + let extensible_target = target.is_extensible(context)?; + + // 12. Let resultDesc be ? ToPropertyDescriptor(trapResultObj). + let result_desc = trap_result_obj.to_property_descriptor(context)?; + + // 13. Call CompletePropertyDescriptor(resultDesc). + let result_desc = result_desc.complete_property_descriptor(); + + // 14. Let valid be IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc). + // 15. If valid is false, throw a TypeError exception. + if !is_compatible_property_descriptor( + extensible_target, + result_desc.clone(), + target_desc.clone(), + ) { + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected property") + .into()); + } + + // 16. If resultDesc.[[Configurable]] is false, then + if !result_desc.expect_configurable() { + // a. If targetDesc is undefined or targetDesc.[[Configurable]] is true, then + match &target_desc { + Some(desc) if !desc.expect_configurable() => { + // b. If resultDesc has a [[Writable]] field and resultDesc.[[Writable]] is false, then + if result_desc.writable() == Some(false) { + // i. If targetDesc.[[Writable]] is true, throw a TypeError exception. + if desc.expect_writable() { + return + Err(JsNativeError::typ().with_message("Proxy trap result is writable and not configurable while target result is not configurable").into()) + ; + } + } + } + // i. Throw a TypeError exception. + _ => { + return Err(JsNativeError::typ() + .with_message( + "Proxy trap result is not configurable and target result is undefined", + ) + .into()) + } + } + } + + // 17. Return resultDesc. + Ok(Some(result_desc)) +} + +/// `10.5.6 [[DefineOwnProperty]] ( P, Desc )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc +pub(crate) fn proxy_exotic_define_own_property( + obj: &JsObject, + key: &PropertyKey, + desc: PropertyDescriptor, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + context.slot().attributes |= SlotAttributes::NOT_CACHABLE; + + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "defineProperty"). + let Some(trap) = handler.get_method(utf16!("defineProperty"), context)? else { + // 6. If trap is undefined, then + // a. Return ? target.[[DefineOwnProperty]](P, Desc). + return target.__define_own_property__(key, desc, context); + }; + + // 7. Let descObj be FromPropertyDescriptor(Desc). + let desc_obj = OrdinaryObject::from_property_descriptor(Some(desc.clone()), context); + + // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, descObj »)). + // 9. If booleanTrapResult is false, return false. + if !trap + .call( + &handler.into(), + &[target.clone().into(), key.clone().into(), desc_obj], + context, + )? + .to_boolean() + { + return Ok(false); + } + + // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). + let target_desc = target.__get_own_property__(key, context)?; + + // 11. Let extensibleTarget be ? IsExtensible(target). + let extensible_target = target.is_extensible(context)?; + + // 12. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, then + let setting_config_false = matches!(desc.configurable(), Some(false)); + + match target_desc { + // 14. If targetDesc is undefined, then + None => { + // a. If extensibleTarget is false, throw a TypeError exception. + if !extensible_target { + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to set property") + .into()); + } + + // b. If settingConfigFalse is true, throw a TypeError exception. + if setting_config_false { + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to set property") + .into()); + } + } + // 15. Else, + Some(target_desc) => { + // a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, targetDesc) is false, throw a TypeError exception. + if !is_compatible_property_descriptor( + extensible_target, + desc.clone(), + Some(target_desc.clone()), + ) { + return Err(JsNativeError::typ() + .with_message("Proxy trap set property to unexpected value") + .into()); + } + + // b. If settingConfigFalse is true and targetDesc.[[Configurable]] is true, throw a TypeError exception. + if setting_config_false && target_desc.expect_configurable() { + return Err(JsNativeError::typ() + .with_message("Proxy trap set property with unexpected configurable field") + .into()); + } + + // c. If IsDataDescriptor(targetDesc) is true, targetDesc.[[Configurable]] is false, and targetDesc.[[Writable]] is true, then + if target_desc.is_data_descriptor() + && !target_desc.expect_configurable() + && target_desc.expect_writable() + { + // i. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, throw a TypeError exception. + if let Some(writable) = desc.writable() { + if !writable { + return Err(JsNativeError::typ() + .with_message("Proxy trap set property with unexpected writable field") + .into()); + } + } + } + } + } + + // 16. Return true. + Ok(true) +} + +/// `10.5.7 [[HasProperty]] ( P )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-hasproperty-p +pub(crate) fn proxy_exotic_has_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + context.slot().attributes |= SlotAttributes::NOT_CACHABLE; + + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "has"). + let Some(trap) = handler.get_method(utf16!("has"), context)? else { + // 6. If trap is undefined, then + // a. Return ? target.[[HasProperty]](P). + return target.has_property(key.clone(), context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)). + let boolean_trap_result = trap + .call( + &handler.into(), + &[target.clone().into(), key.clone().into()], + context, + )? + .to_boolean(); + + // 8. If booleanTrapResult is false, then + if !boolean_trap_result { + // a. Let targetDesc be ? target.[[GetOwnProperty]](P). + let target_desc = target.__get_own_property__(key, context)?; + + // b. If targetDesc is not undefined, then + if let Some(target_desc) = target_desc { + // i. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + if !target_desc.expect_configurable() { + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected property") + .into()); + } + + // ii. Let extensibleTarget be ? IsExtensible(target). + // iii. If extensibleTarget is false, throw a TypeError exception. + if !target.is_extensible(context)? { + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected property") + .into()); + } + } + } + + // 9. Return booleanTrapResult. + Ok(boolean_trap_result) +} + +/// `10.5.8 [[Get]] ( P, Receiver )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver +pub(crate) fn proxy_exotic_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + // Proxy object can't be cached. + context.slot().attributes |= SlotAttributes::NOT_CACHABLE; + + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "get"). + let Some(trap) = handler.get_method(utf16!("get"), context)? else { + // 6. If trap is undefined, then + // a. Return ? target.[[Get]](P, Receiver). + return target.__get__(key, receiver, context); + }; + + // 7. Let trapResult be ? Call(trap, handler, « target, P, Receiver »). + let trap_result = trap.call( + &handler.into(), + &[target.clone().into(), key.clone().into(), receiver], + context, + )?; + + // 8. Let targetDesc be ? target.[[GetOwnProperty]](P). + let target_desc = target.__get_own_property__(key, context)?; + + // 9. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then + if let Some(target_desc) = target_desc { + if !target_desc.expect_configurable() { + // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then + if target_desc.is_data_descriptor() && !target_desc.expect_writable() { + // i. If SameValue(trapResult, targetDesc.[[Value]]) is false, throw a TypeError exception. + if !JsValue::same_value(&trap_result, target_desc.expect_value()) { + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected data descriptor") + .into()); + } + } + + // b. If IsAccessorDescriptor(targetDesc) is true and targetDesc.[[Get]] is undefined, then + if target_desc.is_accessor_descriptor() && target_desc.expect_get().is_undefined() { + // i. If trapResult is not undefined, throw a TypeError exception. + if !trap_result.is_undefined() { + return Err(JsNativeError::typ() + .with_message("Proxy trap returned unexpected accessor descriptor") + .into()); + } + } + } + } + + // 10. Return trapResult. + Ok(trap_result) +} + +/// `10.5.9 [[Set]] ( P, V, Receiver )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver +pub(crate) fn proxy_exotic_set( + obj: &JsObject, + key: PropertyKey, + value: JsValue, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + context.slot().attributes |= SlotAttributes::NOT_CACHABLE; + + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "set"). + let Some(trap) = handler.get_method(utf16!("set"), context)? else { + // 6. If trap is undefined, then + // a. Return ? target.[[Set]](P, V, Receiver). + return target.__set__(key, value, receiver, context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, V, Receiver »)). + // 8. If booleanTrapResult is false, return false. + if !trap + .call( + &handler.into(), + &[ + target.clone().into(), + key.clone().into(), + value.clone(), + receiver, + ], + context, + )? + .to_boolean() + { + return Ok(false); + } + + // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). + let target_desc = target.__get_own_property__(&key, context)?; + + // 10. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then + if let Some(target_desc) = target_desc { + if !target_desc.expect_configurable() { + // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then + if target_desc.is_data_descriptor() && !target_desc.expect_writable() { + // i. If SameValue(V, targetDesc.[[Value]]) is false, throw a TypeError exception. + if !JsValue::same_value(&value, target_desc.expect_value()) { + return Err(JsNativeError::typ() + .with_message("Proxy trap set unexpected data descriptor") + .into()); + } + } + + // b. If IsAccessorDescriptor(targetDesc) is true, then + if target_desc.is_accessor_descriptor() { + // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. + match target_desc.set() { + None | Some(&JsValue::Undefined) => { + return Err(JsNativeError::typ() + .with_message("Proxy trap set unexpected accessor descriptor") + .into()); + } + _ => {} + } + } + } + } + + // 11. Return true. + Ok(true) +} + +/// `10.5.10 [[Delete]] ( P )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-delete-p +pub(crate) fn proxy_exotic_delete( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "deleteProperty"). + let Some(trap) = handler.get_method(utf16!("deleteProperty"), context)? else { + // 6. If trap is undefined, then + // a. Return ? target.[[Delete]](P). + return target.__delete__(key, context); + }; + + // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)). + // 8. If booleanTrapResult is false, return false. + if !trap + .call( + &handler.into(), + &[target.clone().into(), key.clone().into()], + context, + )? + .to_boolean() + { + return Ok(false); + } + + // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). + match target.__get_own_property__(key, context)? { + // 10. If targetDesc is undefined, return true. + None => return Ok(true), + // 11. If targetDesc.[[Configurable]] is false, throw a TypeError exception. + Some(target_desc) => { + if !target_desc.expect_configurable() { + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to delete property") + .into()); + } + } + } + + // 12. Let extensibleTarget be ? IsExtensible(target). + // 13. If extensibleTarget is false, throw a TypeError exception. + if !target.is_extensible(context)? { + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to delete property") + .into()); + } + + // 14. Return true. + Ok(true) +} + +/// `10.5.11 [[OwnPropertyKeys]] ( )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys +pub(crate) fn proxy_exotic_own_property_keys( + obj: &JsObject, + context: &mut Context, +) -> JsResult> { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "ownKeys"). + let Some(trap) = handler.get_method(utf16!("ownKeys"), context)? else { + // 6. If trap is undefined, then + // a. Return ? target.[[OwnPropertyKeys]](). + return target.__own_property_keys__(context); + }; + + // 7. Let trapResultArray be ? Call(trap, handler, « target »). + let trap_result_array = trap.call(&handler.into(), &[target.clone().into()], context)?; + + // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, « String, Symbol »). + let trap_result_raw = + trap_result_array.create_list_from_array_like(&[Type::String, Type::Symbol], context)?; + + // 9. If trapResult contains any duplicate entries, throw a TypeError exception. + let mut unchecked_result_keys: FxHashSet = FxHashSet::default(); + let mut trap_result = Vec::new(); + for value in &trap_result_raw { + match value { + JsValue::String(s) => { + if !unchecked_result_keys.insert(s.clone().into()) { + return Err(JsNativeError::typ() + .with_message("Proxy trap result contains duplicate string property keys") + .into()); + } + trap_result.push(s.clone().into()); + } + JsValue::Symbol(s) => { + if !unchecked_result_keys.insert(s.clone().into()) { + return Err(JsNativeError::typ() + .with_message("Proxy trap result contains duplicate symbol property keys") + .into()); + } + trap_result.push(s.clone().into()); + } + _ => {} + } + } + + // 10. Let extensibleTarget be ? IsExtensible(target). + let extensible_target = target.is_extensible(context)?; + + // 11. Let targetKeys be ? target.[[OwnPropertyKeys]](). + // 12. Assert: targetKeys is a List of property keys. + // 13. Assert: targetKeys contains no duplicate entries. + let target_keys = target.__own_property_keys__(context)?; + + // 14. Let targetConfigurableKeys be a new empty List. + // 15. Let targetNonconfigurableKeys be a new empty List. + let mut target_configurable_keys = Vec::new(); + let mut target_nonconfigurable_keys = Vec::new(); + + // 16. For each element key of targetKeys, do + for key in target_keys { + // a. Let desc be ? target.[[GetOwnProperty]](key). + match target.__get_own_property__(&key, &mut context.into())? { + // b. If desc is not undefined and desc.[[Configurable]] is false, then + Some(desc) if !desc.expect_configurable() => { + // i. Append key as an element of targetNonconfigurableKeys. + target_nonconfigurable_keys.push(key); + } + // c. Else, + _ => { + // i. Append key as an element of targetConfigurableKeys. + target_configurable_keys.push(key); + } + } + } + + // 17. If extensibleTarget is true and targetNonconfigurableKeys is empty, then + if extensible_target && target_nonconfigurable_keys.is_empty() { + // a. Return trapResult. + return Ok(trap_result); + } + + // 18. Let uncheckedResultKeys be a List whose elements are the elements of trapResult. + // 19. For each element key of targetNonconfigurableKeys, do + for key in target_nonconfigurable_keys { + // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. + // b. Remove key from uncheckedResultKeys. + if !unchecked_result_keys.remove(&key) { + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to return all non-configurable property keys") + .into()); + } + } + + // 20. If extensibleTarget is true, return trapResult. + if extensible_target { + return Ok(trap_result); + } + + // 21. For each element key of targetConfigurableKeys, do + for key in target_configurable_keys { + // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. + // b. Remove key from uncheckedResultKeys. + if !unchecked_result_keys.remove(&key) { + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to return all configurable property keys") + .into()); + } + } + + // 22. If uncheckedResultKeys is not empty, throw a TypeError exception. + if !unchecked_result_keys.is_empty() { + return Err(JsNativeError::typ() + .with_message("Proxy trap failed to return all property keys") + .into()); + } + + // 23. Return trapResult. + Ok(trap_result) +} + +/// `10.5.12 [[Call]] ( thisArgument, argumentsList )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist +fn proxy_exotic_call( + obj: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Let trap be ? GetMethod(handler, "apply"). + let Some(trap) = handler.get_method(utf16!("apply"), context)? else { + // 6. If trap is undefined, then + // a. Return ? Call(target, thisArgument, argumentsList). + return Ok(target.__call__(argument_count)); + }; + + let args = context.vm.pop_n_values(argument_count); + + // 7. Let argArray be ! CreateArrayFromList(argumentsList). + let arg_array = array::Array::create_array_from_list(args, context); + + // 8. Return ? Call(trap, handler, « target, thisArgument, argArray »). + let _func = context.vm.pop(); + let this = context.vm.pop(); + + context.vm.push(handler); // This + context.vm.push(trap.clone()); // Function + + context.vm.push(target); + context.vm.push(this); + context.vm.push(arg_array); + Ok(trap.__call__(3)) +} + +/// `[[Construct]] ( argumentsList, newTarget )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget +fn proxy_exotic_construct( + obj: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + // 1. Let handler be O.[[ProxyHandler]]. + // 2. If handler is null, throw a TypeError exception. + // 3. Assert: Type(handler) is Object. + // 4. Let target be O.[[ProxyTarget]]. + let (target, handler) = obj + .downcast_ref::() + .expect("Proxy object internal internal method called on non-proxy object") + .try_data()?; + + // 5. Assert: IsConstructor(target) is true. + assert!(target.is_constructor()); + + // 6. Let trap be ? GetMethod(handler, "construct"). + let Some(trap) = handler.get_method(utf16!("construct"), context)? else { + // 7. If trap is undefined, then + // a. Return ? Construct(target, argumentsList, newTarget). + return Ok(target.__construct__(argument_count)); + }; + + let new_target = context.vm.pop(); + let args = context.vm.pop_n_values(argument_count); + let _func = context.vm.pop(); + + // 8. Let argArray be ! CreateArrayFromList(argumentsList). + let arg_array = array::Array::create_array_from_list(args, context); + + // 9. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »). + let new_obj = trap.call( + &handler.into(), + &[target.into(), arg_array.into(), new_target], + context, + )?; + + // 10. If Type(newObj) is not Object, throw a TypeError exception. + let new_obj = new_obj.as_object().cloned().ok_or_else(|| { + JsNativeError::typ().with_message("Proxy trap constructor returned non-object value") + })?; + + // 11. Return newObj. + context.vm.push(new_obj); + Ok(CallValue::Complete) +} diff --git a/boa_engine/src/builtins/reflect/mod.rs b/boa_engine/src/builtins/reflect/mod.rs index cc470ef1a00..eebf558b43f 100644 --- a/boa_engine/src/builtins/reflect/mod.rs +++ b/boa_engine/src/builtins/reflect/mod.rs @@ -241,7 +241,7 @@ impl Reflect { if args.get_or_undefined(0).is_object() { // This function is the same as Object.prototype.getOwnPropertyDescriptor, that why // it is invoked here. - builtins::object::Object::get_own_property_descriptor( + builtins::object::OrdinaryObject::get_own_property_descriptor( &JsValue::undefined(), args, context, diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index 8e1bdc2e1c5..c46b4392c8c 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -14,16 +14,15 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{ - internal_methods::get_prototype_from_constructor, JsObject, Object, ObjectData, CONSTRUCTOR, - }, + object::{internal_methods::get_prototype_from_constructor, JsObject, CONSTRUCTOR}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16, CodePoint}, symbol::JsSymbol, value::JsValue, - Context, JsArgs, JsResult, JsString, + Context, JsArgs, JsData, JsResult, JsString, }; +use boa_gc::{Finalize, Trace}; use boa_parser::lexer::regex::RegExpFlags; use boa_profiler::Profiler; use regress::{Flags, Range, Regex}; @@ -37,7 +36,9 @@ pub(crate) use regexp_string_iterator::RegExpStringIterator; mod tests; /// The internal representation of a `RegExp` object. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] +// Safety: `RegExp` does not contain any objects which needs to be traced, so this is safe. +#[boa_gc(unsafe_empty_trace)] pub struct RegExp { /// Regex matcher. matcher: Regex, @@ -210,46 +211,42 @@ impl BuiltInConstructor for RegExp { } // 4. If pattern is an Object and pattern has a [[RegExpMatcher]] internal slot, then - let (p, f) = if let Some(pattern) = pattern - .as_object() - .map(JsObject::borrow) - .as_deref() - .and_then(Object::as_regexp) - { - // a. Let P be pattern.[[OriginalSource]]. - let p = pattern.original_source.clone().into(); - - // b. If flags is undefined, let F be pattern.[[OriginalFlags]]. - let f = if flags.is_undefined() { - pattern.original_flags.clone().into() - // c. Else, let F be flags. - } else { - flags.clone() - }; - - (p, f) - } else if let Some(pattern) = pattern_is_regexp { - // a. Let P be ? Get(pattern, "source"). - let p = pattern.get(js_string!("source"), context)?; + let (p, f) = + if let Some(pattern) = pattern.as_object().and_then(|o| o.downcast_ref::()) { + // a. Let P be pattern.[[OriginalSource]]. + let p = pattern.original_source.clone().into(); + + // b. If flags is undefined, let F be pattern.[[OriginalFlags]]. + let f = if flags.is_undefined() { + pattern.original_flags.clone().into() + // c. Else, let F be flags. + } else { + flags.clone() + }; + + (p, f) + } else if let Some(pattern) = pattern_is_regexp { + // a. Let P be ? Get(pattern, "source"). + let p = pattern.get(js_string!("source"), context)?; + + // b. If flags is undefined, then + let f = if flags.is_undefined() { + // i. Let F be ? Get(pattern, "flags"). + pattern.get(js_string!("flags"), context)? + // c. Else, + } else { + // i. Let F be flags. + flags.clone() + }; - // b. If flags is undefined, then - let f = if flags.is_undefined() { - // i. Let F be ? Get(pattern, "flags"). - pattern.get(js_string!("flags"), context)? - // c. Else, + (p, f) + // 6. Else, } else { - // i. Let F be flags. - flags.clone() + // a. Let P be pattern. + // b. Let F be flags. + (pattern.clone(), flags.clone()) }; - (p, f) - // 6. Else, - } else { - // a. Let P be pattern. - // b. Let F be flags. - (pattern.clone(), flags.clone()) - }; - // 7. Let O be ? RegExpAlloc(newTarget). let proto = get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?; @@ -286,7 +283,7 @@ impl RegExp { } // 4. If argument has a [[RegExpMatcher]] internal slot, return true. - if argument.is_regexp() { + if argument.is::() { return Ok(Some(argument)); } @@ -368,7 +365,6 @@ impl RegExp { ) -> JsResult { // Has the steps of `RegExpInitialize`. let regexp = Self::compile_native_regexp(pattern, flags, context)?; - let data = ObjectData::regexp(regexp); // 22. Perform ? Set(obj, "lastIndex", +0𝔽, true). let obj = if let Some(prototype) = prototype { @@ -378,13 +374,13 @@ impl RegExp { .regexp_without_proto() .clone(); template.set_prototype(prototype); - template.create(data, vec![0.into()]) + template.create(regexp, vec![0.into()]) } else { context .intrinsics() .templates() .regexp() - .create(data, vec![0.into()]) + .create(regexp, vec![0.into()]) }; // 23. Return obj. @@ -421,7 +417,7 @@ impl RegExp { fn regexp_has_flag(this: &JsValue, flag: u8, context: &mut Context) -> JsResult { if let Some(object) = this.as_object() { - if let Some(regexp) = object.borrow().as_regexp() { + if let Some(regexp) = object.downcast_ref::() { return Ok(JsValue::new(match flag { b'd' => regexp.flags.contains(RegExpFlags::HAS_INDICES), b'g' => regexp.flags.contains(RegExpFlags::GLOBAL), @@ -681,9 +677,7 @@ impl RegExp { .into()); }; - let object = object.borrow(); - - match object.as_regexp() { + match object.downcast_ref::() { // 3. If R does not have an [[OriginalSource]] internal slot, then None => { // a. If SameValue(R, %RegExp.prototype%) is true, return "(?:)". @@ -801,7 +795,7 @@ impl RegExp { // 2. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). let obj = this .as_object() - .filter(|obj| obj.is_regexp()) + .filter(|obj| obj.is::()) .ok_or_else(|| { JsNativeError::typ().with_message("RegExp.prototype.exec called with invalid value") })?; @@ -848,7 +842,7 @@ impl RegExp { } // 5. Perform ? RequireInternalSlot(R, [[RegExpMatcher]]). - if !this.is_regexp() { + if !this.is::() { return Err(JsNativeError::typ() .with_message("RegExpExec called with invalid value") .into()); @@ -869,16 +863,13 @@ impl RegExp { input: &JsString, context: &mut Context, ) -> JsResult> { - let rx = { - let obj = this.borrow(); - if let Some(rx) = obj.as_regexp() { - rx.clone() - } else { - return Err(JsNativeError::typ() - .with_message("RegExpBuiltinExec called with invalid value") - .into()); - } - }; + let rx = this + .downcast_ref::() + .as_deref() + .cloned() + .ok_or_else(|| { + JsNativeError::typ().with_message("RegExpBuiltinExec called with invalid value") + })?; // 1. Let length be the length of S. let length = input.len() as u64; @@ -1276,23 +1267,16 @@ impl RegExp { /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString #[allow(clippy::wrong_self_convention)] pub(crate) fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - let (body, flags) = if let Some(object) = this.as_object() { - let object = object.borrow(); - let regex = object.as_regexp().ok_or_else(|| { + let (body, flags) = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .map(|rx| (rx.original_source.clone(), rx.original_flags.clone())) + .ok_or_else(|| { JsNativeError::typ().with_message(format!( "Method RegExp.prototype.toString called on incompatible receiver {}", this.display() )) })?; - (regex.original_source.clone(), regex.original_flags.clone()) - } else { - return Err(JsNativeError::typ() - .with_message(format!( - "Method RegExp.prototype.toString called on incompatible receiver {}", - this.display() - )) - .into()); - }; Ok(js_string!(utf16!("/"), &body, utf16!("/"), &flags).into()) } @@ -1842,7 +1826,7 @@ impl RegExp { let this = this .as_object() - .filter(|o| o.borrow().is_regexp()) + .filter(|o| o.is::()) .cloned() .ok_or_else(|| { JsNativeError::typ() @@ -1852,8 +1836,7 @@ impl RegExp { let flags = args.get_or_undefined(1); // 3. If pattern is an Object and pattern has a [[RegExpMatcher]] internal slot, then let (pattern, flags) = if let Some((p, f)) = pattern.as_object().and_then(|o| { - let o = o.borrow(); - o.as_regexp() + o.downcast_ref::() .map(|rx| (rx.original_source.clone(), rx.original_flags.clone())) }) { // a. If flags is not undefined, throw a TypeError exception. @@ -1879,9 +1862,8 @@ impl RegExp { // 5. Return ? RegExpInitialize(O, P, F). { - let mut obj = this.borrow_mut(); - - *obj.as_regexp_mut() + *this + .downcast_mut::() .expect("already checked that the object was a RegExp") = regexp; } diff --git a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs index 171a737a3f1..efac229553a 100644 --- a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs @@ -15,12 +15,12 @@ use crate::{ context::intrinsics::Intrinsics, error::JsNativeError, js_string, - object::{JsObject, ObjectData}, + object::JsObject, property::Attribute, realm::Realm, string::utf16, symbol::JsSymbol, - Context, JsResult, JsString, JsValue, + Context, JsData, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -32,7 +32,7 @@ use regexp::{advance_string_index, RegExp}; /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-regexp-string-iterator-objects -#[derive(Debug, Clone, Finalize, Trace)] +#[derive(Debug, Clone, Finalize, Trace, JsData)] pub struct RegExpStringIterator { matcher: JsObject, string: JsString, @@ -111,7 +111,7 @@ impl RegExpStringIterator { .objects() .iterator_prototypes() .regexp_string(), - ObjectData::reg_exp_string_iterator(Self::new(matcher, string, global, unicode)), + Self::new(matcher, string, global, unicode), ); regexp_string_iterator.into() @@ -124,10 +124,9 @@ impl RegExpStringIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%regexpstringiteratorprototype%.next pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut iterator = this.as_object().map(JsObject::borrow_mut); - let iterator = iterator - .as_mut() - .and_then(|obj| obj.as_regexp_string_iterator_mut()) + let mut iterator = this + .as_object() + .and_then(|o| o.downcast_mut::()) .ok_or_else(|| { JsNativeError::typ().with_message("`this` is not a RegExpStringIterator") })?; diff --git a/boa_engine/src/builtins/regexp/tests.rs b/boa_engine/src/builtins/regexp/tests.rs index 7e413cefe8b..b4c8642441e 100644 --- a/boa_engine/src/builtins/regexp/tests.rs +++ b/boa_engine/src/builtins/regexp/tests.rs @@ -1,5 +1,6 @@ use crate::{ - js_string, object::JsObject, run_test_actions, JsNativeErrorKind, JsValue, TestAction, + js_string, native_function::NativeFunctionObject, run_test_actions, JsNativeErrorKind, JsValue, + TestAction, }; use indoc::indoc; @@ -42,7 +43,8 @@ fn species() { // symbol-species TestAction::assert_eq("descriptor.set", JsValue::undefined()), TestAction::assert_with_op("accessor", |v, _| { - v.as_object().map_or(false, JsObject::is_native_function) + v.as_object() + .map_or(false, |o| o.is::()) }), TestAction::assert("!descriptor.enumerable"), TestAction::assert("descriptor.configurable"), diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index a1cc628b711..855278dee91 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -23,7 +23,7 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::{Attribute, PropertyNameKind}, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -128,7 +128,7 @@ impl BuiltInConstructor for Set { let set = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::set(OrderedSet::default()), + OrderedSet::default(), ); // 4. If iterable is either undefined or null, return set. @@ -178,7 +178,7 @@ impl Set { JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::set(OrderedSet::new()), + OrderedSet::new(), ) } @@ -229,12 +229,10 @@ impl Set { // 1. Let S be the this value. // 2. Perform ? RequireInternalSlot(S, [[SetData]]). - let Some(mut object) = this.as_object().map(JsObject::borrow_mut) else { - return Err(JsNativeError::typ() - .with_message("Method Set.prototype.add called on incompatible receiver") - .into()); - }; - let Some(s) = object.as_set_mut() else { + let Some(mut set) = this + .as_object() + .and_then(|o| o.downcast_mut::()) + else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.add called on incompatible receiver") .into()); @@ -251,7 +249,7 @@ impl Set { }; // 5. Append value to S.[[SetData]]. - s.add(value.clone()); + set.add(value.clone()); Ok(this.clone()) // 6. Return S. @@ -268,14 +266,14 @@ impl Set { /// [spec]: https://tc39.es/ecma262/#sec-set.prototype.clear /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/clear pub(crate) fn clear(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - let mut object = this + let Some(mut set) = this .as_object() - .map(JsObject::borrow_mut) - .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?; - - let set = object - .as_set_mut() - .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?; + .and_then(|o| o.downcast_mut::()) + else { + return Err(JsNativeError::typ() + .with_message("'this' is not a Set") + .into()); + }; set.clear(); @@ -298,12 +296,10 @@ impl Set { // 1. Let S be the this value. // 2. Perform ? RequireInternalSlot(S, [[SetData]]). - let Some(mut object) = this.as_object().map(JsObject::borrow_mut) else { - return Err(JsNativeError::typ() - .with_message("Method Set.prototype.delete called on incompatible receiver") - .into()); - }; - let Some(s) = object.as_set_mut() else { + let Some(mut set) = this + .as_object() + .and_then(|o| o.downcast_mut::()) + else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.delete called on incompatible receiver") .into()); @@ -320,7 +316,7 @@ impl Set { // i. Replace the element of S.[[SetData]] whose value is e with an element whose value is empty. // ii. Return true. // 4. Return false. - Ok(s.delete(value).into()) + Ok(set.delete(value).into()) } /// `Set.prototype.entries( )` @@ -338,10 +334,10 @@ impl Set { _: &[JsValue], context: &mut Context, ) -> JsResult { - let Some(lock) = this - .as_object() - .and_then(|o| o.borrow_mut().as_set_mut().map(|set| set.lock(o.clone()))) - else { + let Some(lock) = this.as_object().and_then(|o| { + o.downcast_mut::() + .map(|mut set| set.lock(o.clone())) + }) else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.entries called on incompatible receiver") .into()); @@ -372,10 +368,10 @@ impl Set { ) -> JsResult { // 1. Let S be the this value. // 2. Perform ? RequireInternalSlot(S, [[SetData]]). - let Some(lock) = this - .as_object() - .and_then(|o| o.borrow_mut().as_set_mut().map(|set| set.lock(o.clone()))) - else { + let Some(lock) = this.as_object().and_then(|o| { + o.downcast_mut::() + .map(|mut set| set.lock(o.clone())) + }) else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.forEach called on incompatible receiver") .into()); @@ -398,15 +394,18 @@ impl Set { // 7. Repeat, while index < numEntries, while index < Self::get_size_full(this)? { // a. Let e be entries[index]. - let Some(e) = this + let Some(set) = this .as_object() - .and_then(|o| o.borrow().as_set().map(|s| s.get_index(index).cloned())) + .and_then(|o| o.downcast_ref::()) else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.forEach called on incompatible receiver") .into()); }; + let e = set.get_index(index).cloned(); + drop(set); + // b. Set index to index + 1. index += 1; @@ -444,12 +443,10 @@ impl Set { // 1. Let S be the this value. // 2. Perform ? RequireInternalSlot(S, [[SetData]]). - let Some(mut object) = this.as_object().map(JsObject::borrow_mut) else { - return Err(JsNativeError::typ() - .with_message("Method Set.prototype.has called on incompatible receiver") - .into()); - }; - let Some(s) = object.as_set_mut() else { + let Some(set) = this + .as_object() + .and_then(|o| o.downcast_ref::()) + else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.has called on incompatible receiver") .into()); @@ -464,7 +461,7 @@ impl Set { // 3. For each element e of S.[[SetData]], do // a. If e is not empty and SameValueZero(e, value) is true, return true. // 4. Return false. - Ok(s.contains(value).into()) + Ok(set.contains(value).into()) } /// `Set.prototype.values( )` @@ -482,10 +479,10 @@ impl Set { _: &[JsValue], context: &mut Context, ) -> JsResult { - let Some(lock) = this - .as_object() - .and_then(|o| o.borrow_mut().as_set_mut().map(|set| set.lock(o.clone()))) - else { + let Some(lock) = this.as_object().and_then(|o| { + o.downcast_mut::() + .map(|mut set| set.lock(o.clone())) + }) else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.values called on incompatible receiver") .into()); @@ -506,7 +503,11 @@ impl Set { /// Helper function to get the size of the `Set` object. pub(crate) fn get_size(set: &JsValue) -> JsResult { set.as_object() - .and_then(|obj| obj.borrow().as_set().map(OrderedSet::len)) + .and_then(|obj| { + obj.borrow() + .downcast_ref::() + .map(OrderedSet::len) + }) .ok_or_else(|| { JsNativeError::typ() .with_message("'this' is not a Set") @@ -517,7 +518,11 @@ impl Set { /// Helper function to get the full size of the `Set` object. pub(crate) fn get_size_full(set: &JsValue) -> JsResult { set.as_object() - .and_then(|obj| obj.borrow().as_set().map(OrderedSet::full_len)) + .and_then(|obj| { + obj.borrow() + .downcast_ref::() + .map(OrderedSet::full_len) + }) .ok_or_else(|| { JsNativeError::typ() .with_message("'this' is not a Set") diff --git a/boa_engine/src/builtins/set/ordered_set.rs b/boa_engine/src/builtins/set/ordered_set.rs index 2ebb853bd81..92649ae7b52 100644 --- a/boa_engine/src/builtins/set/ordered_set.rs +++ b/boa_engine/src/builtins/set/ordered_set.rs @@ -1,19 +1,19 @@ //! Implements a set type that preserves insertion order. -use crate::{builtins::map::ordered_map::MapKey, object::JsObject, JsValue}; +use crate::{builtins::map::ordered_map::MapKey, object::JsObject, JsData, JsValue}; use boa_gc::{custom_trace, Finalize, Trace}; use indexmap::IndexSet; -use std::{collections::hash_map::RandomState, fmt::Debug, hash::BuildHasher}; +use std::fmt::Debug; /// A type wrapping `indexmap::IndexSet` -#[derive(Clone, Finalize)] -pub struct OrderedSet { - inner: IndexSet, +#[derive(Clone, Finalize, JsData)] +pub struct OrderedSet { + inner: IndexSet, lock: u32, empty_count: usize, } -unsafe impl Trace for OrderedSet { +unsafe impl Trace for OrderedSet { custom_trace!(this, { for v in &this.inner { if let MapKey::Key(v) = v { @@ -180,7 +180,9 @@ impl Finalize for SetLock { let Ok(mut set) = self.0.try_borrow_mut() else { return; }; - let set = set.as_set_mut().expect("SetLock does not point to a set"); + let set = set + .downcast_mut::() + .expect("SetLock does not point to a set"); set.unlock(); } } diff --git a/boa_engine/src/builtins/set/set_iterator.rs b/boa_engine/src/builtins/set/set_iterator.rs index 16fab5d6304..4567492ecdc 100644 --- a/boa_engine/src/builtins/set/set_iterator.rs +++ b/boa_engine/src/builtins/set/set_iterator.rs @@ -5,7 +5,7 @@ //! //! [spec]: https://tc39.es/ecma262/#sec-set-iterator-objects -use super::ordered_set::SetLock; +use super::ordered_set::{OrderedSet, SetLock}; use crate::{ builtins::{ iterable::create_iter_result_object, Array, BuiltInBuilder, IntrinsicObject, JsValue, @@ -13,11 +13,11 @@ use crate::{ context::intrinsics::Intrinsics, error::JsNativeError, js_string, - object::{JsObject, ObjectData}, + object::JsObject, property::{Attribute, PropertyNameKind}, realm::Realm, symbol::JsSymbol, - Context, JsResult, + Context, JsData, JsResult, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -28,7 +28,7 @@ use boa_profiler::Profiler; /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-set-iterator-objects -#[derive(Debug, Finalize, Trace)] +#[derive(Debug, Finalize, Trace, JsData)] pub struct SetIterator { iterated_set: JsValue, next_index: usize, @@ -91,7 +91,7 @@ impl SetIterator { let set_iterator = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().objects().iterator_prototypes().set(), - ObjectData::set_iterator(Self::new(set, kind, lock)), + Self::new(set, kind, lock), ); set_iterator.into() } @@ -105,12 +105,15 @@ impl SetIterator { /// /// [spec]: https://tc39.es/ecma262/#sec-%setiteratorprototype%.next pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut set_iterator = this.as_object().map(JsObject::borrow_mut); - - let set_iterator = set_iterator - .as_mut() - .and_then(|obj| obj.as_set_iterator_mut()) + let mut set_iterator = this + .as_object() + .and_then(|o| o.downcast_mut::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an SetIterator"))?; + + // The borrow checker cannot see that we're splitting the `GcRefMut` in two + // disjointed parts. However, if we manipulate a `&mut` instead, it can + // deduce that invariant. + let set_iterator = &mut *set_iterator; { let m = &set_iterator.iterated_set; let mut index = set_iterator.next_index; @@ -127,7 +130,7 @@ impl SetIterator { let entries = m.as_object().map(JsObject::borrow); let entries = entries .as_ref() - .and_then(|obj| obj.as_set()) + .and_then(|obj| obj.downcast_ref::()) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Set"))?; let num_entries = entries.full_len(); diff --git a/boa_engine/src/builtins/string/mod.rs b/boa_engine/src/builtins/string/mod.rs index 120a265924c..46777d6d0ed 100644 --- a/boa_engine/src/builtins/string/mod.rs +++ b/boa_engine/src/builtins/string/mod.rs @@ -14,7 +14,7 @@ use crate::{ context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, error::JsNativeError, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, JsObject}, property::{Attribute, PropertyDescriptor}, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -259,11 +259,8 @@ impl String { // 4. Set S.[[GetOwnProperty]] as specified in 10.4.3.1. // 5. Set S.[[DefineOwnProperty]] as specified in 10.4.3.2. // 6. Set S.[[OwnPropertyKeys]] as specified in 10.4.3.3. - let s = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::string(value), - ); + let s = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, value); // 8. Perform ! DefinePropertyOrThrow(S, "length", PropertyDescriptor { [[Value]]: 𝔽(length), // [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: false }). @@ -296,7 +293,10 @@ impl String { // a. Let s be value.[[StringData]]. // b. Assert: Type(s) is String. // c. Return s. - .or_else(|| this.as_object().and_then(|obj| obj.borrow().as_string())) + .or_else(|| { + this.as_object() + .and_then(|obj| obj.downcast_ref::().as_deref().cloned()) + }) // 3. Throw a TypeError exception. .ok_or_else(|| { JsNativeError::typ() @@ -1408,8 +1408,10 @@ impl String { let cmp = { #[cfg(feature = "intl")] { + use crate::builtins::intl::Collator; + // 4. Let collator be ? Construct(%Collator%, « locales, options »). - let collator = crate::builtins::intl::collator::Collator::constructor( + let collator = Collator::constructor( &context .intrinsics() .constructors() @@ -1425,7 +1427,7 @@ impl String { .map(JsObject::borrow) .expect("constructor must return a JsObject"); let collator = collator - .downcast_ref::() + .downcast_ref::() .expect("constructor must return a `Collator` object") .collator(); diff --git a/boa_engine/src/builtins/string/string_iterator.rs b/boa_engine/src/builtins/string/string_iterator.rs index 9ae8426ad83..2c8378b88d7 100644 --- a/boa_engine/src/builtins/string/string_iterator.rs +++ b/boa_engine/src/builtins/string/string_iterator.rs @@ -10,11 +10,11 @@ use crate::{ context::intrinsics::Intrinsics, error::JsNativeError, js_string, - object::{JsObject, ObjectData}, + object::JsObject, property::Attribute, realm::Realm, symbol::JsSymbol, - Context, JsResult, JsString, JsValue, + Context, JsData, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; @@ -25,7 +25,7 @@ use boa_profiler::Profiler; /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-string-iterator-objects -#[derive(Debug, Clone, Finalize, Trace)] +#[derive(Debug, Clone, Finalize, Trace, JsData)] pub struct StringIterator { string: JsString, next_index: usize, @@ -67,19 +67,18 @@ impl StringIterator { .objects() .iterator_prototypes() .string(), - ObjectData::string_iterator(Self { + Self { string, next_index: 0, - }), + }, ) } /// `StringIterator.prototype.next( )` pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let mut string_iterator = this.as_object().map(JsObject::borrow_mut); - let string_iterator = string_iterator - .as_mut() - .and_then(|obj| obj.as_string_iterator_mut()) + let mut string_iterator = this + .as_object() + .and_then(|o| o.downcast_mut::()) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an ArrayIterator"))?; if string_iterator.string.is_empty() { diff --git a/boa_engine/src/builtins/symbol/mod.rs b/boa_engine/src/builtins/symbol/mod.rs index fd51ced250d..680d79c647a 100644 --- a/boa_engine/src/builtins/symbol/mod.rs +++ b/boa_engine/src/builtins/symbol/mod.rs @@ -232,7 +232,11 @@ impl Symbol { fn this_symbol_value(value: &JsValue) -> JsResult { value .as_symbol() - .or_else(|| value.as_object().and_then(|obj| obj.borrow().as_symbol())) + .or_else(|| { + value + .as_object() + .and_then(|obj| obj.downcast_ref::().as_deref().cloned()) + }) .ok_or_else(|| { JsNativeError::typ() .with_message("'this' is not a Symbol") diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index 1578121ba85..5517e881ce2 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -4,7 +4,8 @@ use std::str::FromStr; use super::{ create_temporal_date, create_temporal_duration, create_temporal_month_day, - create_temporal_year_month, fields, options::TemporalUnitGroup, + create_temporal_year_month, fields, options::TemporalUnitGroup, PlainDate, PlainDateTime, + PlainMonthDay, PlainYearMonth, ZonedDateTime, }; use crate::{ builtins::{ @@ -14,12 +15,13 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, ObjectData}, + object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use boa_temporal::{ calendar::{ @@ -38,7 +40,9 @@ use object::CustomRuntimeCalendar; mod tests; /// The `Temporal.Calendar` object. -#[derive(Debug)] +#[derive(Debug, Trace, Finalize, JsData)] +// SAFETY: `Calendar` doesn't contain traceable types. +#[boa_gc(unsafe_empty_trace)] pub struct Calendar { slot: CalendarSlot, } @@ -160,14 +164,13 @@ impl Calendar { } fn get_id(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let o = o.borrow(); - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(JsObject::downcast_ref::) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("the this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -185,13 +188,13 @@ impl Calendar { ) -> JsResult { // 1. Let calendar be the this value. // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; // Retrieve the current CalendarProtocol. let protocol = match &calendar.slot { @@ -268,13 +271,13 @@ impl Calendar { args: &[JsValue], context: &mut Context, ) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -346,13 +349,13 @@ impl Calendar { ) -> JsResult { // 1. Let calendar be the this value. // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -419,13 +422,13 @@ impl Calendar { // 1. Let calendar be the this value. // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). // 3. Assert: calendar.[[Identifier]] is "iso8601". - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -461,13 +464,13 @@ impl Calendar { // 1. Let calendar be the this value. // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). // 3. Assert: calendar.[[Identifier]] is "iso8601". - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -500,13 +503,13 @@ impl Calendar { /// 15.8.2.6 `Temporal.Calendar.prototype.era ( temporalDateLike )` fn era(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -524,13 +527,13 @@ impl Calendar { /// 15.8.2.7 `Temporal.Calendar.prototype.eraYear ( temporalDateLike )` fn era_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -548,13 +551,13 @@ impl Calendar { /// 15.8.2.8 `Temporal.Calendar.prototype.year ( temporalDateLike )` fn year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -570,13 +573,13 @@ impl Calendar { /// 15.8.2.9 `Temporal.Calendar.prototype.month ( temporalDateLike )` fn month(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -597,13 +600,13 @@ impl Calendar { /// 15.8.2.10 `Temporal.Calendar.prototype.monthCode ( temporalDateLike )` fn month_code(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -619,13 +622,13 @@ impl Calendar { /// 15.8.2.11 `Temporal.Calendar.prototype.day ( temporalDateLike )` fn day(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -643,13 +646,13 @@ impl Calendar { fn day_of_week(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let calendar be the this value. // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -666,14 +669,13 @@ impl Calendar { /// 15.8.2.13 `Temporal.Calendar.prototype.dayOfYear ( temporalDateLike )` fn day_of_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let o = o.borrow(); - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -690,13 +692,13 @@ impl Calendar { /// 15.8.2.14 `Temporal.Calendar.prototype.weekOfYear ( temporalDateLike )` fn week_of_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -713,13 +715,13 @@ impl Calendar { /// 15.8.2.15 `Temporal.Calendar.prototype.yearOfWeek ( temporalDateLike )` fn year_of_week(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -736,13 +738,13 @@ impl Calendar { /// 15.8.2.16 `Temporal.Calendar.prototype.daysInWeek ( temporalDateLike )` fn days_in_week(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -759,13 +761,13 @@ impl Calendar { /// 15.8.2.17 `Temporal.Calendar.prototype.daysInMonth ( temporalDateLike )` fn days_in_month(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -781,13 +783,13 @@ impl Calendar { /// 15.8.2.18 `Temporal.Calendar.prototype.daysInYear ( temporalDateLike )` fn days_in_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -806,13 +808,13 @@ impl Calendar { args: &[JsValue], context: &mut Context, ) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -828,13 +830,13 @@ impl Calendar { /// 15.8.2.20 `Temporal.Calendar.prototype.inLeapYear ( temporalDateLike )` fn in_leap_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -852,13 +854,13 @@ impl Calendar { fn fields(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let calendar be the this value. // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -942,13 +944,13 @@ impl Calendar { fn merge_fields(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { // 1. Let calendar be the this value. // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Calendar must be an object.") - })?; - let calendar = o.as_calendar().ok_or_else(|| { - JsNativeError::typ() - .with_message("the this value of Calendar must be a Calendar object.") - })?; + let calendar = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ() + .with_message("this value of Calendar must be a Calendar object.") + })?; let protocol = match &calendar.slot { CalendarSlot::Identifier(s) => AvailableCalendars::from_str(s)?.to_protocol(), @@ -1058,13 +1060,45 @@ pub(crate) fn create_temporal_calendar( let proto = get_prototype_from_constructor(&new_target, StandardConstructors::calendar, context)?; - let obj = JsObject::from_proto_and_data(proto, ObjectData::calendar(Calendar::new(identifier))); + let obj = JsObject::from_proto_and_data(proto, Calendar::new(identifier)); // 4. Set object.[[Identifier]] to the ASCII-lowercase of identifier. // 5. Return object. Ok(obj.into()) } +fn extract_from_temporal_type( + object: &JsObject, + df: DF, + dtf: DTF, + ymf: YMF, + mdf: MDF, + zdtf: ZDTF, +) -> JsResult> +where + DF: FnOnce(&PlainDate) -> JsResult>, + DTF: FnOnce(&PlainDateTime) -> JsResult>, + YMF: FnOnce(&PlainYearMonth) -> JsResult>, + MDF: FnOnce(&PlainMonthDay) -> JsResult>, + ZDTF: FnOnce(&ZonedDateTime) -> JsResult>, +{ + let o = object.borrow(); + + if let Some(date) = o.downcast_ref::() { + return df(date); + } else if let Some(dt) = o.downcast_ref::() { + return dtf(dt); + } else if let Some(ym) = o.downcast_ref::() { + return ymf(ym); + } else if let Some(md) = o.downcast_ref::() { + return mdf(md); + } else if let Some(dt) = o.downcast_ref::() { + return zdtf(dt); + } + + Ok(None) +} + /// 12.2.21 `GetTemporalCalendarSlotValueWithISODefault ( item )` #[allow(unused)] pub(crate) fn get_temporal_calendar_slot_value_with_default( @@ -1073,42 +1107,19 @@ pub(crate) fn get_temporal_calendar_slot_value_with_default( ) -> JsResult { // 1. If item has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then // a. Return item.[[Calendar]]. - if item.is_plain_date() { - let obj = item.borrow(); - let date = obj.as_plain_date(); - if let Some(date) = date { - let calendar = date.inner.calendar().clone(); - drop(obj); - return Ok(calendar); - } - } else if item.is_plain_date_time() { - let obj = item.borrow(); - let date_time = obj.as_plain_date_time(); - if let Some(dt) = date_time { - let calendar = dt.inner.calendar().clone(); - drop(obj); - return Ok(calendar); - } - } else if item.is_plain_year_month() { - let obj = item.borrow(); - let year_month = obj.as_plain_year_month(); - if let Some(ym) = year_month { - let calendar = ym.inner.calendar().clone(); - drop(obj); - return Ok(calendar); - } - } else if item.is_plain_month_day() { - let obj = item.borrow(); - let month_day = obj.as_plain_month_day(); - if let Some(md) = month_day { - let calendar = md.inner.calendar().clone(); - drop(obj); - return Ok(calendar); - } - } else if item.is_zoned_date_time() { - return Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()); + if let Some(calendar) = extract_from_temporal_type( + item, + |d| Ok(Some(d.inner.calendar().clone())), + |dt| Ok(Some(dt.inner.calendar().clone())), + |ym| Ok(Some(ym.inner.calendar().clone())), + |md| Ok(Some(md.inner.calendar().clone())), + |zdt| { + Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()) + }, + )? { + return Ok(calendar); } // 2. Let calendarLike be ? Get(item, "calendar"). @@ -1132,22 +1143,31 @@ pub(crate) fn to_temporal_calendar_slot_value( } else if let Some(calendar_like) = calendar_like.as_object() { // a. If temporalCalendarLike has an [[InitializedTemporalDate]], [[InitializedTemporalDateTime]], [[InitializedTemporalMonthDay]], [[InitializedTemporalYearMonth]], or [[InitializedTemporalZonedDateTime]] internal slot, then // i. Return temporalCalendarLike.[[Calendar]]. - if calendar_like.is_plain_date() { - let obj = calendar_like.borrow(); - let date = obj.as_plain_date(); - if let Some(date) = date { - let calendar = date.inner.calendar().clone(); - return Ok(calendar); - } - } else if calendar_like.is_plain_date_time() - || calendar_like.is_plain_year_month() - || calendar_like.is_plain_month_day() - || calendar_like.is_zoned_date_time() - { - // TODO(nekevss): Separate out and reimplement the handling of different branches. - return Err(JsNativeError::range() - .with_message("Not yet implemented.") - .into()); + if let Some(calendar) = extract_from_temporal_type( + calendar_like, + |d| Ok(Some(d.inner.calendar().clone())), + |dt| { + Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()) + }, + |ym| { + Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()) + }, + |md| { + Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()) + }, + |zdt| { + Err(JsNativeError::range() + .with_message("Not yet implemented.") + .into()) + }, + )? { + return Ok(calendar); } // TODO: implement ObjectImplementsTemporalCalendarProtocol @@ -1188,28 +1208,25 @@ fn object_implements_calendar_protocol(calendar_like: &JsObject, context: &mut C /// Utility function for taking a `JsValue` and converting it to a temporal library `CalendarDateLike` enum. fn to_calendar_date_like(date_like: &JsValue, context: &mut Context) -> JsResult { - match date_like { - JsValue::Object(o) if o.is_plain_date_time() => { - let obj = o.borrow(); - let date_time = obj.as_plain_date_time().expect("obj must be a DateTime."); + let Some(obj) = date_like.as_object() else { + let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - Ok(CalendarDateLike::DateTime(date_time.inner.clone())) - } - JsValue::Object(o) if o.is_plain_date() => { - let obj = o.borrow(); - let date = obj.as_plain_date().expect("Must be a Date"); + return Ok(CalendarDateLike::Date(date.inner.clone())); + }; + + let Some(date_like) = extract_from_temporal_type( + obj, + |d| Ok(Some(CalendarDateLike::Date(d.inner.clone()))), + |dt| Ok(Some(CalendarDateLike::DateTime(dt.inner.clone()))), + |ym| Ok(Some(CalendarDateLike::YearMonth(ym.inner.clone()))), + |_| Ok(None), + |_| Ok(None), + )? + else { + let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - Ok(CalendarDateLike::Date(date.inner.clone())) - } - JsValue::Object(o) if o.is_plain_year_month() => { - let obj = o.borrow(); - let ym = obj.as_plain_year_month().expect("must be a YearMonth."); + return Ok(CalendarDateLike::Date(date.inner.clone())); + }; - Ok(CalendarDateLike::YearMonth(ym.inner.clone())) - } - _ => { - let date = temporal::plain_date::to_temporal_date(date_like, None, context)?; - Ok(CalendarDateLike::Date(date.inner.clone())) - } - } + Ok(date_like) } diff --git a/boa_engine/src/builtins/temporal/calendar/object.rs b/boa_engine/src/builtins/temporal/calendar/object.rs index 7c54452a898..2805ce37cc7 100644 --- a/boa_engine/src/builtins/temporal/calendar/object.rs +++ b/boa_engine/src/builtins/temporal/calendar/object.rs @@ -20,6 +20,9 @@ use boa_temporal::{ TemporalResult, TinyAsciiStr, }; use num_traits::ToPrimitive; +use plain_date::PlainDate; +use plain_month_day::PlainMonthDay; +use plain_year_month::PlainYearMonth; /// A user-defined, custom calendar that is only known at runtime /// and executed at runtime. @@ -85,7 +88,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { .with_message("datefromFields must return a valid PlainDate object.") })?; - let pd = obj.as_plain_date().ok_or_else(|| { + let pd = obj.downcast_ref::().ok_or_else(|| { TemporalError::r#type().with_message("Object returned was not a PlainDate") })?; @@ -139,7 +142,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { .with_message("yearMonthFromFields must return a valid PlainYearMonth object.") })?; - let ym = obj.as_plain_year_month().ok_or_else(|| { + let ym = obj.downcast_ref::().ok_or_else(|| { TemporalError::r#type().with_message("Object returned was not a PlainDate") })?; @@ -193,7 +196,7 @@ impl CalendarProtocol for CustomRuntimeCalendar { .with_message("yearMonthFromFields must return a valid PlainYearMonth object.") })?; - let md = obj.as_plain_month_day().ok_or_else(|| { + let md = obj.downcast_ref::().ok_or_else(|| { TemporalError::r#type().with_message("Object returned was not a PlainDate") })?; diff --git a/boa_engine/src/builtins/temporal/duration/mod.rs b/boa_engine/src/builtins/temporal/duration/mod.rs index f0d40695159..7022a249bd1 100644 --- a/boa_engine/src/builtins/temporal/duration/mod.rs +++ b/boa_engine/src/builtins/temporal/duration/mod.rs @@ -8,12 +8,13 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, ObjectData}, + object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use boa_temporal::{duration::Duration as InnerDuration, options::TemporalUnit}; @@ -31,7 +32,8 @@ mod tests; /// Per [spec], `Duration` records are float64-representable integers /// /// [spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Trace, Finalize, JsData)] +#[boa_gc(empty_trace)] pub struct Duration { pub(crate) inner: InnerDuration, } @@ -265,12 +267,12 @@ impl BuiltInConstructor for Duration { impl Duration { // Internal utility function for getting `Duration` field values. fn get_internal_field(this: &JsValue, field: &DateTimeValues) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; + let duration = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Duration object.") + })?; let inner = &duration.inner; @@ -345,12 +347,12 @@ impl Duration { fn get_sign(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; + let duration = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Duration object.") + })?; // 3. Return 𝔽(! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], // duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], @@ -362,12 +364,12 @@ impl Duration { fn get_blank(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; + let duration = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Duration object.") + })?; // 3. Let sign be ! DurationSign(duration.[[Years]], duration.[[Months]], duration.[[Weeks]], // duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], @@ -394,12 +396,12 @@ impl Duration { ) -> JsResult { // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; + let duration = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Duration object.") + })?; // 3. Let temporalDurationLike be ? ToTemporalPartialDurationRecord(temporalDurationLike). let temporal_duration_like = @@ -540,12 +542,12 @@ impl Duration { // 3. Return ! CreateTemporalDuration(abs(duration.[[Years]]), abs(duration.[[Months]]), // abs(duration.[[Weeks]]), abs(duration.[[Days]]), abs(duration.[[Hours]]), abs(duration.[[Minutes]]), // abs(duration.[[Seconds]]), abs(duration.[[Milliseconds]]), abs(duration.[[Microseconds]]), abs(duration.[[Nanoseconds]])). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; + let duration = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Duration object.") + })?; let abs = duration.inner.abs(); @@ -575,12 +577,12 @@ impl Duration { ) -> JsResult { // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; + let duration = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Duration object.") + })?; let round_to = args.get_or_undefined(0); let round_to = match round_to { @@ -796,12 +798,12 @@ impl Duration { ) -> JsResult { // 1. Let duration be the this value. // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Duration must be an object.") - })?; - let _duration = o.as_duration().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be a Duration object.") - })?; + let _duration = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Duration object.") + })?; let total_of = args.get_or_undefined(0); @@ -876,17 +878,9 @@ impl Duration { /// 7.5.8 `ToTemporalDuration ( item )` pub(crate) fn to_temporal_duration(item: &JsValue) -> JsResult { // 1a. If Type(item) is Object - if item.is_object() { - // 1b. and item has an [[InitializedTemporalDuration]] internal slot, then - let o = item - .as_object() - .expect("Value must be an object in this instance."); - if o.is_duration() { - // a. Return item. - let obj = o.borrow(); - let duration = obj.as_duration().expect("must be a duration."); - return Ok(duration.inner); - } + // 1b. and item has an [[InitializedTemporalDuration]] internal slot, then + if let Some(duration) = item.as_object().and_then(|o| o.downcast_ref::()) { + return Ok(duration.inner); } // 2. Let result be ? ToTemporalDurationRecord(item). @@ -940,7 +934,7 @@ pub(crate) fn create_temporal_duration( // 12. Set object.[[Microseconds]] to ℝ(𝔽(microseconds)). // 13. Set object.[[Nanoseconds]] to ℝ(𝔽(nanoseconds)). - let obj = JsObject::from_proto_and_data(prototype, ObjectData::duration(Duration::new(inner))); + let obj = JsObject::from_proto_and_data(prototype, Duration::new( inner)); // 14. Return object. Ok(obj) } diff --git a/boa_engine/src/builtins/temporal/instant/mod.rs b/boa_engine/src/builtins/temporal/instant/mod.rs index 498496dd263..8c6671ae84f 100644 --- a/boa_engine/src/builtins/temporal/instant/mod.rs +++ b/boa_engine/src/builtins/temporal/instant/mod.rs @@ -11,12 +11,14 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, ObjectData}, + object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, - Context, JsArgs, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, + JsValue, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use boa_temporal::{duration::Duration, options::TemporalUnit}; @@ -27,7 +29,7 @@ const NANOSECONDS_PER_MINUTE: i64 = 600_000_000_000; const NANOSECONDS_PER_HOUR: i64 = 36_000_000_000_000; /// The `Temporal.Instant` object. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct Instant { pub(crate) nanoseconds: JsBigInt, } @@ -150,12 +152,12 @@ impl Instant { ) -> JsResult { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Instant must be an object.") - })?; - let instant = o.as_instant().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; + let instant = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be an instant object.") + })?; // 3. Let ns be instant.[[Nanoseconds]]. let ns = &instant.nanoseconds; // 4. Let s be floor(ℝ(ns) / 10e9). @@ -172,12 +174,12 @@ impl Instant { ) -> JsResult { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Instant must be an object.") - })?; - let instant = o.as_instant().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; + let instant = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be an instant object.") + })?; // 3. Let ns be instant.[[Nanoseconds]]. let ns = &instant.nanoseconds; // 4. Let ms be floor(ℝ(ns) / 106). @@ -194,12 +196,12 @@ impl Instant { ) -> JsResult { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Instant must be an object.") - })?; - let instant = o.as_instant().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; + let instant = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be an instant object.") + })?; // 3. Let ns be instant.[[Nanoseconds]]. let ns = &instant.nanoseconds; // 4. Let µs be floor(ℝ(ns) / 103). @@ -219,12 +221,12 @@ impl Instant { ) -> JsResult { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Instant must be an object.") - })?; - let instant = o.as_instant().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; + let instant = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be an instant object.") + })?; // 3. Let ns be instant.[[Nanoseconds]]. let ns = &instant.nanoseconds; // 4. Return ns. @@ -239,16 +241,16 @@ impl Instant { ) -> JsResult { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Instant must be an object.") - })?; - let instant = o.as_instant().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; + let instant = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be an instant object.") + })?; // 3. Return ? AddDurationToOrSubtractDurationFromInstant(add, instant, temporalDurationLike). let temporal_duration_like = args.get_or_undefined(0); - add_or_subtract_duration_from_instant(true, instant, temporal_duration_like, context) + add_or_subtract_duration_from_instant(true, &instant, temporal_duration_like, context) } /// 8.3.8 `Temporal.Instant.prototype.subtract ( temporalDurationLike )` @@ -259,16 +261,16 @@ impl Instant { ) -> JsResult { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Instant must be an object.") - })?; - let instant = o.as_instant().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; + let instant = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be an instant object.") + })?; // 3. Return ? AddDurationToOrSubtractDurationFromInstant(subtract, instant, temporalDurationLike). let temporal_duration_like = args.get_or_undefined(0); - add_or_subtract_duration_from_instant(false, instant, temporal_duration_like, context) + add_or_subtract_duration_from_instant(false, &instant, temporal_duration_like, context) } /// 8.3.9 `Temporal.Instant.prototype.until ( other [ , options ] )` @@ -279,17 +281,17 @@ impl Instant { ) -> JsResult { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Instant must be an object.") - })?; - let instant = o.as_instant().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; + let instant = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be an instant object.") + })?; // 3. Return ? DifferenceTemporalInstant(until, instant, other, options). let other = args.get_or_undefined(0); let option = args.get_or_undefined(1); - diff_temporal_instant(true, instant, other, option, context) + diff_temporal_instant(true, &instant, other, option, context) } /// 8.3.10 `Temporal.Instant.prototype.since ( other [ , options ] )` @@ -300,17 +302,17 @@ impl Instant { ) -> JsResult { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Instant must be an object.") - })?; - let instant = o.as_instant().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; + let instant = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be an instant object.") + })?; // 3. Return ? DifferenceTemporalInstant(since, instant, other, options). let other = args.get_or_undefined(0); let option = args.get_or_undefined(1); - diff_temporal_instant(false, instant, other, option, context) + diff_temporal_instant(false, &instant, other, option, context) } /// 8.3.11 `Temporal.Instant.prototype.round ( roundTo )` @@ -321,12 +323,12 @@ impl Instant { ) -> JsResult { // 1. Let instant be the this value. // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Instant must be an object.") - })?; - let instant = o.as_instant().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; + let instant = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be an instant object.") + })?; let round_to = args.get_or_undefined(0); // 3. If roundTo is undefined, then @@ -421,12 +423,12 @@ impl Instant { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). // 4. If instant.[[Nanoseconds]] ≠ other.[[Nanoseconds]], return false. // 5. Return true. - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value of Instant must be an object.") - })?; - let instant = o.as_instant().ok_or_else(|| { - JsNativeError::typ().with_message("the this object must be an instant object.") - })?; + let instant = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("the this object must be an instant object.") + })?; // 3. Set other to ? ToTemporalInstant(other). let other = args.get_or_undefined(0); @@ -506,9 +508,9 @@ fn create_temporal_instant( // 4. Set object.[[Nanoseconds]] to epochNanoseconds. let obj = JsObject::from_proto_and_data( proto, - ObjectData::instant(Instant { + Instant { nanoseconds: epoch_nanos, - }), + }, ); // 5. Return object. diff --git a/boa_engine/src/builtins/temporal/plain_date/mod.rs b/boa_engine/src/builtins/temporal/plain_date/mod.rs index a9679c8c1ac..e3cfdce0519 100644 --- a/boa_engine/src/builtins/temporal/plain_date/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date/mod.rs @@ -8,19 +8,21 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, ObjectData}, + object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use boa_temporal::{date::Date as InnerDate, datetime::DateTime, options::ArithmeticOverflow}; -use super::calendar; +use super::{calendar, PlainDateTime, ZonedDateTime}; /// The `Temporal.PlainDate` object. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] +#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDate` could contain `Trace` types. pub struct PlainDate { pub(crate) inner: InnerDate, } @@ -434,8 +436,7 @@ pub(crate) fn create_temporal_date( // 6. Set object.[[ISOMonth]] to isoMonth. // 7. Set object.[[ISODay]] to isoDay. // 8. Set object.[[Calendar]] to calendar. - let obj = - JsObject::from_proto_and_data(prototype, ObjectData::plain_date(PlainDate::new(inner))); + let obj = JsObject::from_proto_and_data(prototype, PlainDate::new(inner)); // 9. Return object. Ok(obj) @@ -459,13 +460,10 @@ pub(crate) fn to_temporal_date( // 4. If Type(item) is Object, then if let Some(object) = item.as_object() { // a. If item has an [[InitializedTemporalDate]] internal slot, then - if object.is_plain_date() { - // i. Return item. - let obj = object.borrow(); - let date = obj.as_plain_date().expect("obj must be a PlainDate."); + if let Some(date) = object.downcast_ref::() { return Ok(PlainDate::new(date.inner.clone())); // b. If item has an [[InitializedTemporalZonedDateTime]] internal slot, then - } else if object.is_zoned_date_time() { + } else if let Some(data) = object.downcast_ref::() { return Err(JsNativeError::range() .with_message("ZonedDateTime not yet implemented.") .into()); @@ -475,18 +473,12 @@ pub(crate) fn to_temporal_date( // iv. Return ! CreateTemporalDate(plainDateTime.[[ISOYear]], plainDateTime.[[ISOMonth]], plainDateTime.[[ISODay]], plainDateTime.[[Calendar]]). // c. If item has an [[InitializedTemporalDateTime]] internal slot, then - } else if object.is_plain_date_time() { + } else if let Some(date_time) = object.downcast_ref::() { // i. Perform ? ToTemporalOverflow(options). let _o = get_option(&options_obj, utf16!("overflow"), context)? .unwrap_or(ArithmeticOverflow::Constrain); - let obj = object.borrow(); - let date_time = obj - .as_plain_date_time() - .expect("obj must be a PlainDateTime"); - let date = InnerDate::from_datetime(date_time.inner()); - drop(obj); // ii. Return ! CreateTemporalDate(item.[[ISOYear]], item.[[ISOMonth]], item.[[ISODay]], item.[[Calendar]]). return Ok(PlainDate::new(date)); diff --git a/boa_engine/src/builtins/temporal/plain_date_time/mod.rs b/boa_engine/src/builtins/temporal/plain_date_time/mod.rs index 5d658e24f6b..a5b9c6bce9a 100644 --- a/boa_engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_date_time/mod.rs @@ -7,14 +7,16 @@ use crate::{ property::Attribute, realm::Realm, string::common::StaticJsStrings, - Context, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use boa_temporal::datetime::DateTime as InnerDateTime; /// The `Temporal.PlainDateTime` object. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] +#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerDateTime` could contain `Trace` types. pub struct PlainDateTime { pub(crate) inner: InnerDateTime, } diff --git a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs index 8acd997b0ef..0c2f18cc848 100644 --- a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs @@ -3,18 +3,20 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - object::{internal_methods::get_prototype_from_constructor, ObjectData}, + object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::common::StaticJsStrings, - Context, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use boa_temporal::{datetime::DateTime, month_day::MonthDay as InnerMonthDay}; /// The `Temporal.PlainMonthDay` object. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] +#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerMonthDay` could contain `Trace` types. pub struct PlainMonthDay { pub(crate) inner: InnerMonthDay, } @@ -105,7 +107,7 @@ pub(crate) fn create_temporal_month_day( // 8. Set object.[[ISOYear]] to referenceISOYear. let obj = JsObject::from_proto_and_data( proto, - ObjectData::plain_month_day(PlainMonthDay::new(inner)), + PlainMonthDay::new(inner), ); // 9. Return object. diff --git a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs index c3c6479caa3..e628e099657 100644 --- a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs @@ -4,19 +4,21 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, ObjectData}, + object::internal_methods::get_prototype_from_constructor, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use super::calendar::to_temporal_calendar_slot_value; use boa_temporal::{options::ArithmeticOverflow, year_month::YearMonth as InnerYearMonth}; /// The `Temporal.PlainYearMonth` object. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] +#[boa_gc(unsafe_empty_trace)] // TODO: Remove this!!! `InnerYearMonth` could contain `Trace` types. pub struct PlainYearMonth { pub(crate) inner: InnerYearMonth, } @@ -300,7 +302,7 @@ pub(crate) fn create_temporal_year_month( // 8. Set object.[[ISODay]] to referenceISODay. let obj = - JsObject::from_proto_and_data(proto, ObjectData::plain_year_month(PlainYearMonth::new(ym))); + JsObject::from_proto_and_data(proto, PlainYearMonth::new(ym)); // 9. Return object. Ok(obj.into()) diff --git a/boa_engine/src/builtins/temporal/time_zone/mod.rs b/boa_engine/src/builtins/temporal/time_zone/mod.rs index efbc3073444..76c1044ff92 100644 --- a/boa_engine/src/builtins/temporal/time_zone/mod.rs +++ b/boa_engine/src/builtins/temporal/time_zone/mod.rs @@ -7,16 +7,19 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, ObjectData, CONSTRUCTOR}, + object::{internal_methods::get_prototype_from_constructor, CONSTRUCTOR}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsArgs, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; /// The `Temporal.TimeZone` object. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] +// SAFETY: `TimeZone` doesn't contain traceable data. +#[boa_gc(unsafe_empty_trace)] pub struct TimeZone { pub(crate) initialized_temporal_time_zone: bool, pub(crate) identifier: String, @@ -131,12 +134,12 @@ impl BuiltInConstructor for TimeZone { impl TimeZone { // NOTE: id, toJSON, toString currently share the exact same implementation -> Consolidate into one function and define multiple accesors? pub(crate) fn get_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { - let o = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") - })?; - let tz = o.as_time_zone().ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") - })?; + let tz = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") + })?; Ok(JsString::from(tz.identifier.clone()).into()) } @@ -149,11 +152,7 @@ impl TimeZone { // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). let _tz = this .as_object() - .ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") - })? - .borrow() - .as_time_zone() + .and_then(|o| o.downcast_ref::()) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") })?; @@ -178,11 +177,7 @@ impl TimeZone { // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). let _tz = this .as_object() - .ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") - })? - .borrow() - .as_time_zone() + .and_then(|o| o.downcast_ref::()) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") })?; @@ -250,13 +245,12 @@ impl TimeZone { pub(crate) fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { // 1. Let timeZone be the this value. // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). - let o = this.as_object().ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") - })?; - let o = o.borrow(); - let tz = o.as_time_zone().ok_or_else(|| { - JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") - })?; + let tz = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") + })?; // 3. Return timeZone.[[Identifier]]. Ok(JsString::from(tz.identifier.clone()).into()) } @@ -343,11 +337,11 @@ pub(super) fn create_temporal_time_zone( // 6. Return object. let object = JsObject::from_proto_and_data( prototype, - ObjectData::time_zone(TimeZone { + TimeZone { initialized_temporal_time_zone: false, identifier, offset_nanoseconds, - }), + }, ); Ok(object.into()) } diff --git a/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs b/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs index 8988c6e79e5..27d54787cea 100644 --- a/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs +++ b/boa_engine/src/builtins/temporal/zoned_date_time/mod.rs @@ -5,13 +5,14 @@ use crate::{ property::Attribute, realm::Realm, string::common::StaticJsStrings, - Context, JsBigInt, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, + Context, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use boa_temporal::duration::Duration as TemporalDuration; /// The `Temporal.ZonedDateTime` object. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Trace, Finalize, JsData)] pub struct ZonedDateTime { nanoseconds: JsBigInt, time_zone: JsObject, diff --git a/boa_engine/src/builtins/typed_array/builtin.rs b/boa_engine/src/builtins/typed_array/builtin.rs index 1999af2d93e..17085583af0 100644 --- a/boa_engine/src/builtins/typed_array/builtin.rs +++ b/boa_engine/src/builtins/typed_array/builtin.rs @@ -1,6 +1,5 @@ use std::{cmp, ptr, sync::atomic}; -use boa_gc::GcRef; use boa_macros::utf16; use num_traits::Zero; @@ -16,10 +15,7 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{ - internal_methods::{get_prototype_from_constructor, integer_indexed_element_set}, - Object, ObjectData, - }, + object::internal_methods::get_prototype_from_constructor, property::{Attribute, PropertyNameKind}, realm::Realm, string::common::StaticJsStrings, @@ -27,7 +23,10 @@ use crate::{ Context, JsArgs, JsNativeError, JsObject, JsResult, JsString, JsSymbol, JsValue, }; -use super::{ContentType, IntegerIndexed, TypedArray, TypedArrayKind}; +use super::{ + integer_indexed_object::integer_indexed_element_set, ContentType, TypedArray, TypedArrayKind, + TypedArrayMarker, +}; /// The JavaScript `%TypedArray%` object. /// @@ -369,8 +368,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -382,7 +381,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; - drop(obj_borrow); + drop(o); // 4. Let relativeIndex be ? ToIntegerOrInfinity(index). let relative_index = args.get_or_undefined(0).to_integer_or_infinity(context)?; @@ -422,8 +421,7 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + let typed_array = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; @@ -445,8 +443,7 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + let typed_array = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; @@ -474,8 +471,7 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + let typed_array = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; @@ -503,8 +499,7 @@ impl BuiltinTypedArray { })?; let len = { - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; @@ -571,8 +566,7 @@ impl BuiltinTypedArray { // 17. If count > 0, then if count > 0 { - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; @@ -644,8 +638,7 @@ impl BuiltinTypedArray { let o = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - if o.borrow() - .as_typed_array() + if o.downcast_ref::() .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? .is_detached() { @@ -678,8 +671,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -691,7 +684,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); - drop(obj_borrow); + drop(o); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -747,8 +740,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -804,7 +797,7 @@ impl BuiltinTypedArray { .into()); } - drop(obj_borrow); + drop(o); // 15. Repeat, while k < final, while k < r#final { @@ -837,8 +830,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -852,7 +845,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); - drop(obj_borrow); + drop(o); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = @@ -929,8 +922,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -942,7 +935,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); - drop(obj_borrow); + drop(o); let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); @@ -978,8 +971,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -991,7 +984,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); - drop(obj_borrow); + drop(o); let predicate = args.get_or_undefined(0); let this_arg = args.get_or_undefined(1); @@ -1027,8 +1020,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1074,8 +1067,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1121,8 +1114,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1134,7 +1127,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); - drop(obj_borrow); + drop(o); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = @@ -1182,8 +1175,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1195,7 +1188,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; - drop(obj_borrow); + drop(o); // 4. If len is 0, return false. if len == 0 { @@ -1263,8 +1256,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1276,7 +1269,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; - drop(obj_borrow); + drop(o); // 4. If len is 0, return -1𝔽. if len == 0 { @@ -1353,8 +1346,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1366,7 +1359,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); - drop(obj_borrow); + drop(o); // 4. If separator is undefined, let sep be the single-element String ",". let separator = args.get_or_undefined(0); @@ -1414,8 +1407,7 @@ impl BuiltinTypedArray { let o = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - if o.borrow() - .as_typed_array() + if o.downcast_ref::() .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? .is_detached() { @@ -1448,8 +1440,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1461,7 +1453,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; - drop(obj_borrow); + drop(o); // 4. If len is 0, return -1𝔽. if len == 0 { @@ -1527,8 +1519,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let typed_array = obj_borrow.as_typed_array().ok_or_else(|| { + + let typed_array = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; @@ -1559,8 +1551,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1574,7 +1566,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); - drop(obj_borrow); + drop(o); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -1629,8 +1621,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1642,7 +1634,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); - drop(obj_borrow); + drop(o); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = @@ -1716,8 +1708,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1729,7 +1721,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length() as i64; - drop(obj_borrow); + drop(o); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -1806,8 +1798,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -1819,7 +1811,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); - drop(obj_borrow); + drop(o); // 4. Let middle be floor(len / 2). let middle = len / 2; @@ -1866,7 +1858,7 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let array = GcRef::try_map(obj.borrow(), Object::as_typed_array).ok_or_else(|| { + let array = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if array.is_detached() { @@ -1921,7 +1913,7 @@ impl BuiltinTypedArray { let target = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("TypedArray.set must be called on typed array object") })?; - if !target.is_typed_array() { + if !target.is::() { return Err(JsNativeError::typ() .with_message("TypedArray.set must be called on typed array object") .into()); @@ -1949,7 +1941,7 @@ impl BuiltinTypedArray { let source = args.get_or_undefined(0); match source { // 6. If source is an Object that has a [[TypedArrayName]] internal slot, then - JsValue::Object(source) if source.is_typed_array() => { + JsValue::Object(source) if source.is::() => { // a. Perform ? SetTypedArrayFromTypedArray(target, targetOffset, source). Self::set_typed_array_from_typed_array(target, &target_offset, source, context)?; } @@ -1978,12 +1970,12 @@ impl BuiltinTypedArray { ) -> JsResult<()> { let target_borrow = target.borrow(); let target_array = target_borrow - .as_typed_array() + .downcast_ref::() .expect("Target must be a typed array"); let source_borrow = source.borrow(); let source_array = source_borrow - .as_typed_array() + .downcast_ref::() .expect("Source must be a typed array"); // TODO: Implement growable buffers. @@ -2213,7 +2205,7 @@ impl BuiltinTypedArray { let target_length = { let target_borrow = target.borrow(); let target_array = target_borrow - .as_typed_array() + .downcast_ref::() .expect("Target must be a typed array"); // 1. Let targetBuffer be target.[[ViewedArrayBuffer]]. @@ -2287,8 +2279,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -2334,13 +2326,13 @@ impl BuiltinTypedArray { // 13. Let A be ? TypedArraySpeciesCreate(O, « 𝔽(count) »). let a = Self::species_create(obj, o.kind(), &[count.into()], context)?; - let a_borrow = a.borrow(); - let a_array = a_borrow - .as_typed_array() - .expect("This must be a typed array"); // 14. If count > 0, then if count > 0 { + let a_array = a + .downcast_ref::() + .expect("This must be a typed array"); + // a. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is true, throw a TypeError exception. if o.is_detached() { return Err(JsNativeError::typ() @@ -2359,8 +2351,7 @@ impl BuiltinTypedArray { // f. If srcType is different from targetType, then #[allow(clippy::if_not_else)] if src_type != target_type { - drop(obj_borrow); - drop(a_borrow); + drop(a_array); // i. Let n be 0. let mut n = 0; @@ -2432,12 +2423,7 @@ impl BuiltinTypedArray { byte_count, ); } - - drop(target_buffer_obj_borrow); - drop(a_borrow); } - } else { - drop(a_borrow); } // 15. Return A. @@ -2460,8 +2446,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -2473,7 +2459,7 @@ impl BuiltinTypedArray { // 3. Let len be O.[[ArrayLength]]. let len = o.array_length(); - drop(obj_borrow); + drop(o); // 4. If IsCallable(callbackfn) is false, throw a TypeError exception. let callback_fn = match args.get_or_undefined(0).as_object() { @@ -2544,8 +2530,7 @@ impl BuiltinTypedArray { // 4. Let len be IntegerIndexedObjectLength(iieoRecord). let len = { - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ() .with_message("TypedArray.sort must be called on typed array object") })?; @@ -2609,7 +2594,7 @@ impl BuiltinTypedArray { })?; // 3. Let iieoRecord be ? ValidateTypedArray(O, seq-cst). - let array = GcRef::try_map(obj.borrow(), Object::as_typed_array).ok_or_else(|| { + let array = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if array.is_detached() { @@ -2668,8 +2653,8 @@ impl BuiltinTypedArray { let obj = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + + let o = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; @@ -2757,8 +2742,7 @@ impl BuiltinTypedArray { })?; let len = { - let obj_borrow = array.borrow(); - let o = obj_borrow.as_typed_array().ok_or_else(|| { + let o = array.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if o.is_detached() { @@ -2838,8 +2822,7 @@ impl BuiltinTypedArray { let o = this.as_object().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; - if o.borrow() - .as_typed_array() + if o.downcast_ref::() .ok_or_else(|| JsNativeError::typ().with_message("Value is not a typed array object"))? .is_detached() { @@ -2871,7 +2854,7 @@ impl BuiltinTypedArray { })?; // 2. Let iieoRecord be ? ValidateTypedArray(O, seq-cst). - let array = GcRef::try_map(obj.borrow(), Object::as_typed_array).ok_or_else(|| { + let array = obj.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("Value is not a typed array object") })?; if array.is_detached() { @@ -2966,8 +2949,7 @@ impl BuiltinTypedArray { Ok(this .as_object() .and_then(|obj| { - obj.borrow() - .as_typed_array() + obj.downcast_ref::() .map(|o| o.kind().js_name().into()) }) .unwrap_or(JsValue::Undefined)) @@ -2997,8 +2979,7 @@ impl BuiltinTypedArray { // 4. Assert: result has [[TypedArrayName]] and [[ContentType]] internal slots. // 5. If result.[[ContentType]] ≠ exemplar.[[ContentType]], throw a TypeError exception. if result - .borrow() - .as_typed_array() + .downcast_ref::() .expect("This can only be a typed array object") .kind() .content_type() @@ -3027,11 +3008,12 @@ impl BuiltinTypedArray { // 1. Let newTypedArray be ? Construct(constructor, argumentList). let new_typed_array = constructor.construct(args, Some(constructor), context)?; - let obj_borrow = new_typed_array.borrow(); // 2. Perform ? ValidateTypedArray(newTypedArray). - let o = obj_borrow.as_typed_array().ok_or_else(|| { - JsNativeError::typ().with_message("Value is not a typed array object") - })?; + let o = new_typed_array + .downcast_ref::() + .ok_or_else(|| { + JsNativeError::typ().with_message("Value is not a typed array object") + })?; if o.is_detached() { return Err(JsNativeError::typ() .with_message("Buffer of the typed array is detached") @@ -3055,10 +3037,10 @@ impl BuiltinTypedArray { } /// - fn allocate_buffer( + fn allocate_buffer( length: u64, context: &mut Context, - ) -> JsResult { + ) -> JsResult { // 1. Assert: O.[[ViewedArrayBuffer]] is undefined. // 2. Let constructorName be the String value of O.[[TypedArrayName]]. @@ -3086,11 +3068,11 @@ impl BuiltinTypedArray { // 9. Set O.[[ArrayLength]] to length. // 10. Return O. - Ok(IntegerIndexed::new(data, T::ERASED, 0, byte_length, length)) + Ok(TypedArray::new(data, T::ERASED, 0, byte_length, length)) } /// - pub(crate) fn initialize_from_list( + pub(crate) fn initialize_from_list( proto: JsObject, values: Vec, context: &mut Context, @@ -3099,11 +3081,7 @@ impl BuiltinTypedArray { let len = values.len() as u64; // 2. Perform ? AllocateTypedArrayBuffer(O, len). let buf = Self::allocate_buffer::(len, context)?; - let obj = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - proto, - ObjectData::integer_indexed(buf), - ); + let obj = JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, buf); // 3. Let k be 0. // 4. Repeat, while k < len, @@ -3130,7 +3108,7 @@ impl BuiltinTypedArray { /// For more information, check the [spec][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-allocatetypedarray - pub(super) fn allocate( + pub(super) fn allocate( new_target: &JsValue, length: u64, context: &mut Context, @@ -3152,11 +3130,8 @@ impl BuiltinTypedArray { let indexed = Self::allocate_buffer::(length, context)?; // 2. Let obj be ! IntegerIndexedObjectCreate(proto). - let obj = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - proto, - ObjectData::integer_indexed(indexed), - ); + let obj = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, indexed); // 9. Return obj. Ok(obj) @@ -3168,14 +3143,14 @@ impl BuiltinTypedArray { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromtypedarray - pub(super) fn initialize_from_typed_array( + pub(super) fn initialize_from_typed_array( proto: JsObject, src_array: &JsObject, context: &mut Context, ) -> JsResult { let src_array = src_array.borrow(); let src_array = src_array - .as_typed_array() + .downcast_ref::() .expect("this must be a typed array"); let src_data = src_array.viewed_array_buffer(); let src_data = src_data.borrow(); @@ -3233,70 +3208,70 @@ impl BuiltinTypedArray { byte_length, context, )?; - let mut data_obj_b = data_obj.borrow_mut(); - let data = data_obj_b - .as_array_buffer_mut() - .expect("Must be ArrayBuffer"); - let mut data = - SliceRefMut::Slice(data.data_mut().expect("a new buffer cannot be detached")); - - // b. If srcArray.[[ContentType]] is not O.[[ContentType]], throw a TypeError exception. - if src_type.content_type() != element_type.content_type() { - return Err(JsNativeError::typ() - .with_message("Cannot initialize typed array from different content type") - .into()); - } + { + let mut data = data_obj + .downcast_mut::() + .expect("Must be ArrayBuffer"); + let mut data = + SliceRefMut::Slice(data.data_mut().expect("a new buffer cannot be detached")); + + // b. If srcArray.[[ContentType]] is not O.[[ContentType]], throw a TypeError exception. + if src_type.content_type() != element_type.content_type() { + return Err(JsNativeError::typ() + .with_message("Cannot initialize typed array from different content type") + .into()); + } - let src_element_size = src_element_size as usize; - let target_element_size = target_element_size as usize; + let src_element_size = src_element_size as usize; + let target_element_size = target_element_size as usize; - // c. Let srcByteIndex be srcByteOffset. - let mut src_byte_index = src_byte_offset as usize; + // c. Let srcByteIndex be srcByteOffset. + let mut src_byte_index = src_byte_offset as usize; - // d. Let targetByteIndex be 0. - let mut target_byte_index = 0; + // d. Let targetByteIndex be 0. + let mut target_byte_index = 0; - // e. Let count be elementLength. - let mut count = element_length; + // e. Let count be elementLength. + let mut count = element_length; - // f. Repeat, while count > 0, - while count > 0 { - // i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, Unordered). - // SAFETY: All integer indexed objects are always in-bounds and properly - // aligned to their underlying buffer. - let value = unsafe { - src_data - .subslice(src_byte_index..) - .get_value(src_type, atomic::Ordering::Relaxed) - }; + // f. Repeat, while count > 0, + while count > 0 { + // i. Let value be GetValueFromBuffer(srcData, srcByteIndex, srcType, true, Unordered). + // SAFETY: All integer indexed objects are always in-bounds and properly + // aligned to their underlying buffer. + let value = unsafe { + src_data + .subslice(src_byte_index..) + .get_value(src_type, atomic::Ordering::Relaxed) + }; - let value = JsValue::from(value); + let value = JsValue::from(value); - // TODO: cast between types instead of converting to `JsValue`. - let value = element_type - .get_element(&value, context) - .expect("value must be bigint or float"); + // TODO: cast between types instead of converting to `JsValue`. + let value = element_type + .get_element(&value, context) + .expect("value must be bigint or float"); - // ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, Unordered). + // ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, Unordered). - // SAFETY: The newly created buffer has at least `element_size * element_length` - // bytes available, which makes `target_byte_index` always in-bounds. - unsafe { - data.subslice_mut(target_byte_index..) - .set_value(value, atomic::Ordering::Relaxed); - } + // SAFETY: The newly created buffer has at least `element_size * element_length` + // bytes available, which makes `target_byte_index` always in-bounds. + unsafe { + data.subslice_mut(target_byte_index..) + .set_value(value, atomic::Ordering::Relaxed); + } - // iii. Set srcByteIndex to srcByteIndex + srcElementSize. - src_byte_index += src_element_size; + // iii. Set srcByteIndex to srcByteIndex + srcElementSize. + src_byte_index += src_element_size; - // iv. Set targetByteIndex to targetByteIndex + elementSize. - target_byte_index += target_element_size; + // iv. Set targetByteIndex to targetByteIndex + elementSize. + target_byte_index += target_element_size; - // v. Set count to count - 1. - count -= 1; + // v. Set count to count - 1. + count -= 1; + } } - drop(data_obj_b); data_obj }; @@ -3307,13 +3282,7 @@ impl BuiltinTypedArray { let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), proto, - ObjectData::integer_indexed(IntegerIndexed::new( - new_buffer, - element_type, - 0, - byte_length, - element_length, - )), + TypedArray::new(new_buffer, element_type, 0, byte_length, element_length), ); // 16. Return unused. @@ -3326,7 +3295,7 @@ impl BuiltinTypedArray { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraybuffer - pub(super) fn initialize_from_array_buffer( + pub(super) fn initialize_from_array_buffer( proto: JsObject, buffer: JsObject, byte_offset: &JsValue, @@ -3411,13 +3380,13 @@ impl BuiltinTypedArray { let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), proto, - ObjectData::integer_indexed(IntegerIndexed::new( + TypedArray::new( buffer, T::ERASED, offset, new_byte_length, new_byte_length / element_size, - )), + ), ); // 13. Return unused. @@ -3430,7 +3399,7 @@ impl BuiltinTypedArray { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-initializetypedarrayfromarraylike - pub(super) fn initialize_from_array_like( + pub(super) fn initialize_from_array_like( proto: JsObject, array_like: &JsObject, context: &mut Context, @@ -3440,11 +3409,7 @@ impl BuiltinTypedArray { // 2. Perform ? AllocateTypedArrayBuffer(O, len). let buf = Self::allocate_buffer::(len, context)?; - let obj = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - proto, - ObjectData::integer_indexed(buf), - ); + let obj = JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, buf); // 3. Let k be 0. // 4. Repeat, while k < len, @@ -3565,8 +3530,7 @@ fn compare_typed_array_elements( /// /// [spec]: https://tc39.es/ecma262/#sec-isvalidintegerindex pub(crate) fn is_valid_integer_index(obj: &JsObject, index: f64) -> bool { - let obj = obj.borrow(); - let inner = obj.as_typed_array().expect( + let inner = obj.downcast_ref::().expect( "integer indexed exotic method should only be callable from integer indexed objects", ); diff --git a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs index 033af0fdc14..6939dfef3ea 100644 --- a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs +++ b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs @@ -1,19 +1,38 @@ //! This module implements the `Integer-Indexed` exotic object. -use crate::object::JsObject; +use std::sync::atomic; + +use crate::{ + builtins::Number, + object::{ + internal_methods::{ + ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property, + ordinary_has_property, ordinary_set, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, InternalMethodContext, + }, + JsData, JsObject, + }, + property::{PropertyDescriptor, PropertyKey}, + Context, JsResult, JsString, JsValue, +}; use boa_gc::{Finalize, Trace}; +use boa_macros::utf16; + +use super::{is_valid_integer_index, TypedArrayKind}; -use super::TypedArrayKind; +// An `IntegerIndexed` object is just an alias for a `TypedArray` object. +pub type IntegerIndexed = TypedArray; -/// An `Integer-Indexed` exotic object is an exotic object that performs -/// special handling of integer index property keys. +/// A `TypedArrayObject` is an exotic object that performs special handling of integer +/// index property keys. +/// +/// This is also called an `IntegerIndexed` object in the specification. /// /// More information: /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects #[derive(Debug, Clone, Trace, Finalize)] -pub struct IntegerIndexed { +pub struct TypedArray { viewed_array_buffer: JsObject, #[unsafe_ignore_trace] kind: TypedArrayKind, @@ -22,7 +41,24 @@ pub struct IntegerIndexed { array_length: u64, } -impl IntegerIndexed { +impl JsData for TypedArray { + fn internal_methods(&self) -> &'static InternalObjectMethods { + static METHODS: InternalObjectMethods = InternalObjectMethods { + __get_own_property__: integer_indexed_exotic_get_own_property, + __has_property__: integer_indexed_exotic_has_property, + __define_own_property__: integer_indexed_exotic_define_own_property, + __get__: integer_indexed_exotic_get, + __set__: integer_indexed_exotic_set, + __delete__: integer_indexed_exotic_delete, + __own_property_keys__: integer_indexed_exotic_own_property_keys, + ..ORDINARY_INTERNAL_METHODS + }; + + &METHODS + } +} + +impl TypedArray { pub(crate) const fn new( viewed_array_buffer: JsObject, kind: TypedArrayKind, @@ -84,3 +120,413 @@ impl IntegerIndexed { self.array_length } } + +/// `CanonicalNumericIndexString ( argument )` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-canonicalnumericindexstring +fn canonical_numeric_index_string(argument: &JsString) -> Option { + // 1. If argument is "-0", return -0𝔽. + if argument == utf16!("-0") { + return Some(-0.0); + } + + // 2. Let n be ! ToNumber(argument). + let n = argument.to_number(); + + // 3. If ! ToString(n) is argument, return n. + if &Number::to_js_string(n) == argument { + return Some(n); + } + + // 4. Return undefined. + None +} + +/// `[[GetOwnProperty]]` internal method for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p +pub(crate) fn integer_indexed_exotic_get_own_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult> { + let p = match key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) + } + PropertyKey::Index(index) => Some(index.get().into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, then + if let Some(numeric_index) = p { + // i. Let value be IntegerIndexedElementGet(O, numericIndex). + let value = integer_indexed_element_get(obj, numeric_index); + + // ii. If value is undefined, return undefined. + // iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. + return Ok(value.map(|v| { + PropertyDescriptor::builder() + .value(v) + .writable(true) + .enumerable(true) + .configurable(true) + .build() + })); + } + + // 2. Return OrdinaryGetOwnProperty(O, P). + ordinary_get_own_property(obj, key, context) +} + +/// `[[HasProperty]]` internal method for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p +pub(crate) fn integer_indexed_exotic_has_property( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let p = match key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) + } + PropertyKey::Index(index) => Some(index.get().into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, return IsValidIntegerIndex(O, numericIndex). + if let Some(numeric_index) = p { + return Ok(is_valid_integer_index(obj, numeric_index)); + } + + // 2. Return ? OrdinaryHasProperty(O, P). + ordinary_has_property(obj, key, context) +} + +/// `[[DefineOwnProperty]]` internal method for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc +pub(crate) fn integer_indexed_exotic_define_own_property( + obj: &JsObject, + key: &PropertyKey, + desc: PropertyDescriptor, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let p = match key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) + } + PropertyKey::Index(index) => Some(index.get().into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, then + if let Some(numeric_index) = p { + // i. If IsValidIntegerIndex(O, numericIndex) is false, return false. + if !is_valid_integer_index(obj, numeric_index) { + return Ok(false); + } + + // ii. If Desc has a [[Configurable]] field and Desc.[[Configurable]] is false, return false. + if desc.configurable() == Some(false) { + return Ok(false); + } + + // iii. If Desc has an [[Enumerable]] field and Desc.[[Enumerable]] is false, return false. + if desc.enumerable() == Some(false) { + return Ok(false); + } + + // iv. If IsAccessorDescriptor(Desc) is true, return false. + if desc.is_accessor_descriptor() { + return Ok(false); + } + + // v. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, return false. + if desc.writable() == Some(false) { + return Ok(false); + } + + // vi. If Desc has a [[Value]] field, perform ? IntegerIndexedElementSet(O, numericIndex, Desc.[[Value]]). + if let Some(value) = desc.value() { + integer_indexed_element_set(obj, numeric_index, value, context)?; + } + + // vii. Return true. + return Ok(true); + } + + // 2. Return ! OrdinaryDefineOwnProperty(O, P, Desc). + ordinary_define_own_property(obj, key, desc, context) +} + +/// Internal method `[[Get]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-get-p-receiver +pub(crate) fn integer_indexed_exotic_get( + obj: &JsObject, + key: &PropertyKey, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let p = match key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) + } + PropertyKey::Index(index) => Some(index.get().into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, then + if let Some(numeric_index) = p { + // i. Return IntegerIndexedElementGet(O, numericIndex). + return Ok(integer_indexed_element_get(obj, numeric_index).unwrap_or_default()); + } + + // 2. Return ? OrdinaryGet(O, P, Receiver). + ordinary_get(obj, key, receiver, context) +} + +/// Internal method `[[Set]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver +pub(crate) fn integer_indexed_exotic_set( + obj: &JsObject, + key: PropertyKey, + value: JsValue, + receiver: JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let p = match &key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) + } + PropertyKey::Index(index) => Some(index.get().into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, then + if let Some(numeric_index) = p { + // i. If SameValue(O, Receiver) is true, then + if JsValue::same_value(&obj.clone().into(), &receiver) { + // 1. Perform ? IntegerIndexedElementSet(O, numericIndex, V). + integer_indexed_element_set(obj, numeric_index, &value, context)?; + + // 2. Return true. + return Ok(true); + } + + // ii. If IsValidIntegerIndex(O, numericIndex) is false, return true. + if !is_valid_integer_index(obj, numeric_index) { + return Ok(true); + } + } + + // 2. Return ? OrdinarySet(O, P, V, Receiver). + ordinary_set(obj, key, value, receiver, context) +} + +/// Internal method `[[Delete]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p +pub(crate) fn integer_indexed_exotic_delete( + obj: &JsObject, + key: &PropertyKey, + context: &mut InternalMethodContext<'_>, +) -> JsResult { + let p = match &key { + PropertyKey::String(key) => { + // 1.a. Let numericIndex be CanonicalNumericIndexString(P). + canonical_numeric_index_string(key) + } + PropertyKey::Index(index) => Some(index.get().into()), + PropertyKey::Symbol(_) => None, + }; + + // 1. If P is a String, then + // 1.b. If numericIndex is not undefined, then + if let Some(numeric_index) = p { + // i. If IsValidIntegerIndex(O, numericIndex) is false, return true; else return false. + return Ok(!is_valid_integer_index(obj, numeric_index)); + } + + // 2. Return ! OrdinaryDelete(O, P). + ordinary_delete(obj, key, context) +} + +/// Internal method `[[OwnPropertyKeys]]` for Integer-Indexed exotic objects. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys +#[allow(clippy::unnecessary_wraps)] +pub(crate) fn integer_indexed_exotic_own_property_keys( + obj: &JsObject, + _context: &mut Context, +) -> JsResult> { + let obj = obj.borrow(); + let inner = obj.downcast_ref::().expect( + "integer indexed exotic method should only be callable from integer indexed objects", + ); + + // 1. Let keys be a new empty List. + let mut keys = if inner.is_detached() { + vec![] + } else { + // 2. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false, then + // a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do + // i. Add ! ToString(𝔽(i)) as the last element of keys. + (0..inner.array_length()).map(PropertyKey::from).collect() + }; + + // 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do + // a. Add P as the last element of keys. + // + // 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do + // a. Add P as the last element of keys. + keys.extend(obj.properties.shape.keys()); + + // 5. Return keys. + Ok(keys) +} + +/// Abstract operation `IntegerIndexedElementGet ( O, index )`. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementget +fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option { + // 1. If ! IsValidIntegerIndex(O, index) is false, return undefined. + if !is_valid_integer_index(obj, index) { + return None; + } + + let inner = obj + .downcast_ref::() + .expect("Must be an integer indexed object"); + let buffer = inner.viewed_array_buffer(); + let buffer = buffer.borrow(); + let buffer = buffer.as_buffer().expect("Must be a buffer"); + let buffer = buffer + .data() + .expect("already checked that it's not detached"); + + // 2. Let offset be O.[[ByteOffset]]. + let offset = inner.byte_offset(); + + // 3. Let arrayTypeName be the String value of O.[[TypedArrayName]]. + // 6. Let elementType be the Element Type value in Table 73 for arrayTypeName. + let elem_type = inner.kind(); + + // 4. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. + let size = elem_type.element_size(); + + // 5. Let indexedPosition be (ℝ(index) × elementSize) + offset. + let indexed_position = ((index as u64 * size) + offset) as usize; + + // 7. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered). + + // SAFETY: The integer indexed object guarantees that the buffer is aligned. + // The call to `is_valid_integer_index` guarantees that the index is in-bounds. + let value = unsafe { + buffer + .subslice(indexed_position..) + .get_value(elem_type, atomic::Ordering::Relaxed) + }; + + Some(value.into()) +} + +/// Abstract operation `IntegerIndexedElementSet ( O, index, value )`. +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementset +pub(crate) fn integer_indexed_element_set( + obj: &JsObject, + index: f64, + value: &JsValue, + context: &mut InternalMethodContext<'_>, +) -> JsResult<()> { + let obj_borrow = obj.borrow(); + let inner = obj_borrow.downcast_ref::().expect( + "integer indexed exotic method should only be callable from integer indexed objects", + ); + + // 1. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value). + // 2. Otherwise, let numValue be ? ToNumber(value). + let value = inner.kind().get_element(value, context)?; + + if !is_valid_integer_index(obj, index) { + return Ok(()); + } + + // 3. If ! IsValidIntegerIndex(O, index) is true, then + // a. Let offset be O.[[ByteOffset]]. + let offset = inner.byte_offset(); + + // b. Let arrayTypeName be the String value of O.[[TypedArrayName]]. + // e. Let elementType be the Element Type value in Table 73 for arrayTypeName. + let elem_type = inner.kind(); + + // c. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. + let size = elem_type.element_size(); + + // d. Let indexedPosition be (ℝ(index) × elementSize) + offset. + let indexed_position = ((index as u64 * size) + offset) as usize; + + let buffer = inner.viewed_array_buffer(); + let mut buffer = buffer.borrow_mut(); + let mut buffer = buffer.as_buffer_mut().expect("Must be a buffer"); + let mut buffer = buffer + .data_mut() + .expect("already checked that it's not detached"); + + // f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered). + + // SAFETY: The integer indexed object guarantees that the buffer is aligned. + // The call to `is_valid_integer_index` guarantees that the index is in-bounds. + unsafe { + buffer + .subslice_mut(indexed_position..) + .set_value(value, atomic::Ordering::Relaxed); + } + + // 4. Return NormalCompletion(undefined). + Ok(()) +} diff --git a/boa_engine/src/builtins/typed_array/mod.rs b/boa_engine/src/builtins/typed_array/mod.rs index 110460c7690..9729b24cdab 100644 --- a/boa_engine/src/builtins/typed_array/mod.rs +++ b/boa_engine/src/builtins/typed_array/mod.rs @@ -36,14 +36,14 @@ mod integer_indexed_object; pub(crate) use builtin::{is_valid_integer_index, BuiltinTypedArray}; pub(crate) use element::{Atomic, ClampedU8, Element}; -pub use integer_indexed_object::IntegerIndexed; +pub use integer_indexed_object::{IntegerIndexed, TypedArray}; -pub(crate) trait TypedArray { +pub(crate) trait TypedArrayMarker { type Element: Element; const ERASED: TypedArrayKind; } -impl IntrinsicObject for T { +impl IntrinsicObject for T { fn get(intrinsics: &Intrinsics) -> JsObject { Self::STANDARD_CONSTRUCTOR(intrinsics.constructors()).constructor() } @@ -86,18 +86,18 @@ impl IntrinsicObject for T { } } -impl BuiltInObject for T { - const NAME: JsString = ::ERASED.js_name(); +impl BuiltInObject for T { + const NAME: JsString = ::ERASED.js_name(); const ATTRIBUTE: Attribute = Attribute::WRITABLE .union(Attribute::NON_ENUMERABLE) .union(Attribute::CONFIGURABLE); } -impl BuiltInConstructor for T { +impl BuiltInConstructor for T { const LENGTH: usize = 3; const STANDARD_CONSTRUCTOR: fn(&StandardConstructors) -> &StandardConstructor = - ::ERASED.standard_constructor(); + ::ERASED.standard_constructor(); /// `23.2.5.1 TypedArray ( ...args )` /// @@ -143,7 +143,7 @@ impl BuiltInConstructor for T { get_prototype_from_constructor(new_target, T::STANDARD_CONSTRUCTOR, context)?; // ii. If firstArgument has a [[TypedArrayName]] internal slot, then - let o = if first_argument.is_typed_array() { + let o = if first_argument.is::() { // 1. Perform ? InitializeTypedArrayFromTypedArray(O, firstArgument). BuiltinTypedArray::initialize_from_typed_array::(proto, first_argument, context)? } else if first_argument.is_buffer() { @@ -216,7 +216,7 @@ impl BuiltInConstructor for T { #[derive(Debug, Copy, Clone)] pub struct Int8Array; -impl TypedArray for Int8Array { +impl TypedArrayMarker for Int8Array { type Element = i8; const ERASED: TypedArrayKind = TypedArrayKind::Int8; @@ -226,7 +226,7 @@ impl TypedArray for Int8Array { #[derive(Debug, Copy, Clone)] pub struct Uint8Array; -impl TypedArray for Uint8Array { +impl TypedArrayMarker for Uint8Array { type Element = u8; const ERASED: TypedArrayKind = TypedArrayKind::Uint8; @@ -236,7 +236,7 @@ impl TypedArray for Uint8Array { #[derive(Debug, Copy, Clone)] pub struct Uint8ClampedArray; -impl TypedArray for Uint8ClampedArray { +impl TypedArrayMarker for Uint8ClampedArray { type Element = ClampedU8; const ERASED: TypedArrayKind = TypedArrayKind::Uint8Clamped; @@ -246,7 +246,7 @@ impl TypedArray for Uint8ClampedArray { #[derive(Debug, Copy, Clone)] pub struct Int16Array; -impl TypedArray for Int16Array { +impl TypedArrayMarker for Int16Array { type Element = i16; const ERASED: TypedArrayKind = TypedArrayKind::Int16; @@ -256,7 +256,7 @@ impl TypedArray for Int16Array { #[derive(Debug, Copy, Clone)] pub struct Uint16Array; -impl TypedArray for Uint16Array { +impl TypedArrayMarker for Uint16Array { type Element = u16; const ERASED: TypedArrayKind = TypedArrayKind::Uint16; @@ -266,7 +266,7 @@ impl TypedArray for Uint16Array { #[derive(Debug, Copy, Clone)] pub struct Int32Array; -impl TypedArray for Int32Array { +impl TypedArrayMarker for Int32Array { type Element = i32; const ERASED: TypedArrayKind = TypedArrayKind::Int32; @@ -276,7 +276,7 @@ impl TypedArray for Int32Array { #[derive(Debug, Copy, Clone)] pub struct Uint32Array; -impl TypedArray for Uint32Array { +impl TypedArrayMarker for Uint32Array { type Element = u32; const ERASED: TypedArrayKind = TypedArrayKind::Uint32; @@ -286,7 +286,7 @@ impl TypedArray for Uint32Array { #[derive(Debug, Copy, Clone)] pub struct BigInt64Array; -impl TypedArray for BigInt64Array { +impl TypedArrayMarker for BigInt64Array { type Element = i64; const ERASED: TypedArrayKind = TypedArrayKind::BigInt64; @@ -296,7 +296,7 @@ impl TypedArray for BigInt64Array { #[derive(Debug, Copy, Clone)] pub struct BigUint64Array; -impl TypedArray for BigUint64Array { +impl TypedArrayMarker for BigUint64Array { type Element = u64; const ERASED: TypedArrayKind = TypedArrayKind::BigUint64; @@ -306,7 +306,7 @@ impl TypedArray for BigUint64Array { #[derive(Debug, Copy, Clone)] pub struct Float32Array; -impl TypedArray for Float32Array { +impl TypedArrayMarker for Float32Array { type Element = f32; const ERASED: TypedArrayKind = TypedArrayKind::Float32; @@ -316,7 +316,7 @@ impl TypedArray for Float32Array { #[derive(Debug, Copy, Clone)] pub struct Float64Array; -impl TypedArray for Float64Array { +impl TypedArrayMarker for Float64Array { type Element = f64; const ERASED: TypedArrayKind = TypedArrayKind::Float64; diff --git a/boa_engine/src/builtins/weak/weak_ref.rs b/boa_engine/src/builtins/weak/weak_ref.rs index 51543d37ad2..f4d8866ee9b 100644 --- a/boa_engine/src/builtins/weak/weak_ref.rs +++ b/boa_engine/src/builtins/weak/weak_ref.rs @@ -5,7 +5,7 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, ErasedVTableObject, JsObject}, property::Attribute, realm::Realm, string::common::StaticJsStrings, @@ -87,7 +87,7 @@ impl BuiltInConstructor for WeakRef { let weak_ref = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::weak_ref(WeakGc::new(target.inner())), + WeakGc::new(target.inner()), ); // 4. Perform AddToKeptObjects(target). @@ -108,17 +108,14 @@ impl WeakRef { pub(crate) fn deref(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { // 1. Let weakRef be the this value. // 2. Perform ? RequireInternalSlot(weakRef, [[WeakRefTarget]]). - let weak_ref = this.as_object().map(JsObject::borrow).ok_or_else(|| { - JsNativeError::typ().with_message(format!( - "WeakRef.prototype.deref: expected `this` to be an `object`, got value of type `{}`", - this.type_of() - )) - })?; - - let weak_ref = weak_ref.as_weak_ref().ok_or_else(|| { - JsNativeError::typ() - .with_message("WeakRef.prototype.deref: expected `this` to be a `WeakRef` object") - })?; + let weak_ref = this + .as_object() + .and_then(|o| o.downcast_ref::>()) + .ok_or_else(|| { + JsNativeError::typ().with_message(format!( + "WeakRef.prototype.deref: expected `this` to be a `WeakRef` object" + )) + })?; // 3. Return WeakRefDeref(weakRef). diff --git a/boa_engine/src/builtins/weak_map/mod.rs b/boa_engine/src/builtins/weak_map/mod.rs index 5824cdbf2f2..cafca94b5ce 100644 --- a/boa_engine/src/builtins/weak_map/mod.rs +++ b/boa_engine/src/builtins/weak_map/mod.rs @@ -14,7 +14,7 @@ use crate::{ }, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, ErasedVTableObject, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, @@ -24,6 +24,8 @@ use crate::{ use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; +type NativeWeakMap = boa_gc::WeakMap; + #[derive(Debug, Trace, Finalize)] pub(crate) struct WeakMap; @@ -88,7 +90,7 @@ impl BuiltInConstructor for WeakMap { let map = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::weak_map(boa_gc::WeakMap::new()), + NativeWeakMap::new(), ); // 4. If iterable is either undefined or null, return map. @@ -128,15 +130,12 @@ impl WeakMap { ) -> JsResult { // 1. Let M be the this value. // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). - let Some(obj) = this.as_object() else { - return Err(JsNativeError::typ() - .with_message("WeakMap.delete: called with non-object value") - .into()); - }; - let mut obj_borrow = obj.borrow_mut(); - let m = obj_borrow.as_weak_map_mut().ok_or_else(|| { - JsNativeError::typ().with_message("WeakMap.delete: called with non-object value") - })?; + let mut map = this + .as_object() + .and_then(|o| o.downcast_mut::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("WeakMap.delete: called with non-object value") + })?; // 3. Let entries be M.[[WeakMapData]]. // 4. If key is not an Object, return false. @@ -150,7 +149,7 @@ impl WeakMap { // ii. Set p.[[Value]] to empty. // iii. Return true. // 6. Return false. - Ok(m.remove(key.inner()).is_some().into()) + Ok(map.remove(key.inner()).is_some().into()) } /// `WeakMap.prototype.get ( key )` @@ -168,15 +167,12 @@ impl WeakMap { ) -> JsResult { // 1. Let M be the this value. // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). - let Some(obj) = this.as_object() else { - return Err(JsNativeError::typ() - .with_message("WeakMap.get: called with non-object value") - .into()); - }; - let mut obj_borrow = obj.borrow_mut(); - let m = obj_borrow.as_weak_map_mut().ok_or_else(|| { - JsNativeError::typ().with_message("WeakMap.get: called with non-object value") - })?; + let map = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("WeakMap.get: called with non-object value") + })?; // 3. Let entries be M.[[WeakMapData]]. // 4. If key is not an Object, return undefined. @@ -187,7 +183,7 @@ impl WeakMap { // 5. For each Record { [[Key]], [[Value]] } p of entries, do // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return p.[[Value]]. // 6. Return undefined. - Ok(m.get(key.inner()).unwrap_or_default()) + Ok(map.get(key.inner()).unwrap_or_default()) } /// `WeakMap.prototype.has ( key )` @@ -205,15 +201,12 @@ impl WeakMap { ) -> JsResult { // 1. Let M be the this value. // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). - let Some(obj) = this.as_object() else { - return Err(JsNativeError::typ() - .with_message("WeakMap.has: called with non-object value") - .into()); - }; - let mut obj_borrow = obj.borrow_mut(); - let m = obj_borrow.as_weak_map_mut().ok_or_else(|| { - JsNativeError::typ().with_message("WeakMap.has: called with non-object value") - })?; + let map = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("WeakMap.has: called with non-object value") + })?; // 3. Let entries be M.[[WeakMapData]]. // 4. If key is not an Object, return false. @@ -224,7 +217,7 @@ impl WeakMap { // 5. For each Record { [[Key]], [[Value]] } p of entries, do // a. If p.[[Key]] is not empty and SameValue(p.[[Key]], key) is true, return true. // 6. Return false. - Ok(m.contains_key(key.inner()).into()) + Ok(map.contains_key(key.inner()).into()) } /// `WeakMap.prototype.set ( key, value )` @@ -242,15 +235,12 @@ impl WeakMap { ) -> JsResult { // 1. Let M be the this value. // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). - let Some(obj) = this.as_object() else { - return Err(JsNativeError::typ() - .with_message("WeakMap.set: called with non-object value") - .into()); - }; - let mut obj_borrow = obj.borrow_mut(); - let m = obj_borrow.as_weak_map_mut().ok_or_else(|| { - JsNativeError::typ().with_message("WeakMap.set: called with non-object value") - })?; + let mut map = this + .as_object() + .and_then(|o| o.downcast_mut::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("WeakMap.set: called with non-object value") + })?; // 3. Let entries be M.[[WeakMapData]]. // 4. If key is not an Object, throw a TypeError exception. @@ -269,7 +259,7 @@ impl WeakMap { // ii. Return M. // 6. Let p be the Record { [[Key]]: key, [[Value]]: value }. // 7. Append p to entries. - m.insert(key.inner(), args.get_or_undefined(1).clone()); + map.insert(key.inner(), args.get_or_undefined(1).clone()); // 8. Return M. Ok(this.clone()) diff --git a/boa_engine/src/builtins/weak_set/mod.rs b/boa_engine/src/builtins/weak_set/mod.rs index de99c2195f5..7d6ba9595d4 100644 --- a/boa_engine/src/builtins/weak_set/mod.rs +++ b/boa_engine/src/builtins/weak_set/mod.rs @@ -11,16 +11,18 @@ use crate::{ builtins::{BuiltInBuilder, BuiltInConstructor, BuiltInObject, IntrinsicObject}, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, js_string, - object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, + object::{internal_methods::get_prototype_from_constructor, ErasedVTableObject, JsObject}, property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, symbol::JsSymbol, Context, JsArgs, JsNativeError, JsResult, JsString, JsValue, }; -use boa_gc::{Finalize, Trace, WeakMap}; +use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; +type NativeWeakSet = boa_gc::WeakMap; + #[derive(Debug, Trace, Finalize)] pub(crate) struct WeakSet; @@ -84,7 +86,7 @@ impl BuiltInConstructor for WeakSet { let weak_set = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::weak_set(WeakMap::new()), + NativeWeakSet::new(), ); // 4. If iterable is either undefined or null, return set. @@ -140,15 +142,12 @@ impl WeakSet { ) -> JsResult { // 1. Let S be the this value. // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). - let Some(obj) = this.as_object() else { - return Err(JsNativeError::typ() - .with_message("WeakSet.add: called with non-object value") - .into()); - }; - let mut obj_borrow = obj.borrow_mut(); - let o = obj_borrow.as_weak_set_mut().ok_or_else(|| { - JsNativeError::typ().with_message("WeakSet.add: called with non-object value") - })?; + let mut set = this + .as_object() + .and_then(|o| o.downcast_mut::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("WeakSet.add: called with non-object value") + })?; // 3. If Type(value) is not Object, throw a TypeError exception. let value = args.get_or_undefined(0); @@ -162,14 +161,14 @@ impl WeakSet { // 4. Let entries be the List that is S.[[WeakSetData]]. // 5. For each element e of entries, do - if o.contains_key(value.inner()) { + if set.contains_key(value.inner()) { // a. If e is not empty and SameValue(e, value) is true, then // i. Return S. return Ok(this.clone()); } // 6. Append value as the last element of entries. - o.insert(value.inner(), ()); + set.insert(value.inner(), ()); // 7. Return S. Ok(this.clone()) @@ -192,15 +191,12 @@ impl WeakSet { ) -> JsResult { // 1. Let S be the this value. // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). - let Some(obj) = this.as_object() else { - return Err(JsNativeError::typ() - .with_message("WeakSet.delete: called with non-object value") - .into()); - }; - let mut obj_borrow = obj.borrow_mut(); - let o = obj_borrow.as_weak_set_mut().ok_or_else(|| { - JsNativeError::typ().with_message("WeakSet.delete: called with non-object value") - })?; + let mut set = this + .as_object() + .and_then(|o| o.downcast_mut::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("WeakSet.delete: called with non-object value") + })?; // 3. If Type(value) is not Object, return false. let value = args.get_or_undefined(0); @@ -214,12 +210,12 @@ impl WeakSet { // i. Replace the element of entries whose value is e with an element whose value is empty. // ii. Return true. // 6. Return false. - Ok(o.remove(value.inner()).is_some().into()) + Ok(set.remove(value.inner()).is_some().into()) } /// `WeakSet.prototype.has( value )` /// - /// The has() method returns a boolean indicating whether an object exists in a `WeakSet` or not. + /// The has() method returns a boolean indicating whether an object exists in a `WeakSet` or not. /// /// More information: /// - [ECMAScript reference][spec] @@ -234,15 +230,12 @@ impl WeakSet { ) -> JsResult { // 1. Let S be the this value. // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). - let Some(obj) = this.as_object() else { - return Err(JsNativeError::typ() - .with_message("WeakSet.has: called with non-object value") - .into()); - }; - let obj_borrow = obj.borrow(); - let o = obj_borrow.as_weak_set().ok_or_else(|| { - JsNativeError::typ().with_message("WeakSet.has: called with non-object value") - })?; + let set = this + .as_object() + .and_then(|o| o.downcast_ref::()) + .ok_or_else(|| { + JsNativeError::typ().with_message("WeakSet.has: called with non-object value") + })?; // 3. Let entries be the List that is S.[[WeakSetData]]. // 4. If Type(value) is not Object, return false. @@ -254,6 +247,6 @@ impl WeakSet { // 5. For each element e of entries, do // a. If e is not empty and SameValue(e, value) is true, return true. // 6. Return false. - Ok(o.contains_key(value.inner()).into()) + Ok(set.contains_key(value.inner()).into()) } } diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs index 7db6f2ff2c8..1d570836242 100644 --- a/boa_engine/src/bytecompiler/function.rs +++ b/boa_engine/src/bytecompiler/function.rs @@ -114,7 +114,7 @@ impl FunctionCompiler { .set(CodeBlockFlags::IS_GENERATOR, self.generator); compiler.code_block_flags.set( CodeBlockFlags::HAS_PROTOTYPE_PROPERTY, - !self.arrow && !self.method, + !self.arrow && !self.method && !self.r#async && !self.generator, ); if self.arrow { diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index ad4daedd4cc..d5b45907009 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -108,7 +108,7 @@ use crate::{ error::JsNativeError, native_function::NativeFunction, object::{ - ConstructorBuilder, FunctionBinding, JsFunction, JsObject, NativeObject, ObjectData, + ConstructorBuilder, FunctionBinding, JsFunction, JsObject, NativeObject, PROTOTYPE, }, property::{Attribute, PropertyDescriptor, PropertyKey}, @@ -202,7 +202,7 @@ pub trait Class: NativeObject + Sized { let object = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::native_object(data), + data, ); Self::object_constructor(&object, args, context)?; @@ -237,7 +237,7 @@ pub trait Class: NativeObject + Sized { let object = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::native_object(data), + data, ); Self::object_constructor(&object, &[], context)?; diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index b28cdfa0ad8..f2f7d4e9f67 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -3,11 +3,12 @@ use boa_gc::{Finalize, Trace}; use crate::{ - builtins::{iterable::IteratorPrototypes, uri::UriFunctions}, + builtins::{iterable::IteratorPrototypes, uri::UriFunctions, Array, OrdinaryObject}, js_string, object::{ + internal_methods::immutable_prototype::IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, shape::{shared_shape::template::ObjectTemplate, RootShape}, - JsFunction, JsObject, ObjectData, CONSTRUCTOR, PROTOTYPE, + JsFunction, JsObject, Object, CONSTRUCTOR, PROTOTYPE, }, property::{Attribute, PropertyKey}, JsSymbol, @@ -192,9 +193,9 @@ pub struct StandardConstructors { impl Default for StandardConstructors { fn default() -> Self { Self { - object: StandardConstructor::with_prototype(JsObject::from_proto_and_data( - None, - ObjectData::object_prototype(), + object: StandardConstructor::with_prototype(JsObject::from_object_and_vtable( + Object::::default(), + &IMMUTABLE_PROTOTYPE_EXOTIC_INTERNAL_METHODS, )), async_generator_function: StandardConstructor::default(), proxy: StandardConstructor::default(), @@ -205,22 +206,15 @@ impl Default for StandardConstructors { }, async_function: StandardConstructor::default(), generator_function: StandardConstructor::default(), - array: StandardConstructor::with_prototype(JsObject::from_proto_and_data( - None, - ObjectData::array(), - )), + array: StandardConstructor::with_prototype(JsObject::from_proto_and_data(None, Array)), bigint: StandardConstructor::default(), - number: StandardConstructor::with_prototype(JsObject::from_proto_and_data( - None, - ObjectData::number(0.0), - )), + number: StandardConstructor::with_prototype(JsObject::from_proto_and_data(None, 0.0)), boolean: StandardConstructor::with_prototype(JsObject::from_proto_and_data( - None, - ObjectData::boolean(false), + None, false, )), string: StandardConstructor::with_prototype(JsObject::from_proto_and_data( None, - ObjectData::string(js_string!()), + js_string!(), )), regexp: StandardConstructor::default(), symbol: StandardConstructor::default(), diff --git a/boa_engine/src/environments/runtime/declarative/function.rs b/boa_engine/src/environments/runtime/declarative/function.rs index 125c440461f..2684765bc69 100644 --- a/boa_engine/src/environments/runtime/declarative/function.rs +++ b/boa_engine/src/environments/runtime/declarative/function.rs @@ -1,6 +1,6 @@ use boa_gc::{custom_trace, Finalize, GcRefCell, Trace}; -use crate::{JsNativeError, JsObject, JsResult, JsValue}; +use crate::{builtins::function::OrdinaryFunction, JsNativeError, JsObject, JsResult, JsValue}; use super::PoisonableEnvironment; @@ -93,6 +93,7 @@ impl FunctionEnvironment { /// # Panics /// /// Panics if the function object of the environment is not a function. + #[track_caller] pub(crate) fn has_super_binding(&self) -> bool { // 1.If envRec.[[ThisBindingStatus]] is lexical, return false. if matches!(&*self.slots.this.borrow(), ThisBindingStatus::Lexical) { @@ -102,8 +103,7 @@ impl FunctionEnvironment { // 2. If envRec.[[FunctionObject]].[[HomeObject]] is undefined, return false; otherwise, return true. self.slots .function_object - .borrow() - .as_function() + .downcast_ref::() .expect("function object must be function") .get_home_object() .is_some() diff --git a/boa_engine/src/error.rs b/boa_engine/src/error.rs index 6749a8b6f2b..e0c580095f4 100644 --- a/boa_engine/src/error.rs +++ b/boa_engine/src/error.rs @@ -6,7 +6,6 @@ use crate::{ builtins::{error::ErrorKind, Array}, js_string, object::JsObject, - object::ObjectData, property::PropertyDescriptor, realm::Realm, string::utf16, @@ -242,9 +241,7 @@ impl JsError { let obj = val .as_object() .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; - let error = obj - .borrow() - .as_error() + let error = *obj.downcast_ref::() .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; let try_get_property = |key: JsString, name, context: &mut Context| { @@ -952,7 +949,7 @@ impl JsNativeError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::error(tag), + tag, ); o.create_non_enumerable_data_property_or_throw( diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index a9bb3af0ddf..9fd6c28142d 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -76,6 +76,7 @@ #[cfg(not(target_has_atomic = "ptr"))] compile_error!("Boa requires a lock free `AtomicUsize` in order to work properly."); +extern crate self as boa_engine; extern crate static_assertions as sa; pub mod bigint; @@ -109,12 +110,17 @@ mod tests; /// A convenience module that re-exports the most commonly-used Boa APIs pub mod prelude { pub use crate::{ + bigint::JsBigInt, + context::Context, error::{JsError, JsNativeError, JsNativeErrorKind}, + host_defined::HostDefined, module::Module, native_function::NativeFunction, - object::JsObject, + object::{JsData, JsObject, NativeObject}, script::Script, - Context, JsBigInt, JsResult, JsString, JsValue, + string::JsString, + symbol::JsSymbol, + value::JsValue, }; pub use boa_parser::Source; } @@ -123,21 +129,7 @@ use std::result::Result as StdResult; // Export things to root level #[doc(inline)] -pub use crate::{ - bigint::JsBigInt, - context::Context, - error::{JsError, JsNativeError, JsNativeErrorKind}, - host_defined::HostDefined, - module::Module, - native_function::NativeFunction, - object::JsObject, - script::Script, - string::JsString, - symbol::JsSymbol, - value::JsValue, -}; -#[doc(inline)] -pub use boa_parser::Source; +pub use prelude::*; /// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`) pub type JsResult = StdResult; diff --git a/boa_engine/src/module/mod.rs b/boa_engine/src/module/mod.rs index d61c807d236..67007603105 100644 --- a/boa_engine/src/module/mod.rs +++ b/boa_engine/src/module/mod.rs @@ -22,20 +22,21 @@ //! [module]: https://tc39.es/ecma262/#sec-abstract-module-records mod loader; +mod namespace; mod source; mod synthetic; pub use loader::*; +pub use namespace::ModuleNamespace; use source::SourceTextModule; pub use synthetic::{SyntheticModule, SyntheticModuleInitializer}; use std::cell::{Cell, RefCell}; +use std::collections::HashSet; use std::hash::Hash; use std::io::Read; use std::rc::Rc; -use std::{collections::HashSet, hash::BuildHasherDefault}; -use indexmap::IndexSet; -use rustc_hash::{FxHashSet, FxHasher}; +use rustc_hash::FxHashSet; use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use boa_interner::Interner; @@ -45,8 +46,7 @@ use boa_profiler::Profiler; use crate::{ builtins::promise::{PromiseCapability, PromiseState}, environments::DeclarativeEnvironment, - js_string, - object::{JsObject, JsPromise, ObjectData}, + object::{JsObject, JsPromise}, realm::Realm, Context, HostDefined, JsError, JsResult, JsString, JsValue, NativeFunction, }; @@ -574,54 +574,3 @@ impl Hash for Module { std::ptr::hash(self.inner.as_ref(), state); } } - -/// Module namespace exotic object. -/// -/// Exposes the bindings exported by a [`Module`] to be accessed from ECMAScript code. -#[derive(Debug, Trace, Finalize)] -pub struct ModuleNamespace { - module: Module, - #[unsafe_ignore_trace] - exports: IndexSet>, -} - -impl ModuleNamespace { - /// Abstract operation [`ModuleNamespaceCreate ( module, exports )`][spec]. - /// - /// [spec]: https://tc39.es/ecma262/#sec-modulenamespacecreate - pub(crate) fn create(module: Module, names: Vec, context: &mut Context) -> JsObject { - // 1. Assert: module.[[Namespace]] is empty. - // ignored since this is ensured by `Module::namespace`. - - // 6. Let sortedExports be a List whose elements are the elements of exports ordered as if an Array of the same values had been sorted using %Array.prototype.sort% using undefined as comparefn. - let mut exports = names.into_iter().collect::>(); - exports.sort(); - - // 2. Let internalSlotsList be the internal slots listed in Table 32. - // 3. Let M be MakeBasicObject(internalSlotsList). - // 4. Set M's essential internal methods to the definitions specified in 10.4.6. - // 5. Set M.[[Module]] to module. - // 7. Set M.[[Exports]] to sortedExports. - // 8. Create own properties of M corresponding to the definitions in 28.3. - let namespace = context.intrinsics().templates().namespace().create( - ObjectData::module_namespace(Self { module, exports }), - vec![js_string!("Module").into()], - ); - - // 9. Set module.[[Namespace]] to M. - // Ignored because this is done by `Module::namespace` - - // 10. Return M. - namespace - } - - /// Gets the export names of the Module Namespace object. - pub(crate) const fn exports(&self) -> &IndexSet> { - &self.exports - } - - /// Gest the module associated with this Module Namespace object. - pub(crate) const fn module(&self) -> &Module { - &self.module - } -} diff --git a/boa_engine/src/object/internal_methods/module_namespace.rs b/boa_engine/src/module/namespace.rs similarity index 70% rename from boa_engine/src/object/internal_methods/module_namespace.rs rename to boa_engine/src/module/namespace.rs index b070962805e..4a4777dbd83 100644 --- a/boa_engine/src/object/internal_methods/module_namespace.rs +++ b/boa_engine/src/module/namespace.rs @@ -1,37 +1,95 @@ -use std::collections::HashSet; - -use crate::{ - js_string, - module::BindingName, - object::{JsObject, JsPrototype}, - property::{PropertyDescriptor, PropertyKey}, - Context, JsNativeError, JsResult, JsValue, -}; +use std::{collections::HashSet, hash::BuildHasherDefault}; + +use indexmap::IndexSet; +use rustc_hash::FxHasher; + +use boa_gc::{Finalize, Trace}; -use super::{ - immutable_prototype, ordinary_define_own_property, ordinary_delete, ordinary_get, - ordinary_get_own_property, ordinary_has_property, ordinary_own_property_keys, - InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, +use crate::object::internal_methods::immutable_prototype::immutable_prototype_exotic_set_prototype_of; +use crate::object::internal_methods::{ + ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property, + ordinary_has_property, ordinary_own_property_keys, InternalObjectMethods, + ORDINARY_INTERNAL_METHODS, InternalMethodContext, }; +use crate::object::{JsData, JsPrototype}; +use crate::property::{PropertyDescriptor, PropertyKey}; +use crate::{js_string, object::JsObject, Context, JsResult, JsString, JsValue}; +use crate::{JsNativeError, Module}; -/// Definitions of the internal object methods for [**Module Namespace Exotic Objects**][spec]. +use super::BindingName; + +/// Module namespace exotic object. /// -/// [spec]: https://tc39.es/ecma262/#sec-module-namespace-exotic-objects -pub(crate) static MODULE_NAMESPACE_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = - InternalObjectMethods { - __get_prototype_of__: module_namespace_exotic_get_prototype_of, - __set_prototype_of__: module_namespace_exotic_set_prototype_of, - __is_extensible__: module_namespace_exotic_is_extensible, - __prevent_extensions__: module_namespace_exotic_prevent_extensions, - __get_own_property__: module_namespace_exotic_get_own_property, - __define_own_property__: module_namespace_exotic_define_own_property, - __has_property__: module_namespace_exotic_has_property, - __get__: module_namespace_exotic_get, - __set__: module_namespace_exotic_set, - __delete__: module_namespace_exotic_delete, - __own_property_keys__: module_namespace_exotic_own_property_keys, - ..ORDINARY_INTERNAL_METHODS - }; +/// Exposes the bindings exported by a [`Module`] to be accessed from ECMAScript code. +#[derive(Debug, Trace, Finalize)] +pub struct ModuleNamespace { + module: Module, + #[unsafe_ignore_trace] + exports: IndexSet>, +} + +impl JsData for ModuleNamespace { + fn internal_methods(&self) -> &'static InternalObjectMethods { + static METHODS: InternalObjectMethods = InternalObjectMethods { + __get_prototype_of__: module_namespace_exotic_get_prototype_of, + __set_prototype_of__: module_namespace_exotic_set_prototype_of, + __is_extensible__: module_namespace_exotic_is_extensible, + __prevent_extensions__: module_namespace_exotic_prevent_extensions, + __get_own_property__: module_namespace_exotic_get_own_property, + __define_own_property__: module_namespace_exotic_define_own_property, + __has_property__: module_namespace_exotic_has_property, + __get__: module_namespace_exotic_get, + __set__: module_namespace_exotic_set, + __delete__: module_namespace_exotic_delete, + __own_property_keys__: module_namespace_exotic_own_property_keys, + ..ORDINARY_INTERNAL_METHODS + }; + + &METHODS + } +} + +impl ModuleNamespace { + /// Abstract operation [`ModuleNamespaceCreate ( module, exports )`][spec]. + /// + /// [spec]: https://tc39.es/ecma262/#sec-modulenamespacecreate + pub(crate) fn create(module: Module, names: Vec, context: &mut Context) -> JsObject { + // 1. Assert: module.[[Namespace]] is empty. + // ignored since this is ensured by `Module::namespace`. + + // 6. Let sortedExports be a List whose elements are the elements of exports ordered as if an Array of the same values had been sorted using %Array.prototype.sort% using undefined as comparefn. + let mut exports = names.into_iter().collect::>(); + exports.sort(); + + // 2. Let internalSlotsList be the internal slots listed in Table 32. + // 3. Let M be MakeBasicObject(internalSlotsList). + // 4. Set M's essential internal methods to the definitions specified in 10.4.6. + // 5. Set M.[[Module]] to module. + // 7. Set M.[[Exports]] to sortedExports. + // 8. Create own properties of M corresponding to the definitions in 28.3. + let namespace = context + .intrinsics() + .templates() + .namespace() + .create(Self { module, exports }, vec![js_string!("Module").into()]); + + // 9. Set module.[[Namespace]] to M. + // Ignored because this is done by `Module::namespace` + + // 10. Return M. + namespace + } + + /// Gets the export names of the Module Namespace object. + pub(crate) const fn exports(&self) -> &IndexSet> { + &self.exports + } + + /// Gest the module associated with this Module Namespace object. + pub(crate) const fn module(&self) -> &Module { + &self.module + } +} /// [`[[GetPrototypeOf]] ( )`][spec]. /// @@ -56,7 +114,7 @@ fn module_namespace_exotic_set_prototype_of( ) -> JsResult { // 1. Return ! SetImmutablePrototype(O, V). Ok( - immutable_prototype::immutable_prototype_exotic_set_prototype_of(obj, val, context) + immutable_prototype_exotic_set_prototype_of(obj, val, context) .expect("this must not fail per the spec"), ) } @@ -94,9 +152,8 @@ fn module_namespace_exotic_get_own_property( }; { - let obj = obj.borrow(); let obj = obj - .as_module_namespace() + .downcast_ref::() .expect("internal method can only be called on module namespace objects"); // 2. Let exports be O.[[Exports]]. let exports = obj.exports(); @@ -173,9 +230,8 @@ fn module_namespace_exotic_has_property( PropertyKey::String(s) => s.clone(), }; - let obj = obj.borrow(); let obj = obj - .as_module_namespace() + .downcast_ref::() .expect("internal method can only be called on module namespace objects"); // 2. Let exports be O.[[Exports]]. @@ -203,9 +259,8 @@ fn module_namespace_exotic_get( PropertyKey::String(s) => s.clone(), }; - let obj = obj.borrow(); let obj = obj - .as_module_namespace() + .downcast_ref::() .expect("internal method can only be called on module namespace objects"); // 2. Let exports be O.[[Exports]]. @@ -295,9 +350,8 @@ fn module_namespace_exotic_delete( PropertyKey::String(s) => s.clone(), }; - let obj = obj.borrow(); let obj = obj - .as_module_namespace() + .downcast_ref::() .expect("internal method can only be called on module namespace objects"); // 2. Let exports be O.[[Exports]]. @@ -318,9 +372,8 @@ fn module_namespace_exotic_own_property_keys( // 2. Let symbolKeys be OrdinaryOwnPropertyKeys(O). let symbol_keys = ordinary_own_property_keys(obj, context)?; - let obj = obj.borrow(); let obj = obj - .as_module_namespace() + .downcast_ref::() .expect("internal method can only be called on module namespace objects"); // 1. Let exports be O.[[Exports]]. diff --git a/boa_engine/src/native_function.rs b/boa_engine/src/native_function.rs index b3483e682be..8cc1ba37e4e 100644 --- a/boa_engine/src/native_function.rs +++ b/boa_engine/src/native_function.rs @@ -6,10 +6,17 @@ use boa_gc::{custom_trace, Finalize, Gc, Trace}; use crate::{ - builtins::function::ConstructorKind, - object::{FunctionObjectBuilder, JsFunction, JsPromise}, + builtins::{function::ConstructorKind, OrdinaryObject}, + context::intrinsics::StandardConstructors, + object::{ + internal_methods::{ + get_prototype_from_constructor, CallValue, InternalObjectMethods, + ORDINARY_INTERNAL_METHODS, + }, + FunctionObjectBuilder, JsData, JsFunction, JsPromise, + }, realm::Realm, - Context, JsResult, JsValue, + Context, JsNativeError, JsObject, JsResult, JsValue, }; /// The required signature for all native built-in function pointers. @@ -70,6 +77,27 @@ unsafe impl Trace for NativeFunctionObject { }); } +impl JsData for NativeFunctionObject { + fn internal_methods(&self) -> &'static InternalObjectMethods { + static FUNCTION: InternalObjectMethods = InternalObjectMethods { + __call__: native_function_call, + ..ORDINARY_INTERNAL_METHODS + }; + + static CONSTRUCTOR: InternalObjectMethods = InternalObjectMethods { + __call__: native_function_call, + __construct__: native_function_construct, + ..ORDINARY_INTERNAL_METHODS + }; + + if self.constructor.is_some() { + &CONSTRUCTOR + } else { + &FUNCTION + } + } +} + /// A callable Rust function that can be invoked by the engine. /// /// `NativeFunction` functions are divided in two: @@ -303,3 +331,119 @@ impl NativeFunction { FunctionObjectBuilder::new(realm, self).build() } } + +/// Call this object. +/// +/// # Panics +/// +/// Panics if the object is currently mutably borrowed. +// +pub(crate) fn native_function_call( + obj: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + let args = context.vm.pop_n_values(argument_count); + let _func = context.vm.pop(); + let this = context.vm.pop(); + + // We technically don't need this since native functions don't push any new frames to the + // vm, but we'll eventually have to combine the native stack with the vm stack. + context.check_runtime_limits()?; + let this_function_object = obj.clone(); + + let NativeFunctionObject { + f: function, + constructor, + realm, + } = obj + .downcast_ref::() + .expect("the object should be a native function object") + .clone(); + + let mut realm = realm.unwrap_or_else(|| context.realm().clone()); + + context.swap_realm(&mut realm); + context.vm.native_active_function = Some(this_function_object); + + let result = if constructor.is_some() { + function.call(&JsValue::undefined(), &args, context) + } else { + function.call(&this, &args, context) + } + .map_err(|err| err.inject_realm(context.realm().clone())); + + context.vm.native_active_function = None; + context.swap_realm(&mut realm); + + context.vm.push(result?); + + Ok(CallValue::Complete) +} + +/// Construct an instance of this object with the specified arguments. +/// +/// # Panics +/// +/// Panics if the object is currently mutably borrowed. +// +fn native_function_construct( + obj: &JsObject, + argument_count: usize, + context: &mut Context, +) -> JsResult { + // We technically don't need this since native functions don't push any new frames to the + // vm, but we'll eventually have to combine the native stack with the vm stack. + context.check_runtime_limits()?; + let this_function_object = obj.clone(); + + let NativeFunctionObject { + f: function, + constructor, + realm, + } = obj + .downcast_ref::() + .expect("the object should be a native function object") + .clone(); + + let mut realm = realm.unwrap_or_else(|| context.realm().clone()); + + context.swap_realm(&mut realm); + context.vm.native_active_function = Some(this_function_object); + + let new_target = context.vm.pop(); + let args = context.vm.pop_n_values(argument_count); + let _func = context.vm.pop(); + + let result = function + .call(&new_target, &args, context) + .map_err(|err| err.inject_realm(context.realm().clone())) + .and_then(|v| match v { + JsValue::Object(ref o) => Ok(o.clone()), + val => { + if constructor.expect("must be a constructor").is_base() || val.is_undefined() { + let prototype = get_prototype_from_constructor( + &new_target, + StandardConstructors::object, + context, + )?; + Ok(JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + prototype, + OrdinaryObject, + )) + } else { + Err(JsNativeError::typ() + .with_message("derived constructor can only return an Object or undefined") + .into()) + } + } + }); + + context.vm.native_active_function = None; + context.swap_realm(&mut realm); + + context.vm.push(result?); + + Ok(CallValue::Complete) +} diff --git a/boa_engine/src/object/internal_methods/arguments.rs b/boa_engine/src/object/internal_methods/arguments.rs deleted file mode 100644 index 252e80c45e3..00000000000 --- a/boa_engine/src/object/internal_methods/arguments.rs +++ /dev/null @@ -1,249 +0,0 @@ -use crate::{ - object::JsObject, - property::{DescriptorKind, PropertyDescriptor, PropertyKey}, - JsResult, JsValue, -}; - -use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; - -pub(crate) static ARGUMENTS_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = - InternalObjectMethods { - __get_own_property__: arguments_exotic_get_own_property, - __define_own_property__: arguments_exotic_define_own_property, - __get__: arguments_exotic_get, - __set__: arguments_exotic_set, - __delete__: arguments_exotic_delete, - ..ORDINARY_INTERNAL_METHODS - }; - -/// `[[GetOwnProperty]]` for arguments exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-getownproperty-p -pub(crate) fn arguments_exotic_get_own_property( - obj: &JsObject, - key: &PropertyKey, - context: &mut InternalMethodContext<'_>, -) -> JsResult> { - // 1. Let desc be OrdinaryGetOwnProperty(args, P). - // 2. If desc is undefined, return desc. - let Some(desc) = super::ordinary_get_own_property(obj, key, context)? else { - return Ok(None); - }; - - // 3. Let map be args.[[ParameterMap]]. - // 4. Let isMapped be ! HasOwnProperty(map, P). - // 5. If isMapped is true, then - if let PropertyKey::Index(index) = key { - if let Some(value) = obj - .borrow() - .as_mapped_arguments() - .expect("arguments exotic method must only be callable from arguments objects") - .get(index.get()) - { - // a. Set desc.[[Value]] to Get(map, P). - return Ok(Some( - PropertyDescriptor::builder() - .value(value) - .maybe_writable(desc.writable()) - .maybe_enumerable(desc.enumerable()) - .maybe_configurable(desc.configurable()) - .build(), - )); - } - } - - // 6. Return desc. - Ok(Some(desc)) -} - -/// `[[DefineOwnProperty]]` for arguments exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-defineownproperty-p-desc -#[allow(clippy::needless_pass_by_value)] -pub(crate) fn arguments_exotic_define_own_property( - obj: &JsObject, - key: &PropertyKey, - desc: PropertyDescriptor, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - // 2. Let isMapped be HasOwnProperty(map, P). - let mapped = if let &PropertyKey::Index(index) = &key { - // 1. Let map be args.[[ParameterMap]]. - obj.borrow() - .as_mapped_arguments() - .expect("arguments exotic method must only be callable from arguments objects") - .get(index.get()) - .map(|value| (index, value)) - } else { - None - }; - - let new_arg_desc = match desc.kind() { - // 4. If isMapped is true and IsDataDescriptor(Desc) is true, then - // a. If Desc.[[Value]] is not present and Desc.[[Writable]] is present and its - // value is false, then - DescriptorKind::Data { - writable: Some(false), - value: None, - } => - // i. Set newArgDesc to a copy of Desc. - // ii. Set newArgDesc.[[Value]] to Get(map, P). - { - if let Some((_, value)) = &mapped { - PropertyDescriptor::builder() - .value(value.clone()) - .writable(false) - .maybe_enumerable(desc.enumerable()) - .maybe_configurable(desc.configurable()) - .build() - } else { - desc.clone() - } - } - - // 3. Let newArgDesc be Desc. - _ => desc.clone(), - }; - - // 5. Let allowed be ? OrdinaryDefineOwnProperty(args, P, newArgDesc). - // 6. If allowed is false, return false. - if !super::ordinary_define_own_property(obj, key, new_arg_desc, context)? { - return Ok(false); - } - - // 7. If isMapped is true, then - if let Some((index, _)) = mapped { - // 1. Let map be args.[[ParameterMap]]. - let mut obj_mut = obj.borrow_mut(); - let map = obj_mut - .as_mapped_arguments_mut() - .expect("arguments exotic method must only be callable from arguments objects"); - - // a. If IsAccessorDescriptor(Desc) is true, then - if desc.is_accessor_descriptor() { - // i. Call map.[[Delete]](P). - map.delete(index.get()); - } - // b. Else, - else { - // i. If Desc.[[Value]] is present, then - if let Some(value) = desc.value() { - // 1. Let setStatus be Set(map, P, Desc.[[Value]], false). - // 2. Assert: setStatus is true because formal parameters mapped by argument objects are always writable. - map.set(index.get(), value); - } - - // ii. If Desc.[[Writable]] is present and its value is false, then - if desc.writable() == Some(false) { - // 1. Call map.[[Delete]](P). - map.delete(index.get()); - } - } - } - - // 8. Return true. - Ok(true) -} - -/// `[[Get]]` for arguments exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-get-p-receiver -pub(crate) fn arguments_exotic_get( - obj: &JsObject, - key: &PropertyKey, - receiver: JsValue, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - if let PropertyKey::Index(index) = key { - // 1. Let map be args.[[ParameterMap]]. - // 2. Let isMapped be ! HasOwnProperty(map, P). - if let Some(value) = obj - .borrow() - .as_mapped_arguments() - .expect("arguments exotic method must only be callable from arguments objects") - .get(index.get()) - { - // a. Assert: map contains a formal parameter mapping for P. - // b. Return Get(map, P). - return Ok(value); - } - } - - // 3. If isMapped is false, then - // a. Return ? OrdinaryGet(args, P, Receiver). - super::ordinary_get(obj, key, receiver, context) -} - -/// `[[Set]]` for arguments exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-set-p-v-receiver -pub(crate) fn arguments_exotic_set( - obj: &JsObject, - key: PropertyKey, - value: JsValue, - receiver: JsValue, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - // 1. If SameValue(args, Receiver) is false, then - // a. Let isMapped be false. - // 2. Else, - if let PropertyKey::Index(index) = &key { - if JsValue::same_value(&obj.clone().into(), &receiver) { - // a. Let map be args.[[ParameterMap]]. - // b. Let isMapped be ! HasOwnProperty(map, P). - // 3. If isMapped is true, then - // a. Let setStatus be Set(map, P, V, false). - // b. Assert: setStatus is true because formal parameters mapped by argument objects are always writable. - obj.borrow_mut() - .as_mapped_arguments_mut() - .expect("arguments exotic method must only be callable from arguments objects") - .set(index.get(), &value); - } - } - - // 4. Return ? OrdinarySet(args, P, V, Receiver). - super::ordinary_set(obj, key, value, receiver, context) -} - -/// `[[Delete]]` for arguments exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-arguments-exotic-objects-delete-p -pub(crate) fn arguments_exotic_delete( - obj: &JsObject, - key: &PropertyKey, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - // 3. Let result be ? OrdinaryDelete(args, P). - let result = super::ordinary_delete(obj, key, context)?; - - if result { - if let PropertyKey::Index(index) = key { - // 1. Let map be args.[[ParameterMap]]. - // 2. Let isMapped be ! HasOwnProperty(map, P). - // 4. If result is true and isMapped is true, then - // a. Call map.[[Delete]](P). - obj.borrow_mut() - .as_mapped_arguments_mut() - .expect("arguments exotic method must only be callable from arguments objects") - .delete(index.get()); - } - } - - // 5. Return result. - Ok(result) -} diff --git a/boa_engine/src/object/internal_methods/array.rs b/boa_engine/src/object/internal_methods/array.rs deleted file mode 100644 index 8b588743a9f..00000000000 --- a/boa_engine/src/object/internal_methods/array.rs +++ /dev/null @@ -1,258 +0,0 @@ -use crate::{ - error::JsNativeError, - object::JsObject, - property::{PropertyDescriptor, PropertyKey}, - string::utf16, - JsResult, -}; - -use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; - -/// Definitions of the internal object methods for array exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects -pub(crate) static ARRAY_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { - __define_own_property__: array_exotic_define_own_property, - ..ORDINARY_INTERNAL_METHODS -}; - -/// Define an own property for an array exotic object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc -pub(crate) fn array_exotic_define_own_property( - obj: &JsObject, - key: &PropertyKey, - desc: PropertyDescriptor, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - // 1. Assert: IsPropertyKey(P) is true. - match key { - // 2. If P is "length", then - PropertyKey::String(ref s) if s == utf16!("length") => { - // a. Return ? ArraySetLength(A, Desc). - - array_set_length(obj, desc, context) - } - // 3. Else if P is an array index, then - PropertyKey::Index(index) => { - let index = index.get(); - - // a. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length"). - let old_len_desc = - super::ordinary_get_own_property(obj, &utf16!("length").into(), context)? - .expect("the property descriptor must exist"); - - // b. Assert: ! IsDataDescriptor(oldLenDesc) is true. - debug_assert!(old_len_desc.is_data_descriptor()); - - // c. Assert: oldLenDesc.[[Configurable]] is false. - debug_assert!(!old_len_desc.expect_configurable()); - - // d. Let oldLen be oldLenDesc.[[Value]]. - // e. Assert: oldLen is a non-negative integral Number. - // f. Let index be ! ToUint32(P). - let old_len = old_len_desc - .expect_value() - .to_u32(context) - .expect("this ToUint32 call must not fail"); - - // g. If index ≥ oldLen and oldLenDesc.[[Writable]] is false, return false. - if index >= old_len && !old_len_desc.expect_writable() { - return Ok(false); - } - - // h. Let succeeded be ! OrdinaryDefineOwnProperty(A, P, Desc). - if super::ordinary_define_own_property(obj, key, desc, context)? { - // j. If index ≥ oldLen, then - if index >= old_len { - // i. Set oldLenDesc.[[Value]] to index + 1𝔽. - let old_len_desc = PropertyDescriptor::builder() - .value(index + 1) - .maybe_writable(old_len_desc.writable()) - .maybe_enumerable(old_len_desc.enumerable()) - .maybe_configurable(old_len_desc.configurable()); - - // ii. Set succeeded to OrdinaryDefineOwnProperty(A, "length", oldLenDesc). - let succeeded = super::ordinary_define_own_property( - obj, - &utf16!("length").into(), - old_len_desc.into(), - context, - )?; - - // iii. Assert: succeeded is true. - debug_assert!(succeeded); - } - - // k. Return true. - Ok(true) - } else { - // i. If succeeded is false, return false. - Ok(false) - } - } - // 4. Return OrdinaryDefineOwnProperty(A, P, Desc). - _ => super::ordinary_define_own_property(obj, key, desc, context), - } -} - -/// Abstract operation `ArraySetLength ( A, Desc )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-arraysetlength -fn array_set_length( - obj: &JsObject, - desc: PropertyDescriptor, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - // 1. If Desc.[[Value]] is absent, then - let Some(new_len_val) = desc.value() else { - // a. Return OrdinaryDefineOwnProperty(A, "length", Desc). - return super::ordinary_define_own_property(obj, &utf16!("length").into(), desc, context); - }; - - // 3. Let newLen be ? ToUint32(Desc.[[Value]]). - let new_len = new_len_val.to_u32(context)?; - - // 4. Let numberLen be ? ToNumber(Desc.[[Value]]). - let number_len = new_len_val.to_number(context)?; - - // 5. If SameValueZero(newLen, numberLen) is false, throw a RangeError exception. - #[allow(clippy::float_cmp)] - if f64::from(new_len) != number_len { - return Err(JsNativeError::range() - .with_message("bad length for array") - .into()); - } - - // 2. Let newLenDesc be a copy of Desc. - // 6. Set newLenDesc.[[Value]] to newLen. - let mut new_len_desc = PropertyDescriptor::builder() - .value(new_len) - .maybe_writable(desc.writable()) - .maybe_enumerable(desc.enumerable()) - .maybe_configurable(desc.configurable()); - - // 7. Let oldLenDesc be OrdinaryGetOwnProperty(A, "length"). - let old_len_desc = super::ordinary_get_own_property(obj, &utf16!("length").into(), context)? - .expect("the property descriptor must exist"); - - // 8. Assert: ! IsDataDescriptor(oldLenDesc) is true. - debug_assert!(old_len_desc.is_data_descriptor()); - - // 9. Assert: oldLenDesc.[[Configurable]] is false. - debug_assert!(!old_len_desc.expect_configurable()); - - // 10. Let oldLen be oldLenDesc.[[Value]]. - let old_len = old_len_desc.expect_value(); - - // 11. If newLen ≥ oldLen, then - if new_len >= old_len.to_u32(context)? { - // a. Return OrdinaryDefineOwnProperty(A, "length", newLenDesc). - return super::ordinary_define_own_property( - obj, - &utf16!("length").into(), - new_len_desc.build(), - context, - ); - } - - // 12. If oldLenDesc.[[Writable]] is false, return false. - if !old_len_desc.expect_writable() { - return Ok(false); - } - - // 13. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true. - let new_writable = if new_len_desc.inner().writable().unwrap_or(true) { - true - } - // 14. Else, - else { - // a. NOTE: Setting the [[Writable]] attribute to false is deferred in case any - // elements cannot be deleted. - // c. Set newLenDesc.[[Writable]] to true. - new_len_desc = new_len_desc.writable(true); - - // b. Let newWritable be false. - false - }; - - // 15. Let succeeded be ! OrdinaryDefineOwnProperty(A, "length", newLenDesc). - // 16. If succeeded is false, return false. - if !super::ordinary_define_own_property( - obj, - &utf16!("length").into(), - new_len_desc.clone().build(), - context, - ) - .expect("this OrdinaryDefineOwnProperty call must not fail") - { - return Ok(false); - } - - // 17. For each own property key P of A that is an array index, whose numeric value is - // greater than or equal to newLen, in descending numeric index order, do - let ordered_keys = { - let mut keys: Vec<_> = obj - .borrow() - .properties - .index_property_keys() - .filter(|idx| new_len <= *idx && *idx < u32::MAX) - .collect(); - keys.sort_unstable_by(|x, y| y.cmp(x)); - keys - }; - - for index in ordered_keys { - // a. Let deleteSucceeded be ! A.[[Delete]](P). - // b. If deleteSucceeded is false, then - if !obj.__delete__(&index.into(), context)? { - // i. Set newLenDesc.[[Value]] to ! ToUint32(P) + 1𝔽. - new_len_desc = new_len_desc.value(index + 1); - - // ii. If newWritable is false, set newLenDesc.[[Writable]] to false. - if !new_writable { - new_len_desc = new_len_desc.writable(false); - } - - // iii. Perform ! OrdinaryDefineOwnProperty(A, "length", newLenDesc). - super::ordinary_define_own_property( - obj, - &utf16!("length").into(), - new_len_desc.build(), - context, - ) - .expect("this OrdinaryDefineOwnProperty call must not fail"); - - // iv. Return false. - return Ok(false); - } - } - - // 18. If newWritable is false, then - if !new_writable { - // a. Set succeeded to ! OrdinaryDefineOwnProperty(A, "length", - // PropertyDescriptor { [[Writable]]: false }). - let succeeded = super::ordinary_define_own_property( - obj, - &utf16!("length").into(), - PropertyDescriptor::builder().writable(false).build(), - context, - ) - .expect("this OrdinaryDefineOwnProperty call must not fail"); - - // b. Assert: succeeded is true. - debug_assert!(succeeded); - } - - // 19. Return true. - Ok(true) -} diff --git a/boa_engine/src/object/internal_methods/bound_function.rs b/boa_engine/src/object/internal_methods/bound_function.rs deleted file mode 100644 index f991ec18bca..00000000000 --- a/boa_engine/src/object/internal_methods/bound_function.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::{object::JsObject, Context, JsResult, JsValue}; - -use super::{CallValue, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; - -/// Definitions of the internal object methods for function objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects -pub(crate) static BOUND_FUNCTION_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = - InternalObjectMethods { - __call__: bound_function_exotic_call, - ..ORDINARY_INTERNAL_METHODS - }; - -pub(crate) static BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = - InternalObjectMethods { - __call__: bound_function_exotic_call, - __construct__: bound_function_exotic_construct, - ..ORDINARY_INTERNAL_METHODS - }; - -/// Internal method `[[Call]]` for Bound Function Exotic Objects -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist -#[allow(clippy::unnecessary_wraps)] -fn bound_function_exotic_call( - obj: &JsObject, - argument_count: usize, - context: &mut Context, -) -> JsResult { - let obj = obj.borrow(); - let bound_function = obj - .as_bound_function() - .expect("bound function exotic method should only be callable from bound function objects"); - - let arguments_start_index = context.vm.stack.len() - argument_count; - - // 1. Let target be F.[[BoundTargetFunction]]. - let target = bound_function.target_function(); - context.vm.stack[arguments_start_index - 1] = target.clone().into(); - - // 2. Let boundThis be F.[[BoundThis]]. - let bound_this = bound_function.this(); - context.vm.stack[arguments_start_index - 2] = bound_this.clone(); - - // 3. Let boundArgs be F.[[BoundArguments]]. - let bound_args = bound_function.args(); - - // 4. Let args be the list-concatenation of boundArgs and argumentsList. - context - .vm - .insert_values_at(bound_args, arguments_start_index); - - // 5. Return ? Call(target, boundThis, args). - Ok(target.__call__(bound_args.len() + argument_count)) -} - -/// Internal method `[[Construct]]` for Bound Function Exotic Objects -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget -#[allow(clippy::unnecessary_wraps)] -fn bound_function_exotic_construct( - function_object: &JsObject, - argument_count: usize, - context: &mut Context, -) -> JsResult { - let new_target = context.vm.pop(); - - debug_assert!(new_target.is_object(), "new.target should be an object"); - - let object = function_object.borrow(); - let bound_function = object - .as_bound_function() - .expect("bound function exotic method should only be callable from bound function objects"); - - // 1. Let target be F.[[BoundTargetFunction]]. - let target = bound_function.target_function(); - - // 2. Assert: IsConstructor(target) is true. - - // 3. Let boundArgs be F.[[BoundArguments]]. - let bound_args = bound_function.args(); - - // 4. Let args be the list-concatenation of boundArgs and argumentsList. - let arguments_start_index = context.vm.stack.len() - argument_count; - context - .vm - .insert_values_at(bound_args, arguments_start_index); - - // 5. If SameValue(F, newTarget) is true, set newTarget to target. - let function_object: JsValue = function_object.clone().into(); - let new_target = if JsValue::same_value(&function_object, &new_target) { - target.clone().into() - } else { - new_target - }; - - // 6. Return ? Construct(target, args, newTarget). - context.vm.push(new_target); - Ok(target.__construct__(bound_args.len() + argument_count)) -} diff --git a/boa_engine/src/object/internal_methods/function.rs b/boa_engine/src/object/internal_methods/function.rs deleted file mode 100644 index 1449f901a3a..00000000000 --- a/boa_engine/src/object/internal_methods/function.rs +++ /dev/null @@ -1,354 +0,0 @@ -use crate::{ - builtins::function::ThisMode, - context::intrinsics::StandardConstructors, - environments::{FunctionSlots, ThisBindingStatus}, - native_function::NativeFunctionObject, - object::{ - internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}, - JsObject, ObjectData, - }, - vm::{CallFrame, CallFrameFlags}, - Context, JsNativeError, JsResult, JsValue, -}; - -use super::{get_prototype_from_constructor, CallValue}; - -/// Definitions of the internal object methods for function objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects -pub(crate) static FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { - __call__: function_call, - ..ORDINARY_INTERNAL_METHODS -}; - -pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { - __call__: function_call, - __construct__: function_construct, - ..ORDINARY_INTERNAL_METHODS -}; - -/// Call this object. -/// -/// # Panics -/// -/// Panics if the object is currently mutably borrowed. -// -// -pub(crate) fn function_call( - function_object: &JsObject, - argument_count: usize, - context: &mut Context, -) -> JsResult { - context.check_runtime_limits()?; - - let object = function_object.borrow(); - let function = object.as_function().expect("not a function"); - let realm = function.realm().clone(); - - if function.code.is_class_constructor() { - debug_assert!( - function.is_ordinary(), - "only ordinary functions can be classes" - ); - return Err(JsNativeError::typ() - .with_message("class constructor cannot be invoked without 'new'") - .with_realm(realm) - .into()); - } - - let code = function.code.clone(); - let environments = function.environments.clone(); - let script_or_module = function.script_or_module.clone(); - - drop(object); - - let env_fp = environments.len() as u32; - - let frame = CallFrame::new(code.clone(), script_or_module, environments, realm) - .with_argument_count(argument_count as u32) - .with_env_fp(env_fp); - - context.vm.push_frame(frame); - - let fp = context.vm.stack.len() - argument_count - CallFrame::FUNCTION_PROLOGUE; - context.vm.frame_mut().fp = fp as u32; - - let this = context.vm.stack[fp + CallFrame::THIS_POSITION].clone(); - - let lexical_this_mode = code.this_mode == ThisMode::Lexical; - - let this = if lexical_this_mode { - ThisBindingStatus::Lexical - } else if code.strict() { - ThisBindingStatus::Initialized(this.clone()) - } else if this.is_null_or_undefined() { - ThisBindingStatus::Initialized(context.realm().global_this().clone().into()) - } else { - ThisBindingStatus::Initialized( - this.to_object(context) - .expect("conversion cannot fail") - .into(), - ) - }; - - let mut last_env = 0; - - if code.has_binding_identifier() { - let index = context - .vm - .environments - .push_lexical(code.constant_compile_time_environment(last_env)); - context - .vm - .environments - .put_lexical_value(index, 0, function_object.clone().into()); - last_env += 1; - } - - context.vm.environments.push_function( - code.constant_compile_time_environment(last_env), - FunctionSlots::new(this, function_object.clone(), None), - ); - - Ok(CallValue::Ready) -} - -/// Construct an instance of this object with the specified arguments. -/// -/// # Panics -/// -/// Panics if the object is currently mutably borrowed. -// -fn function_construct( - this_function_object: &JsObject, - argument_count: usize, - context: &mut Context, -) -> JsResult { - context.check_runtime_limits()?; - - let object = this_function_object.borrow(); - let function = object.as_function().expect("not a function"); - let realm = function.realm().clone(); - - debug_assert!( - function.is_ordinary(), - "only ordinary functions can be constructed" - ); - - let code = function.code.clone(); - let environments = function.environments.clone(); - let script_or_module = function.script_or_module.clone(); - drop(object); - - let env_fp = environments.len() as u32; - - let new_target = context.vm.pop(); - - let at = context.vm.stack.len() - argument_count; - - let this = if code.is_derived_constructor() { - None - } else { - // If the prototype of the constructor is not an object, then use the default object - // prototype as prototype for the new object - // see - // see - let prototype = - get_prototype_from_constructor(&new_target, StandardConstructors::object, context)?; - let this = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::ordinary(), - ); - - this.initialize_instance_elements(this_function_object, context)?; - - Some(this) - }; - - let frame = CallFrame::new(code.clone(), script_or_module, environments, realm) - .with_argument_count(argument_count as u32) - .with_env_fp(env_fp) - .with_flags(CallFrameFlags::CONSTRUCT); - - context.vm.push_frame(frame); - - context.vm.frame_mut().fp = at as u32 - 1; - - let mut last_env = 0; - - if code.has_binding_identifier() { - let index = context - .vm - .environments - .push_lexical(code.constant_compile_time_environment(last_env)); - context - .vm - .environments - .put_lexical_value(index, 0, this_function_object.clone().into()); - last_env += 1; - } - - context.vm.environments.push_function( - code.constant_compile_time_environment(last_env), - FunctionSlots::new( - this.clone().map_or(ThisBindingStatus::Uninitialized, |o| { - ThisBindingStatus::Initialized(o.into()) - }), - this_function_object.clone(), - Some( - new_target - .as_object() - .expect("new.target should be an object") - .clone(), - ), - ), - ); - - // Insert `this` value - context - .vm - .stack - .insert(at - 1, this.map(JsValue::new).unwrap_or_default()); - - Ok(CallValue::Ready) -} - -/// Definitions of the internal object methods for native function objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects -pub(crate) static NATIVE_FUNCTION_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { - __call__: native_function_call, - ..ORDINARY_INTERNAL_METHODS -}; - -pub(crate) static NATIVE_CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = - InternalObjectMethods { - __call__: native_function_call, - __construct__: native_function_construct, - ..ORDINARY_INTERNAL_METHODS - }; - -/// Call this object. -/// -/// # Panics -/// -/// Panics if the object is currently mutably borrowed. -// -pub(crate) fn native_function_call( - obj: &JsObject, - argument_count: usize, - context: &mut Context, -) -> JsResult { - let args = context.vm.pop_n_values(argument_count); - let _func = context.vm.pop(); - let this = context.vm.pop(); - - // We technically don't need this since native functions don't push any new frames to the - // vm, but we'll eventually have to combine the native stack with the vm stack. - context.check_runtime_limits()?; - let this_function_object = obj.clone(); - - let NativeFunctionObject { - f: function, - constructor, - realm, - } = obj - .borrow() - .as_native_function() - .cloned() - .expect("the object should be a native function object"); - - let mut realm = realm.unwrap_or_else(|| context.realm().clone()); - - context.swap_realm(&mut realm); - context.vm.native_active_function = Some(this_function_object); - - let result = if constructor.is_some() { - function.call(&JsValue::undefined(), &args, context) - } else { - function.call(&this, &args, context) - } - .map_err(|err| err.inject_realm(context.realm().clone())); - - context.vm.native_active_function = None; - context.swap_realm(&mut realm); - - context.vm.push(result?); - - Ok(CallValue::Complete) -} - -/// Construct an instance of this object with the specified arguments. -/// -/// # Panics -/// -/// Panics if the object is currently mutably borrowed. -// -fn native_function_construct( - obj: &JsObject, - argument_count: usize, - context: &mut Context, -) -> JsResult { - // We technically don't need this since native functions don't push any new frames to the - // vm, but we'll eventually have to combine the native stack with the vm stack. - context.check_runtime_limits()?; - let this_function_object = obj.clone(); - - let NativeFunctionObject { - f: function, - constructor, - realm, - } = obj - .borrow() - .as_native_function() - .cloned() - .expect("the object should be a native function object"); - - let mut realm = realm.unwrap_or_else(|| context.realm().clone()); - - context.swap_realm(&mut realm); - context.vm.native_active_function = Some(this_function_object); - - let new_target = context.vm.pop(); - let args = context.vm.pop_n_values(argument_count); - let _func = context.vm.pop(); - - let result = function - .call(&new_target, &args, context) - .map_err(|err| err.inject_realm(context.realm().clone())) - .and_then(|v| match v { - JsValue::Object(ref o) => Ok(o.clone()), - val => { - if constructor.expect("must be a constructor").is_base() || val.is_undefined() { - let prototype = get_prototype_from_constructor( - &new_target, - StandardConstructors::object, - context, - )?; - Ok(JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - ObjectData::ordinary(), - )) - } else { - Err(JsNativeError::typ() - .with_message("derived constructor can only return an Object or undefined") - .into()) - } - } - }); - - context.vm.native_active_function = None; - context.swap_realm(&mut realm); - - context.vm.push(result?); - - Ok(CallValue::Complete) -} diff --git a/boa_engine/src/object/internal_methods/immutable_prototype.rs b/boa_engine/src/object/internal_methods/immutable_prototype.rs index 57c3ccb898f..0dded715a91 100644 --- a/boa_engine/src/object/internal_methods/immutable_prototype.rs +++ b/boa_engine/src/object/internal_methods/immutable_prototype.rs @@ -36,4 +36,4 @@ pub(crate) fn immutable_prototype_exotic_set_prototype_of( // 2. If SameValue(V, current) is true, return true. // 3. Return false. Ok(val == current) -} +} \ No newline at end of file diff --git a/boa_engine/src/object/internal_methods/integer_indexed.rs b/boa_engine/src/object/internal_methods/integer_indexed.rs deleted file mode 100644 index 467087ace75..00000000000 --- a/boa_engine/src/object/internal_methods/integer_indexed.rs +++ /dev/null @@ -1,439 +0,0 @@ -use std::sync::atomic; - -use boa_macros::utf16; - -use crate::{ - builtins::{typed_array::is_valid_integer_index, Number}, - object::JsObject, - property::{PropertyDescriptor, PropertyKey}, - Context, JsResult, JsString, JsValue, -}; - -use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; - -/// Definitions of the internal object methods for integer-indexed exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects -pub(crate) static INTEGER_INDEXED_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = - InternalObjectMethods { - __get_own_property__: integer_indexed_exotic_get_own_property, - __has_property__: integer_indexed_exotic_has_property, - __define_own_property__: integer_indexed_exotic_define_own_property, - __get__: integer_indexed_exotic_get, - __set__: integer_indexed_exotic_set, - __delete__: integer_indexed_exotic_delete, - __own_property_keys__: integer_indexed_exotic_own_property_keys, - ..ORDINARY_INTERNAL_METHODS - }; - -/// `CanonicalNumericIndexString ( argument )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-canonicalnumericindexstring -fn canonical_numeric_index_string(argument: &JsString) -> Option { - // 1. If argument is "-0", return -0𝔽. - if argument == utf16!("-0") { - return Some(-0.0); - } - - // 2. Let n be ! ToNumber(argument). - let n = argument.to_number(); - - // 3. If ! ToString(n) is argument, return n. - if &Number::to_js_string(n) == argument { - return Some(n); - } - - // 4. Return undefined. - None -} - -/// `[[GetOwnProperty]]` internal method for Integer-Indexed exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-getownproperty-p -pub(crate) fn integer_indexed_exotic_get_own_property( - obj: &JsObject, - key: &PropertyKey, - context: &mut InternalMethodContext<'_>, -) -> JsResult> { - let p = match key { - PropertyKey::String(key) => { - // 1.a. Let numericIndex be CanonicalNumericIndexString(P). - canonical_numeric_index_string(key) - } - PropertyKey::Index(index) => Some(index.get().into()), - PropertyKey::Symbol(_) => None, - }; - - // 1. If P is a String, then - // 1.b. If numericIndex is not undefined, then - if let Some(numeric_index) = p { - // i. Let value be IntegerIndexedElementGet(O, numericIndex). - let value = integer_indexed_element_get(obj, numeric_index); - - // ii. If value is undefined, return undefined. - // iii. Return the PropertyDescriptor { [[Value]]: value, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true }. - return Ok(value.map(|v| { - PropertyDescriptor::builder() - .value(v) - .writable(true) - .enumerable(true) - .configurable(true) - .build() - })); - } - - // 2. Return OrdinaryGetOwnProperty(O, P). - super::ordinary_get_own_property(obj, key, context) -} - -/// `[[HasProperty]]` internal method for Integer-Indexed exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-hasproperty-p -pub(crate) fn integer_indexed_exotic_has_property( - obj: &JsObject, - key: &PropertyKey, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - let p = match key { - PropertyKey::String(key) => { - // 1.a. Let numericIndex be CanonicalNumericIndexString(P). - canonical_numeric_index_string(key) - } - PropertyKey::Index(index) => Some(index.get().into()), - PropertyKey::Symbol(_) => None, - }; - - // 1. If P is a String, then - // 1.b. If numericIndex is not undefined, return IsValidIntegerIndex(O, numericIndex). - if let Some(numeric_index) = p { - return Ok(is_valid_integer_index(obj, numeric_index)); - } - - // 2. Return ? OrdinaryHasProperty(O, P). - super::ordinary_has_property(obj, key, context) -} - -/// `[[DefineOwnProperty]]` internal method for Integer-Indexed exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-defineownproperty-p-desc -pub(crate) fn integer_indexed_exotic_define_own_property( - obj: &JsObject, - key: &PropertyKey, - desc: PropertyDescriptor, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - let p = match key { - PropertyKey::String(key) => { - // 1.a. Let numericIndex be CanonicalNumericIndexString(P). - canonical_numeric_index_string(key) - } - PropertyKey::Index(index) => Some(index.get().into()), - PropertyKey::Symbol(_) => None, - }; - - // 1. If P is a String, then - // 1.b. If numericIndex is not undefined, then - if let Some(numeric_index) = p { - // i. If IsValidIntegerIndex(O, numericIndex) is false, return false. - if !is_valid_integer_index(obj, numeric_index) { - return Ok(false); - } - - // ii. If Desc has a [[Configurable]] field and Desc.[[Configurable]] is false, return false. - if desc.configurable() == Some(false) { - return Ok(false); - } - - // iii. If Desc has an [[Enumerable]] field and Desc.[[Enumerable]] is false, return false. - if desc.enumerable() == Some(false) { - return Ok(false); - } - - // iv. If IsAccessorDescriptor(Desc) is true, return false. - if desc.is_accessor_descriptor() { - return Ok(false); - } - - // v. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, return false. - if desc.writable() == Some(false) { - return Ok(false); - } - - // vi. If Desc has a [[Value]] field, perform ? IntegerIndexedElementSet(O, numericIndex, Desc.[[Value]]). - if let Some(value) = desc.value() { - integer_indexed_element_set(obj, numeric_index, value, context)?; - } - - // vii. Return true. - return Ok(true); - } - - // 2. Return ! OrdinaryDefineOwnProperty(O, P, Desc). - super::ordinary_define_own_property(obj, key, desc, context) -} - -/// Internal method `[[Get]]` for Integer-Indexed exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-get-p-receiver -pub(crate) fn integer_indexed_exotic_get( - obj: &JsObject, - key: &PropertyKey, - receiver: JsValue, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - let p = match key { - PropertyKey::String(key) => { - // 1.a. Let numericIndex be CanonicalNumericIndexString(P). - canonical_numeric_index_string(key) - } - PropertyKey::Index(index) => Some(index.get().into()), - PropertyKey::Symbol(_) => None, - }; - - // 1. If P is a String, then - // 1.b. If numericIndex is not undefined, then - if let Some(numeric_index) = p { - // i. Return IntegerIndexedElementGet(O, numericIndex). - return Ok(integer_indexed_element_get(obj, numeric_index).unwrap_or_default()); - } - - // 2. Return ? OrdinaryGet(O, P, Receiver). - super::ordinary_get(obj, key, receiver, context) -} - -/// Internal method `[[Set]]` for Integer-Indexed exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver -pub(crate) fn integer_indexed_exotic_set( - obj: &JsObject, - key: PropertyKey, - value: JsValue, - receiver: JsValue, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - let p = match &key { - PropertyKey::String(key) => { - // 1.a. Let numericIndex be CanonicalNumericIndexString(P). - canonical_numeric_index_string(key) - } - PropertyKey::Index(index) => Some(index.get().into()), - PropertyKey::Symbol(_) => None, - }; - - // 1. If P is a String, then - // 1.b. If numericIndex is not undefined, then - if let Some(numeric_index) = p { - // i. If SameValue(O, Receiver) is true, then - if JsValue::same_value(&obj.clone().into(), &receiver) { - // 1. Perform ? IntegerIndexedElementSet(O, numericIndex, V). - integer_indexed_element_set(obj, numeric_index, &value, context)?; - - // 2. Return true. - return Ok(true); - } - - // ii. If IsValidIntegerIndex(O, numericIndex) is false, return true. - if !is_valid_integer_index(obj, numeric_index) { - return Ok(true); - } - } - - // 2. Return ? OrdinarySet(O, P, V, Receiver). - super::ordinary_set(obj, key, value, receiver, context) -} - -/// Internal method `[[Delete]]` for Integer-Indexed exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-delete-p -pub(crate) fn integer_indexed_exotic_delete( - obj: &JsObject, - key: &PropertyKey, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - let p = match &key { - PropertyKey::String(key) => { - // 1.a. Let numericIndex be CanonicalNumericIndexString(P). - canonical_numeric_index_string(key) - } - PropertyKey::Index(index) => Some(index.get().into()), - PropertyKey::Symbol(_) => None, - }; - - // 1. If P is a String, then - // 1.b. If numericIndex is not undefined, then - if let Some(numeric_index) = p { - // i. If IsValidIntegerIndex(O, numericIndex) is false, return true; else return false. - return Ok(!is_valid_integer_index(obj, numeric_index)); - } - - // 2. Return ! OrdinaryDelete(O, P). - super::ordinary_delete(obj, key, context) -} - -/// Internal method `[[OwnPropertyKeys]]` for Integer-Indexed exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-ownpropertykeys -#[allow(clippy::unnecessary_wraps)] -pub(crate) fn integer_indexed_exotic_own_property_keys( - obj: &JsObject, - _context: &mut Context, -) -> JsResult> { - let obj = obj.borrow(); - let inner = obj.as_typed_array().expect( - "integer indexed exotic method should only be callable from integer indexed objects", - ); - - // 1. Let keys be a new empty List. - let mut keys = if inner.is_detached() { - vec![] - } else { - // 2. If IsDetachedBuffer(O.[[ViewedArrayBuffer]]) is false, then - // a. For each integer i starting with 0 such that i < O.[[ArrayLength]], in ascending order, do - // i. Add ! ToString(𝔽(i)) as the last element of keys. - (0..inner.array_length()).map(PropertyKey::from).collect() - }; - - // 3. For each own property key P of O such that Type(P) is String and P is not an array index, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - // - // 4. For each own property key P of O such that Type(P) is Symbol, in ascending chronological order of property creation, do - // a. Add P as the last element of keys. - keys.extend(obj.properties.shape.keys()); - - // 5. Return keys. - Ok(keys) -} - -/// Abstract operation `IntegerIndexedElementGet ( O, index )`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementget -fn integer_indexed_element_get(obj: &JsObject, index: f64) -> Option { - // 1. If ! IsValidIntegerIndex(O, index) is false, return undefined. - if !is_valid_integer_index(obj, index) { - return None; - } - - let obj = obj.borrow(); - let inner = obj.as_typed_array().expect("Must be a typed array"); - let buffer = inner.viewed_array_buffer(); - let buffer = buffer.borrow(); - let buffer = buffer.as_buffer().expect("Must be a buffer"); - let buffer = buffer - .data() - .expect("already checked that it's not detached"); - - // 2. Let offset be O.[[ByteOffset]]. - let offset = inner.byte_offset(); - - // 3. Let arrayTypeName be the String value of O.[[TypedArrayName]]. - // 6. Let elementType be the Element Type value in Table 73 for arrayTypeName. - let elem_type = inner.kind(); - - // 4. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. - let size = elem_type.element_size(); - - // 5. Let indexedPosition be (ℝ(index) × elementSize) + offset. - let indexed_position = ((index as u64 * size) + offset) as usize; - - // 7. Return GetValueFromBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, true, Unordered). - - // SAFETY: The integer indexed object guarantees that the buffer is aligned. - // The call to `is_valid_integer_index` guarantees that the index is in-bounds. - let value = unsafe { - buffer - .subslice(indexed_position..) - .get_value(elem_type, atomic::Ordering::Relaxed) - }; - - Some(value.into()) -} - -/// Abstract operation `IntegerIndexedElementSet ( O, index, value )`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-integerindexedelementset -pub(crate) fn integer_indexed_element_set( - obj: &JsObject, - index: f64, - value: &JsValue, - context: &mut InternalMethodContext<'_>, -) -> JsResult<()> { - let obj_borrow = obj.borrow(); - let inner = obj_borrow.as_typed_array().expect( - "integer indexed exotic method should only be callable from integer indexed objects", - ); - - // 1. If O.[[ContentType]] is BigInt, let numValue be ? ToBigInt(value). - // 2. Otherwise, let numValue be ? ToNumber(value). - let value = inner.kind().get_element(value, context)?; - - if !is_valid_integer_index(obj, index) { - return Ok(()); - } - - // 3. If ! IsValidIntegerIndex(O, index) is true, then - // a. Let offset be O.[[ByteOffset]]. - let offset = inner.byte_offset(); - - // b. Let arrayTypeName be the String value of O.[[TypedArrayName]]. - // e. Let elementType be the Element Type value in Table 73 for arrayTypeName. - let elem_type = inner.kind(); - - // c. Let elementSize be the Element Size value specified in Table 73 for arrayTypeName. - let size = elem_type.element_size(); - - // d. Let indexedPosition be (ℝ(index) × elementSize) + offset. - let indexed_position = ((index as u64 * size) + offset) as usize; - - let buffer = inner.viewed_array_buffer(); - let mut buffer = buffer.borrow_mut(); - let mut buffer = buffer.as_buffer_mut().expect("Must be a buffer"); - let mut buffer = buffer - .data_mut() - .expect("already checked that it's not detached"); - - // f. Perform SetValueInBuffer(O.[[ViewedArrayBuffer]], indexedPosition, elementType, numValue, true, Unordered). - - // SAFETY: The integer indexed object guarantees that the buffer is aligned. - // The call to `is_valid_integer_index` guarantees that the index is in-bounds. - unsafe { - buffer - .subslice_mut(indexed_position..) - .set_value(value, atomic::Ordering::Relaxed); - } - - // 4. Return NormalCompletion(undefined). - Ok(()) -} diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index 2c527b1a755..8c5395e6e5b 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/boa_engine/src/object/internal_methods/mod.rs @@ -20,18 +20,8 @@ use crate::{ }; use boa_profiler::Profiler; -pub(super) mod arguments; -pub(super) mod array; -pub(super) mod bound_function; -pub(super) mod function; -pub(super) mod immutable_prototype; -pub(super) mod integer_indexed; -pub(super) mod module_namespace; -pub(super) mod proxy; -pub(super) mod string; - -pub(crate) use array::ARRAY_EXOTIC_INTERNAL_METHODS; -pub(crate) use integer_indexed::integer_indexed_element_set; +pub(crate) mod immutable_prototype; +pub(crate) mod string; /// A lightweight wrapper around [`Context`] used in [`InternalObjectMethods`]. #[derive(Debug)] @@ -335,7 +325,7 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj /// For a guide on how to implement exotic internal methods, see `ORDINARY_INTERNAL_METHODS`. #[derive(Clone, Copy)] #[allow(clippy::type_complexity)] -pub(crate) struct InternalObjectMethods { +pub struct InternalObjectMethods { pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context) -> JsResult, pub(crate) __set_prototype_of__: fn(&JsObject, JsPrototype, &mut Context) -> JsResult, pub(crate) __is_extensible__: fn(&JsObject, &mut Context) -> JsResult, diff --git a/boa_engine/src/object/internal_methods/proxy.rs b/boa_engine/src/object/internal_methods/proxy.rs deleted file mode 100644 index 002afacee9e..00000000000 --- a/boa_engine/src/object/internal_methods/proxy.rs +++ /dev/null @@ -1,1025 +0,0 @@ -use crate::{ - builtins::{array, object::Object}, - error::JsNativeError, - object::{shape::slot::SlotAttributes, InternalObjectMethods, JsObject, JsPrototype}, - property::{PropertyDescriptor, PropertyKey}, - string::utf16, - value::Type, - Context, JsResult, JsValue, -}; -use rustc_hash::FxHashSet; - -use super::{CallValue, InternalMethodContext, ORDINARY_INTERNAL_METHODS}; - -/// Definitions of the internal object methods for array exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects -pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods = - InternalObjectMethods { - __get_prototype_of__: proxy_exotic_get_prototype_of, - __set_prototype_of__: proxy_exotic_set_prototype_of, - __is_extensible__: proxy_exotic_is_extensible, - __prevent_extensions__: proxy_exotic_prevent_extensions, - __get_own_property__: proxy_exotic_get_own_property, - __define_own_property__: proxy_exotic_define_own_property, - __has_property__: proxy_exotic_has_property, - __get__: proxy_exotic_get, - __set__: proxy_exotic_set, - __delete__: proxy_exotic_delete, - __own_property_keys__: proxy_exotic_own_property_keys, - ..ORDINARY_INTERNAL_METHODS - }; - -pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods = - InternalObjectMethods { - __call__: proxy_exotic_call, - ..PROXY_EXOTIC_INTERNAL_METHODS_BASIC - }; - -pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods = - InternalObjectMethods { - __call__: proxy_exotic_call, - __construct__: proxy_exotic_construct, - ..PROXY_EXOTIC_INTERNAL_METHODS_BASIC - }; - -/// `10.5.1 [[GetPrototypeOf]] ( )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getprototypeof -pub(crate) fn proxy_exotic_get_prototype_of( - obj: &JsObject, - context: &mut Context, -) -> JsResult { - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "getPrototypeOf"). - let Some(trap) = handler.get_method(utf16!("getPrototypeOf"), context)? else { - // 6. If trap is undefined, then - // a. Return ? target.[[GetPrototypeOf]](). - return target.__get_prototype_of__(context); - }; - - // 7. Let handlerProto be ? Call(trap, handler, « target »). - let handler_proto = trap.call(&handler.into(), &[target.clone().into()], context)?; - - // 8. If Type(handlerProto) is neither Object nor Null, throw a TypeError exception. - let handler_proto = match &handler_proto { - JsValue::Object(obj) => Some(obj.clone()), - JsValue::Null => None, - _ => { - return Err(JsNativeError::typ() - .with_message("Proxy trap result is neither object nor null") - .into()) - } - }; - - // 9. Let extensibleTarget be ? IsExtensible(target). - // 10. If extensibleTarget is true, return handlerProto. - if target.is_extensible(context)? { - return Ok(handler_proto); - } - - // 11. Let targetProto be ? target.[[GetPrototypeOf]](). - let target_proto = target.__get_prototype_of__(context)?; - - // 12. If SameValue(handlerProto, targetProto) is false, throw a TypeError exception. - if handler_proto != target_proto { - return Err(JsNativeError::typ() - .with_message("Proxy trap returned unexpected prototype") - .into()); - } - - // 13. Return handlerProto. - Ok(handler_proto) -} - -/// `10.5.2 [[SetPrototypeOf]] ( V )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-setprototypeof-v -pub(crate) fn proxy_exotic_set_prototype_of( - obj: &JsObject, - val: JsPrototype, - context: &mut Context, -) -> JsResult { - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "setPrototypeOf"). - let Some(trap) = handler.get_method(utf16!("setPrototypeOf"), context)? else { - // 6. If trap is undefined, then - // a. Return ? target.[[SetPrototypeOf]](V). - return target.__set_prototype_of__(val, context); - }; - - // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, V »)). - // 8. If booleanTrapResult is false, return false. - if !trap - .call( - &handler.into(), - &[ - target.clone().into(), - val.clone().map_or(JsValue::Null, Into::into), - ], - context, - )? - .to_boolean() - { - return Ok(false); - } - - // 9. Let extensibleTarget be ? IsExtensible(target). - // 10. If extensibleTarget is true, return true. - if target.is_extensible(context)? { - return Ok(true); - } - - // 11. Let targetProto be ? target.[[GetPrototypeOf]](). - let target_proto = target.__get_prototype_of__(context)?; - - // 12. If SameValue(V, targetProto) is false, throw a TypeError exception. - if val != target_proto { - return Err(JsNativeError::typ() - .with_message("Proxy trap failed to set prototype") - .into()); - } - - // 13. Return true. - Ok(true) -} - -/// `10.5.3 [[IsExtensible]] ( )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-isextensible -pub(crate) fn proxy_exotic_is_extensible(obj: &JsObject, context: &mut Context) -> JsResult { - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "isExtensible"). - let Some(trap) = handler.get_method(utf16!("isExtensible"), context)? else { - // 6. If trap is undefined, then - // a. Return ? IsExtensible(target). - return target.is_extensible(context); - }; - - // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)). - let boolean_trap_result = trap - .call(&handler.into(), &[target.clone().into()], context)? - .to_boolean(); - - // 8. Let targetResult be ? IsExtensible(target). - let target_result = target.is_extensible(context)?; - - // 9. If SameValue(booleanTrapResult, targetResult) is false, throw a TypeError exception. - if boolean_trap_result != target_result { - return Err(JsNativeError::typ() - .with_message("Proxy trap returned unexpected extensible value") - .into()); - } - - // 10. Return booleanTrapResult. - Ok(boolean_trap_result) -} - -/// `10.5.4 [[PreventExtensions]] ( )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-preventextensions -pub(crate) fn proxy_exotic_prevent_extensions( - obj: &JsObject, - context: &mut Context, -) -> JsResult { - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "preventExtensions"). - let Some(trap) = handler.get_method(utf16!("preventExtensions"), context)? else { - // 6. If trap is undefined, then - // a. Return ? target.[[PreventExtensions]](). - return target.__prevent_extensions__(context); - }; - - // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target »)). - let boolean_trap_result = trap - .call(&handler.into(), &[target.clone().into()], context)? - .to_boolean(); - - // 8. If booleanTrapResult is true, then - if boolean_trap_result && target.is_extensible(context)? { - // a. Let extensibleTarget be ? IsExtensible(target). - // b. If extensibleTarget is true, throw a TypeError exception. - return Err(JsNativeError::typ() - .with_message("Proxy trap failed to set extensible") - .into()); - } - - // 9. Return booleanTrapResult. - Ok(boolean_trap_result) -} - -/// `10.5.5 [[GetOwnProperty]] ( P )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-getownproperty-p -pub(crate) fn proxy_exotic_get_own_property( - obj: &JsObject, - key: &PropertyKey, - context: &mut InternalMethodContext<'_>, -) -> JsResult> { - context.slot().attributes |= SlotAttributes::NOT_CACHABLE; - - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "getOwnPropertyDescriptor"). - let Some(trap) = handler.get_method(utf16!("getOwnPropertyDescriptor"), context)? else { - // 6. If trap is undefined, then - // a. Return ? target.[[GetOwnProperty]](P). - - return target.__get_own_property__(key, context); - }; - - // 7. Let trapResultObj be ? Call(trap, handler, « target, P »). - let trap_result_obj = trap.call( - &handler.into(), - &[target.clone().into(), key.clone().into()], - context, - )?; - - // 8. If Type(trapResultObj) is neither Object nor Undefined, throw a TypeError exception. - if !trap_result_obj.is_object() && !trap_result_obj.is_undefined() { - return Err(JsNativeError::typ() - .with_message("Proxy trap result is neither object nor undefined") - .into()); - } - - // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). - let target_desc = target.__get_own_property__(key, context)?; - - // 10. If trapResultObj is undefined, then - if trap_result_obj.is_undefined() { - if let Some(desc) = target_desc { - // b. If targetDesc.[[Configurable]] is false, throw a TypeError exception. - if !desc.expect_configurable() { - return Err(JsNativeError::typ() - .with_message( - "Proxy trap result is undefined adn target result is not configurable", - ) - .into()); - } - - // c. Let extensibleTarget be ? IsExtensible(target). - // d. If extensibleTarget is false, throw a TypeError exception. - if !target.is_extensible(context)? { - return Err(JsNativeError::typ() - .with_message("Proxy trap result is undefined and target is not extensible") - .into()); - } - // e. Return undefined. - return Ok(None); - } - - // a. If targetDesc is undefined, return undefined. - return Ok(None); - } - - // 11. Let extensibleTarget be ? IsExtensible(target). - let extensible_target = target.is_extensible(context)?; - - // 12. Let resultDesc be ? ToPropertyDescriptor(trapResultObj). - let result_desc = trap_result_obj.to_property_descriptor(context)?; - - // 13. Call CompletePropertyDescriptor(resultDesc). - let result_desc = result_desc.complete_property_descriptor(); - - // 14. Let valid be IsCompatiblePropertyDescriptor(extensibleTarget, resultDesc, targetDesc). - // 15. If valid is false, throw a TypeError exception. - if !super::is_compatible_property_descriptor( - extensible_target, - result_desc.clone(), - target_desc.clone(), - ) { - return Err(JsNativeError::typ() - .with_message("Proxy trap returned unexpected property") - .into()); - } - - // 16. If resultDesc.[[Configurable]] is false, then - if !result_desc.expect_configurable() { - // a. If targetDesc is undefined or targetDesc.[[Configurable]] is true, then - match &target_desc { - Some(desc) if !desc.expect_configurable() => { - // b. If resultDesc has a [[Writable]] field and resultDesc.[[Writable]] is false, then - if result_desc.writable() == Some(false) { - // i. If targetDesc.[[Writable]] is true, throw a TypeError exception. - if desc.expect_writable() { - return - Err(JsNativeError::typ().with_message("Proxy trap result is writable and not configurable while target result is not configurable").into()) - ; - } - } - } - // i. Throw a TypeError exception. - _ => { - return Err(JsNativeError::typ() - .with_message( - "Proxy trap result is not configurable and target result is undefined", - ) - .into()) - } - } - } - - // 17. Return resultDesc. - Ok(Some(result_desc)) -} - -/// `10.5.6 [[DefineOwnProperty]] ( P, Desc )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-defineownproperty-p-desc -pub(crate) fn proxy_exotic_define_own_property( - obj: &JsObject, - key: &PropertyKey, - desc: PropertyDescriptor, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - context.slot().attributes |= SlotAttributes::NOT_CACHABLE; - - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "defineProperty"). - let Some(trap) = handler.get_method(utf16!("defineProperty"), context)? else { - // 6. If trap is undefined, then - // a. Return ? target.[[DefineOwnProperty]](P, Desc). - return target.__define_own_property__(key, desc, context); - }; - - // 7. Let descObj be FromPropertyDescriptor(Desc). - let desc_obj = Object::from_property_descriptor(Some(desc.clone()), context); - - // 8. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, descObj »)). - // 9. If booleanTrapResult is false, return false. - if !trap - .call( - &handler.into(), - &[target.clone().into(), key.clone().into(), desc_obj], - context, - )? - .to_boolean() - { - return Ok(false); - } - - // 10. Let targetDesc be ? target.[[GetOwnProperty]](P). - let target_desc = target.__get_own_property__(key, context)?; - - // 11. Let extensibleTarget be ? IsExtensible(target). - let extensible_target = target.is_extensible(context)?; - - // 12. If Desc has a [[Configurable]] field and if Desc.[[Configurable]] is false, then - let setting_config_false = matches!(desc.configurable(), Some(false)); - - match target_desc { - // 14. If targetDesc is undefined, then - None => { - // a. If extensibleTarget is false, throw a TypeError exception. - if !extensible_target { - return Err(JsNativeError::typ() - .with_message("Proxy trap failed to set property") - .into()); - } - - // b. If settingConfigFalse is true, throw a TypeError exception. - if setting_config_false { - return Err(JsNativeError::typ() - .with_message("Proxy trap failed to set property") - .into()); - } - } - // 15. Else, - Some(target_desc) => { - // a. If IsCompatiblePropertyDescriptor(extensibleTarget, Desc, targetDesc) is false, throw a TypeError exception. - if !super::is_compatible_property_descriptor( - extensible_target, - desc.clone(), - Some(target_desc.clone()), - ) { - return Err(JsNativeError::typ() - .with_message("Proxy trap set property to unexpected value") - .into()); - } - - // b. If settingConfigFalse is true and targetDesc.[[Configurable]] is true, throw a TypeError exception. - if setting_config_false && target_desc.expect_configurable() { - return Err(JsNativeError::typ() - .with_message("Proxy trap set property with unexpected configurable field") - .into()); - } - - // c. If IsDataDescriptor(targetDesc) is true, targetDesc.[[Configurable]] is false, and targetDesc.[[Writable]] is true, then - if target_desc.is_data_descriptor() - && !target_desc.expect_configurable() - && target_desc.expect_writable() - { - // i. If Desc has a [[Writable]] field and Desc.[[Writable]] is false, throw a TypeError exception. - if let Some(writable) = desc.writable() { - if !writable { - return Err(JsNativeError::typ() - .with_message("Proxy trap set property with unexpected writable field") - .into()); - } - } - } - } - } - - // 16. Return true. - Ok(true) -} - -/// `10.5.7 [[HasProperty]] ( P )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-hasproperty-p -pub(crate) fn proxy_exotic_has_property( - obj: &JsObject, - key: &PropertyKey, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - context.slot().attributes |= SlotAttributes::NOT_CACHABLE; - - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "has"). - let Some(trap) = handler.get_method(utf16!("has"), context)? else { - // 6. If trap is undefined, then - // a. Return ? target.[[HasProperty]](P). - return target.has_property(key.clone(), context); - }; - - // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)). - let boolean_trap_result = trap - .call( - &handler.into(), - &[target.clone().into(), key.clone().into()], - context, - )? - .to_boolean(); - - // 8. If booleanTrapResult is false, then - if !boolean_trap_result { - // a. Let targetDesc be ? target.[[GetOwnProperty]](P). - let target_desc = target.__get_own_property__(key, context)?; - - // b. If targetDesc is not undefined, then - if let Some(target_desc) = target_desc { - // i. If targetDesc.[[Configurable]] is false, throw a TypeError exception. - if !target_desc.expect_configurable() { - return Err(JsNativeError::typ() - .with_message("Proxy trap returned unexpected property") - .into()); - } - - // ii. Let extensibleTarget be ? IsExtensible(target). - // iii. If extensibleTarget is false, throw a TypeError exception. - if !target.is_extensible(context)? { - return Err(JsNativeError::typ() - .with_message("Proxy trap returned unexpected property") - .into()); - } - } - } - - // 9. Return booleanTrapResult. - Ok(boolean_trap_result) -} - -/// `10.5.8 [[Get]] ( P, Receiver )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-get-p-receiver -pub(crate) fn proxy_exotic_get( - obj: &JsObject, - key: &PropertyKey, - receiver: JsValue, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - // Proxy object can't be cached. - context.slot().attributes |= SlotAttributes::NOT_CACHABLE; - - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "get"). - let Some(trap) = handler.get_method(utf16!("get"), context)? else { - // 6. If trap is undefined, then - // a. Return ? target.[[Get]](P, Receiver). - return target.__get__(key, receiver, context); - }; - - // 7. Let trapResult be ? Call(trap, handler, « target, P, Receiver »). - let trap_result = trap.call( - &handler.into(), - &[target.clone().into(), key.clone().into(), receiver], - context, - )?; - - // 8. Let targetDesc be ? target.[[GetOwnProperty]](P). - let target_desc = target.__get_own_property__(key, context)?; - - // 9. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then - if let Some(target_desc) = target_desc { - if !target_desc.expect_configurable() { - // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then - if target_desc.is_data_descriptor() && !target_desc.expect_writable() { - // i. If SameValue(trapResult, targetDesc.[[Value]]) is false, throw a TypeError exception. - if !JsValue::same_value(&trap_result, target_desc.expect_value()) { - return Err(JsNativeError::typ() - .with_message("Proxy trap returned unexpected data descriptor") - .into()); - } - } - - // b. If IsAccessorDescriptor(targetDesc) is true and targetDesc.[[Get]] is undefined, then - if target_desc.is_accessor_descriptor() && target_desc.expect_get().is_undefined() { - // i. If trapResult is not undefined, throw a TypeError exception. - if !trap_result.is_undefined() { - return Err(JsNativeError::typ() - .with_message("Proxy trap returned unexpected accessor descriptor") - .into()); - } - } - } - } - - // 10. Return trapResult. - Ok(trap_result) -} - -/// `10.5.9 [[Set]] ( P, V, Receiver )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver -pub(crate) fn proxy_exotic_set( - obj: &JsObject, - key: PropertyKey, - value: JsValue, - receiver: JsValue, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - context.slot().attributes |= SlotAttributes::NOT_CACHABLE; - - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "set"). - let Some(trap) = handler.get_method(utf16!("set"), context)? else { - // 6. If trap is undefined, then - // a. Return ? target.[[Set]](P, V, Receiver). - return target.__set__(key, value, receiver, context); - }; - - // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P, V, Receiver »)). - // 8. If booleanTrapResult is false, return false. - if !trap - .call( - &handler.into(), - &[ - target.clone().into(), - key.clone().into(), - value.clone(), - receiver, - ], - context, - )? - .to_boolean() - { - return Ok(false); - } - - // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). - let target_desc = target.__get_own_property__(&key, context)?; - - // 10. If targetDesc is not undefined and targetDesc.[[Configurable]] is false, then - if let Some(target_desc) = target_desc { - if !target_desc.expect_configurable() { - // a. If IsDataDescriptor(targetDesc) is true and targetDesc.[[Writable]] is false, then - if target_desc.is_data_descriptor() && !target_desc.expect_writable() { - // i. If SameValue(V, targetDesc.[[Value]]) is false, throw a TypeError exception. - if !JsValue::same_value(&value, target_desc.expect_value()) { - return Err(JsNativeError::typ() - .with_message("Proxy trap set unexpected data descriptor") - .into()); - } - } - - // b. If IsAccessorDescriptor(targetDesc) is true, then - if target_desc.is_accessor_descriptor() { - // i. If targetDesc.[[Set]] is undefined, throw a TypeError exception. - match target_desc.set() { - None | Some(&JsValue::Undefined) => { - return Err(JsNativeError::typ() - .with_message("Proxy trap set unexpected accessor descriptor") - .into()); - } - _ => {} - } - } - } - } - - // 11. Return true. - Ok(true) -} - -/// `10.5.10 [[Delete]] ( P )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-delete-p -pub(crate) fn proxy_exotic_delete( - obj: &JsObject, - key: &PropertyKey, - context: &mut InternalMethodContext<'_>, -) -> JsResult { - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "deleteProperty"). - let Some(trap) = handler.get_method(utf16!("deleteProperty"), context)? else { - // 6. If trap is undefined, then - // a. Return ? target.[[Delete]](P). - return target.__delete__(key, context); - }; - - // 7. Let booleanTrapResult be ! ToBoolean(? Call(trap, handler, « target, P »)). - // 8. If booleanTrapResult is false, return false. - if !trap - .call( - &handler.into(), - &[target.clone().into(), key.clone().into()], - context, - )? - .to_boolean() - { - return Ok(false); - } - - // 9. Let targetDesc be ? target.[[GetOwnProperty]](P). - match target.__get_own_property__(key, context)? { - // 10. If targetDesc is undefined, return true. - None => return Ok(true), - // 11. If targetDesc.[[Configurable]] is false, throw a TypeError exception. - Some(target_desc) => { - if !target_desc.expect_configurable() { - return Err(JsNativeError::typ() - .with_message("Proxy trap failed to delete property") - .into()); - } - } - } - - // 12. Let extensibleTarget be ? IsExtensible(target). - // 13. If extensibleTarget is false, throw a TypeError exception. - if !target.is_extensible(context)? { - return Err(JsNativeError::typ() - .with_message("Proxy trap failed to delete property") - .into()); - } - - // 14. Return true. - Ok(true) -} - -/// `10.5.11 [[OwnPropertyKeys]] ( )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-ownpropertykeys -pub(crate) fn proxy_exotic_own_property_keys( - obj: &JsObject, - context: &mut Context, -) -> JsResult> { - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "ownKeys"). - let Some(trap) = handler.get_method(utf16!("ownKeys"), context)? else { - // 6. If trap is undefined, then - // a. Return ? target.[[OwnPropertyKeys]](). - return target.__own_property_keys__(context); - }; - - // 7. Let trapResultArray be ? Call(trap, handler, « target »). - let trap_result_array = trap.call(&handler.into(), &[target.clone().into()], context)?; - - // 8. Let trapResult be ? CreateListFromArrayLike(trapResultArray, « String, Symbol »). - let trap_result_raw = - trap_result_array.create_list_from_array_like(&[Type::String, Type::Symbol], context)?; - - // 9. If trapResult contains any duplicate entries, throw a TypeError exception. - let mut unchecked_result_keys: FxHashSet = FxHashSet::default(); - let mut trap_result = Vec::new(); - for value in &trap_result_raw { - match value { - JsValue::String(s) => { - if !unchecked_result_keys.insert(s.clone().into()) { - return Err(JsNativeError::typ() - .with_message("Proxy trap result contains duplicate string property keys") - .into()); - } - trap_result.push(s.clone().into()); - } - JsValue::Symbol(s) => { - if !unchecked_result_keys.insert(s.clone().into()) { - return Err(JsNativeError::typ() - .with_message("Proxy trap result contains duplicate symbol property keys") - .into()); - } - trap_result.push(s.clone().into()); - } - _ => {} - } - } - - // 10. Let extensibleTarget be ? IsExtensible(target). - let extensible_target = target.is_extensible(context)?; - - // 11. Let targetKeys be ? target.[[OwnPropertyKeys]](). - // 12. Assert: targetKeys is a List of property keys. - // 13. Assert: targetKeys contains no duplicate entries. - let target_keys = target.__own_property_keys__(context)?; - - // 14. Let targetConfigurableKeys be a new empty List. - // 15. Let targetNonconfigurableKeys be a new empty List. - let mut target_configurable_keys = Vec::new(); - let mut target_nonconfigurable_keys = Vec::new(); - - // 16. For each element key of targetKeys, do - for key in target_keys { - // a. Let desc be ? target.[[GetOwnProperty]](key). - match target.__get_own_property__(&key, &mut context.into())? { - // b. If desc is not undefined and desc.[[Configurable]] is false, then - Some(desc) if !desc.expect_configurable() => { - // i. Append key as an element of targetNonconfigurableKeys. - target_nonconfigurable_keys.push(key); - } - // c. Else, - _ => { - // i. Append key as an element of targetConfigurableKeys. - target_configurable_keys.push(key); - } - } - } - - // 17. If extensibleTarget is true and targetNonconfigurableKeys is empty, then - if extensible_target && target_nonconfigurable_keys.is_empty() { - // a. Return trapResult. - return Ok(trap_result); - } - - // 18. Let uncheckedResultKeys be a List whose elements are the elements of trapResult. - // 19. For each element key of targetNonconfigurableKeys, do - for key in target_nonconfigurable_keys { - // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. - // b. Remove key from uncheckedResultKeys. - if !unchecked_result_keys.remove(&key) { - return Err(JsNativeError::typ() - .with_message("Proxy trap failed to return all non-configurable property keys") - .into()); - } - } - - // 20. If extensibleTarget is true, return trapResult. - if extensible_target { - return Ok(trap_result); - } - - // 21. For each element key of targetConfigurableKeys, do - for key in target_configurable_keys { - // a. If key is not an element of uncheckedResultKeys, throw a TypeError exception. - // b. Remove key from uncheckedResultKeys. - if !unchecked_result_keys.remove(&key) { - return Err(JsNativeError::typ() - .with_message("Proxy trap failed to return all configurable property keys") - .into()); - } - } - - // 22. If uncheckedResultKeys is not empty, throw a TypeError exception. - if !unchecked_result_keys.is_empty() { - return Err(JsNativeError::typ() - .with_message("Proxy trap failed to return all property keys") - .into()); - } - - // 23. Return trapResult. - Ok(trap_result) -} - -/// `10.5.12 [[Call]] ( thisArgument, argumentsList )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist -fn proxy_exotic_call( - obj: &JsObject, - argument_count: usize, - context: &mut Context, -) -> JsResult { - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Let trap be ? GetMethod(handler, "apply"). - let Some(trap) = handler.get_method(utf16!("apply"), context)? else { - // 6. If trap is undefined, then - // a. Return ? Call(target, thisArgument, argumentsList). - return Ok(target.__call__(argument_count)); - }; - - let args = context.vm.pop_n_values(argument_count); - - // 7. Let argArray be ! CreateArrayFromList(argumentsList). - let arg_array = array::Array::create_array_from_list(args, context); - - // 8. Return ? Call(trap, handler, « target, thisArgument, argArray »). - let _func = context.vm.pop(); - let this = context.vm.pop(); - - context.vm.push(handler); // This - context.vm.push(trap.clone()); // Function - - context.vm.push(target); - context.vm.push(this); - context.vm.push(arg_array); - Ok(trap.__call__(3)) -} - -/// `[[Construct]] ( argumentsList, newTarget )` -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget -fn proxy_exotic_construct( - obj: &JsObject, - argument_count: usize, - context: &mut Context, -) -> JsResult { - // 1. Let handler be O.[[ProxyHandler]]. - // 2. If handler is null, throw a TypeError exception. - // 3. Assert: Type(handler) is Object. - // 4. Let target be O.[[ProxyTarget]]. - let (target, handler) = obj - .borrow() - .as_proxy() - .expect("Proxy object internal internal method called on non-proxy object") - .try_data()?; - - // 5. Assert: IsConstructor(target) is true. - assert!(target.is_constructor()); - - // 6. Let trap be ? GetMethod(handler, "construct"). - let Some(trap) = handler.get_method(utf16!("construct"), context)? else { - // 7. If trap is undefined, then - // a. Return ? Construct(target, argumentsList, newTarget). - return Ok(target.__construct__(argument_count)); - }; - - let new_target = context.vm.pop(); - let args = context.vm.pop_n_values(argument_count); - let _func = context.vm.pop(); - - // 8. Let argArray be ! CreateArrayFromList(argumentsList). - let arg_array = array::Array::create_array_from_list(args, context); - - // 9. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »). - let new_obj = trap.call( - &handler.into(), - &[target.into(), arg_array.into(), new_target], - context, - )?; - - // 10. If Type(newObj) is not Object, throw a TypeError exception. - let new_obj = new_obj.as_object().cloned().ok_or_else(|| { - JsNativeError::typ().with_message("Proxy trap constructor returned non-object value") - })?; - - // 11. Return newObj. - context.vm.push(new_obj); - Ok(CallValue::Complete) -} diff --git a/boa_engine/src/object/internal_methods/string.rs b/boa_engine/src/object/internal_methods/string.rs index 3876478141c..e2c5809e7e2 100644 --- a/boa_engine/src/object/internal_methods/string.rs +++ b/boa_engine/src/object/internal_methods/string.rs @@ -1,24 +1,24 @@ use crate::{ js_string, - object::JsObject, + object::{JsData, JsObject}, property::{PropertyDescriptor, PropertyKey}, - Context, JsResult, + Context, JsResult, JsString, }; use super::{InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; -/// Definitions of the internal object methods for string exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string-exotic-objects -pub(crate) static STRING_EXOTIC_INTERNAL_METHODS: InternalObjectMethods = InternalObjectMethods { - __get_own_property__: string_exotic_get_own_property, - __define_own_property__: string_exotic_define_own_property, - __own_property_keys__: string_exotic_own_property_keys, - ..ORDINARY_INTERNAL_METHODS -}; +impl JsData for JsString { + fn internal_methods(&self) -> &'static InternalObjectMethods { + static METHODS: InternalObjectMethods = InternalObjectMethods { + __get_own_property__: string_exotic_get_own_property, + __define_own_property__: string_exotic_define_own_property, + __own_property_keys__: string_exotic_own_property_keys, + ..ORDINARY_INTERNAL_METHODS + }; + + &METHODS + } +} /// Gets own property of 'String' exotic object /// @@ -92,8 +92,9 @@ pub(crate) fn string_exotic_own_property_keys( // 2. Let str be O.[[StringData]]. // 3. Assert: Type(str) is String. let string = obj - .as_string() - .expect("string exotic method should only be callable from string objects"); + .downcast_ref::() + .expect("string exotic method should only be callable from string objects") + .clone(); // 4. Let len be the length of str. let len = string.len(); @@ -150,9 +151,9 @@ fn string_get_own_property(obj: &JsObject, key: &PropertyKey) -> Option() + .expect("string exotic method should only be callable from string objects") + .clone(); // 10. Let len be the length of str. // 11. If ℝ(index) < 0 or len ≤ ℝ(index), return undefined. diff --git a/boa_engine/src/object/shape/shared_shape/template.rs b/boa_engine/src/object/shape/shared_shape/template.rs index 14316cd21fb..2f04618e4ac 100644 --- a/boa_engine/src/object/shape/shared_shape/template.rs +++ b/boa_engine/src/object/shape/shared_shape/template.rs @@ -2,7 +2,7 @@ use boa_gc::{Finalize, Trace}; use thin_vec::ThinVec; use crate::{ - object::{shape::slot::SlotAttributes, JsObject, Object, ObjectData, PropertyMap}, + object::{shape::slot::SlotAttributes, JsObject, NativeObject, Object, PropertyMap}, property::{Attribute, PropertyKey}, JsValue, }; @@ -104,9 +104,11 @@ impl ObjectTemplate { /// Create an object from the [`ObjectTemplate`] /// /// The storage must match the properties provided. - pub(crate) fn create(&self, data: ObjectData, storage: Vec) -> JsObject { + pub(crate) fn create(&self, data: T, storage: Vec) -> JsObject { + let internal_methods = data.internal_methods(); + let mut object = Object { - kind: data.kind, + data, extensible: true, properties: PropertyMap::new(self.shape.clone().into(), ThinVec::default()), private_elements: ThinVec::new(), @@ -114,21 +116,22 @@ impl ObjectTemplate { object.properties.storage = storage; - JsObject::from_object_and_vtable(object, data.internal_methods) + JsObject::from_object_and_vtable(object, internal_methods) } /// Create an object from the [`ObjectTemplate`] /// /// The storage must match the properties provided. It does not apply to /// the indexed propeties. - pub(crate) fn create_with_indexed_properties( + pub(crate) fn create_with_indexed_properties( &self, - data: ObjectData, + data: T, storage: Vec, elements: ThinVec, ) -> JsObject { + let internal_methods = data.internal_methods(); let mut object = Object { - kind: data.kind, + data, extensible: true, properties: PropertyMap::new(self.shape.clone().into(), elements), private_elements: ThinVec::new(), @@ -136,6 +139,6 @@ impl ObjectTemplate { object.properties.storage = storage; - JsObject::from_object_and_vtable(object, data.internal_methods) + JsObject::from_object_and_vtable(object, internal_methods) } } diff --git a/boa_engine/src/string/mod.rs b/boa_engine/src/string/mod.rs index 7483a0897e9..425f4d1449f 100644 --- a/boa_engine/src/string/mod.rs +++ b/boa_engine/src/string/mod.rs @@ -28,7 +28,7 @@ use crate::{ tagged::{Tagged, UnwrappedTagged}, JsBigInt, }; -use boa_gc::{empty_trace, Finalize, Trace}; +use boa_gc::{Finalize, Trace}; pub use boa_macros::utf16; use std::{ @@ -204,7 +204,9 @@ const DATA_OFFSET: usize = std::mem::size_of::(); /// /// [`JsString`] implements [Deref], inheriting all of /// \[u16\]'s methods. -#[derive(Finalize)] +#[derive(Trace, Finalize)] +// Safety: `JsString` does not contain any objects which needs to be traced, so this is safe. +#[boa_gc(unsafe_empty_trace)] pub struct JsString { ptr: Tagged, } @@ -212,11 +214,6 @@ pub struct JsString { // JsString should always be pointer sized. sa::assert_eq_size!(JsString, *const ()); -// Safety: `JsString` does not contain any objects which needs to be traced, so this is safe. -unsafe impl Trace for JsString { - empty_trace!(); -} - impl JsString { /// Obtains the underlying [`&[u16]`][slice] slice of a [`JsString`] #[must_use] diff --git a/boa_engine/src/symbol.rs b/boa_engine/src/symbol.rs index 7514a69088c..06c6590ec05 100644 --- a/boa_engine/src/symbol.rs +++ b/boa_engine/src/symbol.rs @@ -25,9 +25,9 @@ use crate::{ js_string, string::{common::StaticJsStrings, utf16}, tagged::{Tagged, UnwrappedTagged}, - JsString, + JsData, JsString, }; -use boa_gc::{empty_trace, Finalize, Trace}; +use boa_gc::{Finalize, Trace}; use num_enum::{IntoPrimitive, TryFromPrimitive}; @@ -131,6 +131,10 @@ struct Inner { } /// This represents a JavaScript symbol primitive. +#[derive(Trace, Finalize, JsData)] +// Safety: JsSymbol does not contain any objects which needs to be traced, +// so this is safe. +#[boa_gc(unsafe_empty_trace)] pub struct JsSymbol { repr: Tagged, } @@ -140,14 +144,6 @@ unsafe impl Send for JsSymbol {} // SAFETY: `JsSymbol` uses `Arc` to do the reference counting, making this type thread-safe. unsafe impl Sync for JsSymbol {} -impl Finalize for JsSymbol {} - -// Safety: JsSymbol does not contain any objects which needs to be traced, -// so this is safe. -unsafe impl Trace for JsSymbol { - empty_trace!(); -} - macro_rules! well_known_symbols { ( $( $(#[$attr:meta])* ($name:ident, $variant:path) ),+$(,)? ) => { $( diff --git a/boa_engine/src/value/display.rs b/boa_engine/src/value/display.rs index 1cdff70f63a..6bf058127a6 100644 --- a/boa_engine/src/value/display.rs +++ b/boa_engine/src/value/display.rs @@ -1,8 +1,13 @@ use std::borrow::Cow; use crate::{ - builtins::promise::PromiseState, object::ObjectKind, property::PropertyDescriptor, - string::utf16, JsError, JsString, + builtins::{ + error::ErrorKind, map::ordered_map::OrderedMap, promise::PromiseState, + set::ordered_set::OrderedSet, Array, Promise, + }, + property::PropertyDescriptor, + string::utf16, + JsError, JsString, }; use super::{fmt, Display, HashSet, JsValue}; @@ -100,149 +105,143 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children JsValue::Object(ref v) => { // Can use the private "type" field of an Object to match on // which type of Object it represents for special printing - match v.borrow().kind() { - ObjectKind::String(ref string) => { - format!("String {{ \"{}\" }}", string.to_std_string_escaped()) - } - ObjectKind::Boolean(boolean) => format!("Boolean {{ {boolean} }}"), - ObjectKind::Number(rational) => { - if rational.is_sign_negative() && *rational == 0.0 { - "Number { -0 }".to_string() - } else { - let mut buffer = ryu_js::Buffer::new(); - format!("Number {{ {} }}", buffer.format(*rational)) - } + let v_bor = v.borrow(); + if let Some(s) = v_bor.downcast_ref::() { + format!("String {{ \"{}\" }}", s.to_std_string_escaped()) + } else if let Some(b) = v_bor.downcast_ref::() { + format!("Boolean {{ {b} }}") + } else if let Some(r) = v_bor.downcast_ref::() { + if r.is_sign_negative() && *r == 0.0 { + "Number { -0 }".to_string() + } else { + let mut buffer = ryu_js::Buffer::new(); + format!("Number {{ {} }}", buffer.format(*r)) } - ObjectKind::Array => { - let len = v - .borrow() - .properties() - .get(&utf16!("length").into()) - .expect("array object must have 'length' property") - // FIXME: handle accessor descriptors - .expect_value() - .as_number() - .map(|n| n as i32) - .unwrap_or_default(); + } else if v_bor.is::() { + let len = v_bor + .properties() + .get(&utf16!("length").into()) + .expect("array object must have 'length' property") + // FIXME: handle accessor descriptors + .expect_value() + .as_number() + .map(|n| n as i32) + .unwrap_or_default(); - if print_children { - if len == 0 { - return String::from("[]"); - } + if print_children { + if len == 0 { + return String::from("[]"); + } - let arr = (0..len) - .map(|i| { - // Introduce recursive call to stringify any objects - // which are part of the Array + let arr = (0..len) + .map(|i| { + // Introduce recursive call to stringify any objects + // which are part of the Array - // FIXME: handle accessor descriptors - if let Some(value) = v - .borrow() - .properties() - .get(&i.into()) - .and_then(|x| x.value().cloned()) - { - log_string_from(&value, print_internals, false) - } else { - String::from("") - } - }) - .collect::>() - .join(", "); + // FIXME: handle accessor descriptors + if let Some(value) = v_bor + .properties() + .get(&i.into()) + .and_then(|x| x.value().cloned()) + { + log_string_from(&value, print_internals, false) + } else { + String::from("") + } + }) + .collect::>() + .join(", "); - format!("[ {arr} ]") - } else { - format!("Array({len})") - } + format!("[ {arr} ]") + } else { + format!("Array({len})") } - ObjectKind::Map(ref map) => { - let size = map.len(); - if size == 0 { - return String::from("Map(0)"); - } - - if print_children { - let mappings = map - .iter() - .map(|(key, value)| { - let key = log_string_from(key, print_internals, false); - let value = log_string_from(value, print_internals, false); - format!("{key} → {value}") - }) - .collect::>() - .join(", "); - format!("Map {{ {mappings} }}") - } else { - format!("Map({size})") - } + } else if let Some(map) = v_bor.downcast_ref::>() { + let size = map.len(); + if size == 0 { + return String::from("Map(0)"); } - ObjectKind::Set(ref set) => { - let size = set.len(); - if size == 0 { - return String::from("Set(0)"); - } + if print_children { + let mappings = map + .iter() + .map(|(key, value)| { + let key = log_string_from(key, print_internals, false); + let value = log_string_from(value, print_internals, false); + format!("{key} → {value}") + }) + .collect::>() + .join(", "); + format!("Map {{ {mappings} }}") + } else { + format!("Map({size})") + } + } else if let Some(set) = v_bor.downcast_ref::() { + let size = set.len(); - if print_children { - let entries = set - .iter() - .map(|value| log_string_from(value, print_internals, false)) - .collect::>() - .join(", "); - format!("Set {{ {entries} }}") - } else { - format!("Set({size})") - } + if size == 0 { + return String::from("Set(0)"); } - ObjectKind::Error(_) => { - let name: Cow<'static, str> = v - .get_property(&utf16!("name").into()) - .as_ref() - .and_then(PropertyDescriptor::value) - .map_or_else( - || "".into(), - |v| { - v.as_string() - .map_or_else( - || v.display().to_string(), - JsString::to_std_string_escaped, - ) - .into() - }, - ); - let message = v - .get_property(&utf16!("message").into()) - .as_ref() - .and_then(PropertyDescriptor::value) - .map(|v| { - v.as_string().map_or_else( - || v.display().to_string(), - JsString::to_std_string_escaped, - ) - }) - .unwrap_or_default(); - if name.is_empty() { - message - } else if message.is_empty() { - name.to_string() - } else { - format!("{name}: {message}") - } + + if print_children { + let entries = set + .iter() + .map(|value| log_string_from(value, print_internals, false)) + .collect::>() + .join(", "); + format!("Set {{ {entries} }}") + } else { + format!("Set({size})") } - ObjectKind::Promise(ref promise) => { - format!( - "Promise {{ {} }}", - match promise.state() { - PromiseState::Pending => Cow::Borrowed(""), - PromiseState::Fulfilled(val) => Cow::Owned(val.display().to_string()), - PromiseState::Rejected(reason) => Cow::Owned(format!( - " {}", - JsError::from_opaque(reason.clone()) - )), - } - ) + } else if v_bor.is::() { + drop(v_bor); + let name: Cow<'static, str> = v + .get_property(&utf16!("name").into()) + .as_ref() + .and_then(PropertyDescriptor::value) + .map_or_else( + || "".into(), + |v| { + v.as_string() + .map_or_else( + || v.display().to_string(), + JsString::to_std_string_escaped, + ) + .into() + }, + ); + let message = v + .get_property(&utf16!("message").into()) + .as_ref() + .and_then(PropertyDescriptor::value) + .map(|v| { + v.as_string().map_or_else( + || v.display().to_string(), + JsString::to_std_string_escaped, + ) + }) + .unwrap_or_default(); + if name.is_empty() { + message + } else if message.is_empty() { + name.to_string() + } else { + format!("{name}: {message}") } - _ => x.display_obj(print_internals), + } else if let Some(promise) = v_bor.downcast_ref::() { + format!( + "Promise {{ {} }}", + match promise.state() { + PromiseState::Pending => Cow::Borrowed(""), + PromiseState::Fulfilled(val) => Cow::Owned(val.display().to_string()), + PromiseState::Rejected(reason) => Cow::Owned(format!( + " {}", + JsError::from_opaque(reason.clone()) + )), + } + ) + } else { + x.display_obj(print_internals) } } _ => x.display().to_string(), @@ -255,9 +254,9 @@ impl JsValue { pub fn display_obj(&self, print_internals: bool) -> String { // A simple helper for getting the address of a value // TODO: Find a more general place for this, as it can be used in other situations as well - fn address_of(t: &T) -> usize { + fn address_of(t: &T) -> usize { let my_ptr: *const T = t; - my_ptr as usize + my_ptr.cast::<()>() as usize } fn display_obj_internal( diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index d27c815e242..b3973e4dd6b 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -16,11 +16,11 @@ mod tests; use crate::{ builtins::{ number::{f64_to_int32, f64_to_uint32}, - Number, + Number, Promise, }, error::JsNativeError, js_string, - object::{JsObject, ObjectData}, + object::JsObject, property::{PropertyDescriptor, PropertyKey}, symbol::JsSymbol, Context, JsBigInt, JsResult, JsString, @@ -186,14 +186,14 @@ impl JsValue { #[inline] #[must_use] pub fn is_promise(&self) -> bool { - matches!(self, Self::Object(obj) if obj.is_promise()) + matches!(self, Self::Object(obj) if obj.is::()) } /// Returns the promise if the value is a promise, otherwise `None`. #[inline] #[must_use] pub fn as_promise(&self) -> Option<&JsObject> { - self.as_object().filter(|obj| obj.is_promise()) + self.as_object().filter(|obj| obj.is::()) } /// Returns true if the value is a symbol. @@ -501,31 +501,32 @@ impl JsValue { .intrinsics() .templates() .boolean() - .create(ObjectData::boolean(*boolean), Vec::default())), + .create(*boolean, Vec::default())), Self::Integer(integer) => Ok(context .intrinsics() .templates() .number() - .create(ObjectData::number(f64::from(*integer)), Vec::default())), + .create(f64::from(*integer), Vec::default())), Self::Rational(rational) => Ok(context .intrinsics() .templates() .number() - .create(ObjectData::number(*rational), Vec::default())), - Self::String(ref string) => Ok(context.intrinsics().templates().string().create( - ObjectData::string(string.clone()), - vec![string.len().into()], - )), + .create(*rational, Vec::default())), + Self::String(ref string) => Ok(context + .intrinsics() + .templates() + .string() + .create(string.clone(), vec![string.len().into()])), Self::Symbol(ref symbol) => Ok(context .intrinsics() .templates() .symbol() - .create(ObjectData::symbol(symbol.clone()), Vec::default())), + .create(symbol.clone(), Vec::default())), Self::BigInt(ref bigint) => Ok(context .intrinsics() .templates() .bigint() - .create(ObjectData::big_int(bigint.clone()), Vec::default())), + .create(bigint.clone(), Vec::default())), Self::Object(jsobject) => Ok(jsobject.clone()), } } From 3be8db5faca2784962fdd327cf2366a906f5fd95 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 04:17:39 -0600 Subject: [PATCH 05/15] Migrate VM to erased objects --- boa_engine/src/vm/code_block.rs | 32 +++++------- boa_engine/src/vm/opcode/arguments.rs | 6 +-- boa_engine/src/vm/opcode/await/mod.rs | 11 ++-- .../src/vm/opcode/define/class/getter.rs | 38 +++++++------- .../src/vm/opcode/define/class/method.rs | 38 +++++++------- .../src/vm/opcode/define/class/setter.rs | 38 +++++++------- boa_engine/src/vm/opcode/environment/mod.rs | 14 +++-- boa_engine/src/vm/opcode/generator/mod.rs | 51 ++++++++++--------- .../src/vm/opcode/generator/yield_stm.rs | 5 +- boa_engine/src/vm/opcode/push/array.rs | 3 +- boa_engine/src/vm/opcode/push/class/field.rs | 28 +++++----- .../src/vm/opcode/push/class/private.rs | 11 ++-- boa_engine/src/vm/opcode/push/environment.rs | 6 +-- boa_engine/src/vm/opcode/push/object.rs | 4 +- .../src/vm/opcode/set/class_prototype.rs | 15 +++--- boa_engine/src/vm/opcode/set/home_object.rs | 16 +++--- boa_engine/src/vm/opcode/set/property.rs | 4 +- 17 files changed, 152 insertions(+), 168 deletions(-) diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 1ed1e1a7838..1ceec0f8ae7 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -3,11 +3,14 @@ //! This module is for the `CodeBlock` which implements a function representation in the VM use crate::{ - builtins::function::{OrdinaryFunction, ThisMode}, + builtins::{ + function::{OrdinaryFunction, ThisMode}, + OrdinaryObject, + }, environments::{BindingLocator, CompileTimeEnvironment}, object::{ shape::{slot::Slot, Shape, WeakShape}, - JsObject, ObjectData, + JsObject, }, Context, JsBigInt, JsString, JsValue, }; @@ -892,8 +895,6 @@ pub(crate) fn create_function_object( context.realm().clone(), ); - let data = ObjectData::ordinary_function(function, !is_async && !is_generator); - let templates = context.intrinsics().templates(); let (mut template, storage, constructor_prototype) = if is_generator { @@ -904,7 +905,7 @@ pub(crate) fn create_function_object( } else { context.intrinsics().objects().generator() }, - ObjectData::ordinary(), + OrdinaryObject, ); ( @@ -921,7 +922,7 @@ pub(crate) fn create_function_object( } else { let constructor_prototype = templates .function_prototype() - .create(ObjectData::ordinary(), vec![JsValue::undefined()]); + .create(OrdinaryObject, vec![JsValue::undefined()]); let template = templates.function_with_prototype_without_proto(); @@ -934,7 +935,7 @@ pub(crate) fn create_function_object( template.set_prototype(prototype); - let constructor = template.create(data, storage); + let constructor = template.create(function, storage); if let Some(constructor_prototype) = &constructor_prototype { constructor_prototype.borrow_mut().properties_mut().storage[0] = constructor.clone().into(); @@ -965,11 +966,6 @@ pub(crate) fn create_function_object_fast(code: Gc, context: &mut Con context.realm().clone(), ); - let data = ObjectData::ordinary_function( - function, - has_prototype_property && !is_async && !is_generator, - ); - if is_generator { let prototype = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), @@ -978,7 +974,7 @@ pub(crate) fn create_function_object_fast(code: Gc, context: &mut Con } else { context.intrinsics().objects().generator() }, - ObjectData::ordinary(), + OrdinaryObject, ); let template = if is_async { context.intrinsics().templates().async_generator_function() @@ -986,31 +982,31 @@ pub(crate) fn create_function_object_fast(code: Gc, context: &mut Con context.intrinsics().templates().generator_function() }; - template.create(data, vec![length, name, prototype.into()]) + template.create(function, vec![length, name, prototype.into()]) } else if is_async { context .intrinsics() .templates() .async_function() - .create(data, vec![length, name]) + .create(function, vec![length, name]) } else if !has_prototype_property { context .intrinsics() .templates() .function() - .create(data, vec![length, name]) + .create(function, vec![length, name]) } else { let prototype = context .intrinsics() .templates() .function_prototype() - .create(ObjectData::ordinary(), vec![JsValue::undefined()]); + .create(OrdinaryObject, vec![JsValue::undefined()]); let constructor = context .intrinsics() .templates() .function_with_prototype() - .create(data, vec![length, name, prototype.clone().into()]); + .create(function, vec![length, name, prototype.clone().into()]); prototype.borrow_mut().properties_mut().storage[0] = constructor.clone().into(); diff --git a/boa_engine/src/vm/opcode/arguments.rs b/boa_engine/src/vm/opcode/arguments.rs index c8cedd01d4f..2af9c57318f 100644 --- a/boa_engine/src/vm/opcode/arguments.rs +++ b/boa_engine/src/vm/opcode/arguments.rs @@ -1,5 +1,5 @@ use crate::{ - builtins::function::arguments::Arguments, + builtins::function::arguments::{MappedArguments, UnmappedArguments}, vm::{CallFrame, CompletionType}, Context, JsResult, }; @@ -30,7 +30,7 @@ impl Operation for CreateMappedArgumentsObject { let args = context.vm.stack[arguments_start..].to_vec(); let env = context.vm.environments.current(); - let arguments = Arguments::create_mapped_arguments_object( + let arguments = MappedArguments::new( &function_object, &code.params, &args, @@ -57,7 +57,7 @@ impl Operation for CreateUnmappedArgumentsObject { fn execute(context: &mut Context) -> JsResult { let arguments_start = context.vm.frame().fp as usize + CallFrame::FIRST_ARGUMENT_POSITION; let args = context.vm.stack[arguments_start..].to_vec(); - let arguments = Arguments::create_unmapped_arguments_object(&args, context); + let arguments = UnmappedArguments::new(&args, context); context.vm.push(arguments); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/await/mod.rs b/boa_engine/src/vm/opcode/await/mod.rs index c17ec585791..8f2cb5a53a5 100644 --- a/boa_engine/src/vm/opcode/await/mod.rs +++ b/boa_engine/src/vm/opcode/await/mod.rs @@ -1,7 +1,10 @@ use boa_gc::{Gc, GcRefCell}; use crate::{ - builtins::{generator::GeneratorContext, promise::PromiseCapability, Promise}, + builtins::{ + async_generator::AsyncGenerator, generator::GeneratorContext, promise::PromiseCapability, + Promise, + }, native_function::NativeFunction, object::FunctionObjectBuilder, vm::{opcode::Operation, CompletionType, GeneratorResumeKind}, @@ -58,8 +61,7 @@ impl Operation for Await { .and_then(|f| f.async_generator.clone()) { async_generator - .borrow_mut() - .as_async_generator_mut() + .downcast_mut::() .expect("must be async generator") .context = Some(gen); } @@ -102,8 +104,7 @@ impl Operation for Await { .and_then(|f| f.async_generator.clone()) { async_generator - .borrow_mut() - .as_async_generator_mut() + .downcast_mut::() .expect("must be async generator") .context = Some(gen); } diff --git a/boa_engine/src/vm/opcode/define/class/getter.rs b/boa_engine/src/vm/opcode/define/class/getter.rs index 306f08d4925..7eda9a7f14b 100644 --- a/boa_engine/src/vm/opcode/define/class/getter.rs +++ b/boa_engine/src/vm/opcode/define/class/getter.rs @@ -1,5 +1,5 @@ use crate::{ - builtins::function::set_function_name, + builtins::function::{set_function_name, OrdinaryFunction}, object::internal_methods::InternalMethodContext, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, @@ -29,11 +29,10 @@ impl DefineClassStaticGetterByName { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, Some(JsString::from("get")), context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class.clone()); } let set = class .__get_own_property__(&key, &mut InternalMethodContext::new(context))? @@ -98,11 +97,10 @@ impl DefineClassGetterByName { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, Some(JsString::from("get")), context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class_proto.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class_proto.clone()); } let set = class_proto .__get_own_property__(&key, &mut InternalMethodContext::new(context))? @@ -169,11 +167,10 @@ impl Operation for DefineClassStaticGetterByValue { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, Some(JsString::from("get")), context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class.clone()); } let set = class @@ -220,11 +217,10 @@ impl Operation for DefineClassGetterByValue { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, Some(JsString::from("get")), context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class_proto.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class_proto.clone()); } let set = class_proto .__get_own_property__(&key, &mut InternalMethodContext::new(context))? diff --git a/boa_engine/src/vm/opcode/define/class/method.rs b/boa_engine/src/vm/opcode/define/class/method.rs index 2f8768b3867..43b25043363 100644 --- a/boa_engine/src/vm/opcode/define/class/method.rs +++ b/boa_engine/src/vm/opcode/define/class/method.rs @@ -1,5 +1,5 @@ use crate::{ - builtins::function::set_function_name, + builtins::function::{set_function_name, OrdinaryFunction}, object::internal_methods::InternalMethodContext, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, @@ -29,11 +29,10 @@ impl DefineClassStaticMethodByName { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, None, context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class.clone()); } class.__define_own_property__( @@ -94,11 +93,10 @@ impl DefineClassMethodByName { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, None, context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class_proto.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class_proto.clone()); } class_proto.__define_own_property__( @@ -161,11 +159,10 @@ impl Operation for DefineClassStaticMethodByValue { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, None, context); - let mut function_object_mut = function_object.borrow_mut(); - let function_mut = function_object_mut - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class.clone()); } class.define_property_or_throw( @@ -207,11 +204,10 @@ impl Operation for DefineClassMethodByValue { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, None, context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class_proto.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class_proto.clone()); } class_proto.__define_own_property__( diff --git a/boa_engine/src/vm/opcode/define/class/setter.rs b/boa_engine/src/vm/opcode/define/class/setter.rs index b2277af6319..1a8cdbfe4d2 100644 --- a/boa_engine/src/vm/opcode/define/class/setter.rs +++ b/boa_engine/src/vm/opcode/define/class/setter.rs @@ -1,5 +1,5 @@ use crate::{ - builtins::function::set_function_name, + builtins::function::{set_function_name, OrdinaryFunction}, object::internal_methods::InternalMethodContext, property::PropertyDescriptor, vm::{opcode::Operation, CompletionType}, @@ -29,11 +29,10 @@ impl DefineClassStaticSetterByName { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, Some(JsString::from("set")), context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class.clone()); } let get = class .__get_own_property__(&key, &mut InternalMethodContext::new(context))? @@ -99,11 +98,10 @@ impl DefineClassSetterByName { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, Some(JsString::from("set")), context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class_proto.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class_proto.clone()); } let get = class_proto .__get_own_property__(&key, &mut InternalMethodContext::new(context))? @@ -172,11 +170,10 @@ impl Operation for DefineClassStaticSetterByValue { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, Some(JsString::from("set")), context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class.clone()); } let get = class .__get_own_property__(&key, &mut InternalMethodContext::new(context))? @@ -224,11 +221,10 @@ impl Operation for DefineClassSetterByValue { .as_object() .expect("method must be function object"); set_function_name(function_object, &key, Some(JsString::from("set")), context); - let mut function_object = function_object.borrow_mut(); - let function_mut = function_object - .as_function_mut() - .expect("method must be function object"); - function_mut.set_home_object(class_proto.clone()); + function_object + .downcast_mut::() + .expect("method must be function object") + .set_home_object(class_proto.clone()); } let get = class_proto .__get_own_property__(&key, &mut InternalMethodContext::new(context))? diff --git a/boa_engine/src/vm/opcode/environment/mod.rs b/boa_engine/src/vm/opcode/environment/mod.rs index 6331724dca2..937d8d67fa5 100644 --- a/boa_engine/src/vm/opcode/environment/mod.rs +++ b/boa_engine/src/vm/opcode/environment/mod.rs @@ -1,4 +1,5 @@ use crate::{ + builtins::function::OrdinaryFunction, error::JsNativeError, object::internal_methods::InternalMethodContext, vm::{opcode::Operation, CompletionType}, @@ -47,11 +48,14 @@ impl Operation for Super { let this = env .get_this_binding()? .expect("`get_this_environment` ensures this returns `Some`"); - let function_object = env.slots().function_object().borrow(); - let function = function_object - .as_function() - .expect("must be function object"); - function.get_home_object().or(this.as_object()).cloned() + + env.slots() + .function_object() + .downcast_ref::() + .expect("must be function object") + .get_home_object() + .or(this.as_object()) + .cloned() }; let value = home_object diff --git a/boa_engine/src/vm/opcode/generator/mod.rs b/boa_engine/src/vm/opcode/generator/mod.rs index 9579e15a1d0..5fce5d68329 100644 --- a/boa_engine/src/vm/opcode/generator/mod.rs +++ b/boa_engine/src/vm/opcode/generator/mod.rs @@ -8,7 +8,7 @@ use crate::{ generator::{GeneratorContext, GeneratorState}, }, error::JsNativeError, - object::{ObjectData, PROTOTYPE}, + object::PROTOTYPE, string::utf16, vm::{ call_frame::GeneratorResumeKind, @@ -82,28 +82,32 @@ impl Operation for Generator { Clone::clone, ); - let data = if r#async { - ObjectData::async_generator(AsyncGenerator { - state: AsyncGeneratorState::SuspendedStart, - context: Some(GeneratorContext::new(stack, call_frame)), - queue: VecDeque::new(), - }) + let generator = if r#async { + JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + proto, + AsyncGenerator { + state: AsyncGeneratorState::SuspendedStart, + context: Some(GeneratorContext::new(stack, call_frame)), + queue: VecDeque::new(), + }, + ) } else { - ObjectData::generator(crate::builtins::generator::Generator { - state: GeneratorState::SuspendedStart { - context: GeneratorContext::new(stack, call_frame), + JsObject::from_proto_and_data_with_shared_shape( + context.root_shape(), + proto, + crate::builtins::generator::Generator { + state: GeneratorState::SuspendedStart { + context: GeneratorContext::new(stack, call_frame), + }, }, - }) + ) }; - let generator = - JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, data); - if r#async { let gen_clone = generator.clone(); - let mut generator_mut = generator.borrow_mut(); - let gen = generator_mut - .as_async_generator_mut() + let mut gen = generator + .downcast_mut::() .expect("must be object here"); let gen_context = gen.context.as_mut().expect("must exist"); // TODO: try to move this to the context itself. @@ -140,19 +144,18 @@ impl Operation for AsyncGeneratorClose { .clone() .expect("There should be a object"); - let mut generator_object_mut = generator_object.borrow_mut(); - let generator = generator_object_mut - .as_async_generator_mut() + let mut gen = generator_object + .downcast_mut::() .expect("must be async generator"); - generator.state = AsyncGeneratorState::Completed; - generator.context = None; + gen.state = AsyncGeneratorState::Completed; + gen.context = None; - let next = generator + let next = gen .queue .pop_front() .expect("must have item in queue"); - drop(generator_object_mut); + drop(gen); let return_value = context.vm.get_return_value(); context.vm.set_return_value(JsValue::undefined()); diff --git a/boa_engine/src/vm/opcode/generator/yield_stm.rs b/boa_engine/src/vm/opcode/generator/yield_stm.rs index 97a2cb8aeb4..35dbfc923ed 100644 --- a/boa_engine/src/vm/opcode/generator/yield_stm.rs +++ b/boa_engine/src/vm/opcode/generator/yield_stm.rs @@ -46,8 +46,7 @@ impl Operation for AsyncGeneratorYield { .expect("`AsyncGeneratorYield` must only be called inside async generators"); let completion = Ok(value); let next = async_gen - .borrow_mut() - .as_async_generator_mut() + .downcast_mut::() .expect("must be async generator object") .queue .pop_front() @@ -58,7 +57,7 @@ impl Operation for AsyncGeneratorYield { let mut generator_object_mut = async_gen.borrow_mut(); let gen = generator_object_mut - .as_async_generator_mut() + .downcast_mut::() .expect("must be async generator object"); if let Some(next) = gen.queue.front() { diff --git a/boa_engine/src/vm/opcode/push/array.rs b/boa_engine/src/vm/opcode/push/array.rs index 8afbe2603a2..89b95e21394 100644 --- a/boa_engine/src/vm/opcode/push/array.rs +++ b/boa_engine/src/vm/opcode/push/array.rs @@ -1,6 +1,5 @@ use crate::{ builtins::Array, - object::ObjectData, string::utf16, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsValue, @@ -23,7 +22,7 @@ impl Operation for PushNewArray { .intrinsics() .templates() .array() - .create(ObjectData::array(), vec![JsValue::new(0)]); + .create(Array, vec![JsValue::new(0)]); context.vm.push(array); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/push/class/field.rs b/boa_engine/src/vm/opcode/push/class/field.rs index 5868b761c1a..de5d1bca209 100644 --- a/boa_engine/src/vm/opcode/push/class/field.rs +++ b/boa_engine/src/vm/opcode/push/class/field.rs @@ -1,4 +1,5 @@ use crate::{ + builtins::function::OrdinaryFunction, object::JsFunction, vm::{opcode::Operation, CompletionType}, Context, JsResult, @@ -25,17 +26,17 @@ impl Operation for PushClassField { let field_function_object = field_function_value .as_object() .expect("field value must be function object"); - let mut field_function_object_borrow = field_function_object.borrow_mut(); - let field_function = field_function_object_borrow - .as_function_mut() - .expect("field value must be function object"); let class_object = class_value .as_object() .expect("class must be function object"); - field_function.set_home_object(class_object.clone()); + + field_function_object + .downcast_mut::() + .expect("field value must be function object") + .set_home_object(class_object.clone()); + class_object - .borrow_mut() - .as_function_mut() + .downcast_mut::() .expect("class must be function object") .push_field( field_name_key, @@ -62,18 +63,17 @@ impl PushClassFieldPrivate { let field_function_object = field_function_value .as_object() .expect("field value must be function object"); - let mut field_function_object_borrow = field_function_object.borrow_mut(); - let field_function = field_function_object_borrow - .as_function_mut() - .expect("field value must be function object"); let class_object = class_value .as_object() .expect("class must be function object"); - field_function.set_home_object(class_object.clone()); + + field_function_object + .downcast_mut::() + .expect("field value must be function object") + .set_home_object(class_object.clone()); class_object - .borrow_mut() - .as_function_mut() + .downcast_mut::() .expect("class must be function object") .push_field_private( class_object.private_name(name), diff --git a/boa_engine/src/vm/opcode/push/class/private.rs b/boa_engine/src/vm/opcode/push/class/private.rs index ed0e1a77e0c..03afab35b4c 100644 --- a/boa_engine/src/vm/opcode/push/class/private.rs +++ b/boa_engine/src/vm/opcode/push/class/private.rs @@ -4,7 +4,7 @@ use crate::{ property::PropertyDescriptor, string::utf16, vm::{opcode::Operation, CompletionType}, - Context, JsResult, + Context, JsResult, builtins::function::OrdinaryFunction, }; /// `PushClassPrivateMethod` implements the Opcode Operation for `Opcode::PushClassPrivateMethod` @@ -40,8 +40,7 @@ impl PushClassPrivateMethod { let class_object = class.as_object().expect("class must be function object"); class_object - .borrow_mut() - .as_function_mut() + .downcast_mut::() .expect("class must be function object") .push_private_method( class_object.private_name(name), @@ -90,8 +89,7 @@ impl PushClassPrivateGetter { let class_object = class.as_object().expect("class must be function object"); class_object - .borrow_mut() - .as_function_mut() + .downcast_mut::() .expect("class must be function object") .push_private_method( class_object.private_name(name), @@ -143,8 +141,7 @@ impl PushClassPrivateSetter { let class_object = class.as_object().expect("class must be function object"); class_object - .borrow_mut() - .as_function_mut() + .downcast_mut::() .expect("class must be function object") .push_private_method( class_object.private_name(name), diff --git a/boa_engine/src/vm/opcode/push/environment.rs b/boa_engine/src/vm/opcode/push/environment.rs index 13eddef9661..ca2a5e2e643 100644 --- a/boa_engine/src/vm/opcode/push/environment.rs +++ b/boa_engine/src/vm/opcode/push/environment.rs @@ -1,4 +1,5 @@ use crate::{ + builtins::function::OrdinaryFunction, environments::PrivateEnvironment, vm::{opcode::Operation, CompletionType}, Context, JsResult, @@ -99,11 +100,10 @@ impl Operation for PushPrivateEnvironment { } let ptr: *const _ = class.as_ref(); - let environment = Gc::new(PrivateEnvironment::new(ptr as usize, names)); + let environment = Gc::new(PrivateEnvironment::new(ptr.cast::<()>() as usize, names)); class - .borrow_mut() - .as_function_mut() + .downcast_mut::() .expect("class object must be function") .push_private_environment(environment.clone()); context.vm.environments.push_private(environment); diff --git a/boa_engine/src/vm/opcode/push/object.rs b/boa_engine/src/vm/opcode/push/object.rs index c04eecd7fca..26297b79f25 100644 --- a/boa_engine/src/vm/opcode/push/object.rs +++ b/boa_engine/src/vm/opcode/push/object.rs @@ -1,5 +1,5 @@ use crate::{ - object::ObjectData, + builtins::OrdinaryObject, vm::{opcode::Operation, CompletionType}, Context, JsResult, }; @@ -21,7 +21,7 @@ impl Operation for PushEmptyObject { .intrinsics() .templates() .ordinary_object() - .create(ObjectData::ordinary(), Vec::default()); + .create(OrdinaryObject, Vec::default()); context.vm.push(o); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/set/class_prototype.rs b/boa_engine/src/vm/opcode/set/class_prototype.rs index a115bfc9a1b..9e7c96f3e98 100644 --- a/boa_engine/src/vm/opcode/set/class_prototype.rs +++ b/boa_engine/src/vm/opcode/set/class_prototype.rs @@ -1,10 +1,10 @@ use crate::{ object::{ - internal_methods::InternalMethodContext, JsObject, ObjectData, CONSTRUCTOR, PROTOTYPE, + internal_methods::InternalMethodContext, JsObject, CONSTRUCTOR, PROTOTYPE, }, property::PropertyDescriptorBuilder, vm::{opcode::Operation, CompletionType}, - Context, JsResult, JsValue, + Context, JsResult, JsValue, builtins::{function::OrdinaryFunction, OrdinaryObject}, }; /// `SetClassProtoType` implements the Opcode Operation for `Opcode::SetClassPrototype` @@ -32,7 +32,7 @@ impl Operation for SetClassPrototype { let proto = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::ordinary(), + OrdinaryObject, ); let class = context.vm.pop(); @@ -49,11 +49,10 @@ impl Operation for SetClassPrototype { context, ) .expect("cannot fail per spec"); - let mut class_object_mut = class_object.borrow_mut(); - let class_function = class_object_mut - .as_function_mut() - .expect("class must be function object"); - class_function.set_home_object(proto.clone()); + class_object + .downcast_mut::() + .expect("class must be function object") + .set_home_object(proto.clone()); } proto diff --git a/boa_engine/src/vm/opcode/set/home_object.rs b/boa_engine/src/vm/opcode/set/home_object.rs index b280c4ad4b1..bdbf68e9939 100644 --- a/boa_engine/src/vm/opcode/set/home_object.rs +++ b/boa_engine/src/vm/opcode/set/home_object.rs @@ -1,4 +1,5 @@ use crate::{ + builtins::function::OrdinaryFunction, vm::{opcode::Operation, CompletionType}, Context, JsResult, }; @@ -19,15 +20,12 @@ impl Operation for SetHomeObject { let function = context.vm.pop(); let home = context.vm.pop(); - { - let function_object = function.as_object().expect("must be object"); - let home_object = home.as_object().expect("must be object"); - let mut function_object_mut = function_object.borrow_mut(); - let function_mut = function_object_mut - .as_function_mut() - .expect("must be function object"); - function_mut.set_home_object(home_object.clone()); - } + function + .as_object() + .expect("must be object") + .downcast_mut::() + .expect("must be function object") + .set_home_object(home.as_object().expect("must be object").clone()); context.vm.push(home); context.vm.push(function); diff --git a/boa_engine/src/vm/opcode/set/property.rs b/boa_engine/src/vm/opcode/set/property.rs index 7ff827188f8..07ac703679c 100644 --- a/boa_engine/src/vm/opcode/set/property.rs +++ b/boa_engine/src/vm/opcode/set/property.rs @@ -1,7 +1,7 @@ use boa_macros::utf16; use crate::{ - builtins::function::set_function_name, + builtins::{function::set_function_name, Proxy}, object::{internal_methods::InternalMethodContext, shape::slot::SlotAttributes}, property::{PropertyDescriptor, PropertyKey}, vm::{opcode::Operation, CompletionType}, @@ -171,7 +171,7 @@ impl Operation for SetPropertyByValue { // because we have to the call prototypes [[set]] on non-existing property, // and proxy objects can override [[set]]. let prototype = shape.prototype(); - if prototype.map_or(false, |x| x.is_proxy()) { + if prototype.map_or(false, |x| x.is::()) { break 'fast_path; } From 3f3f730d62815b86bf1ca2d0361f2ed2c7c87ca4 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 04:18:03 -0600 Subject: [PATCH 06/15] Migrate object wrappers to erased objects --- boa_engine/src/object/builtins/jsarraybuffer.rs | 11 ++++------- boa_engine/src/object/builtins/jsdataview.rs | 12 +++++------- boa_engine/src/object/builtins/jsdate.rs | 8 ++++---- boa_engine/src/object/builtins/jsfunction.rs | 17 ++++------------- boa_engine/src/object/builtins/jsgenerator.rs | 2 +- boa_engine/src/object/builtins/jsmap.rs | 8 ++++---- .../src/object/builtins/jsmap_iterator.rs | 2 +- boa_engine/src/object/builtins/jspromise.rs | 11 +++++------ boa_engine/src/object/builtins/jsproxy.rs | 9 +++------ boa_engine/src/object/builtins/jsregexp.rs | 2 +- boa_engine/src/object/builtins/jsset.rs | 4 ++-- .../src/object/builtins/jsset_iterator.rs | 2 +- .../src/object/builtins/jssharedarraybuffer.rs | 17 ++++++----------- boa_engine/src/object/builtins/jstypedarray.rs | 6 +++--- 14 files changed, 44 insertions(+), 67 deletions(-) diff --git a/boa_engine/src/object/builtins/jsarraybuffer.rs b/boa_engine/src/object/builtins/jsarraybuffer.rs index 2231cb38831..c04840cc9fd 100644 --- a/boa_engine/src/object/builtins/jsarraybuffer.rs +++ b/boa_engine/src/object/builtins/jsarraybuffer.rs @@ -3,9 +3,7 @@ use crate::{ builtins::array_buffer::ArrayBuffer, context::intrinsics::StandardConstructors, error::JsNativeError, - object::{ - internal_methods::get_prototype_from_constructor, JsObject, JsObjectType, ObjectData, - }, + object::{internal_methods::get_prototype_from_constructor, JsObject, JsObjectType}, value::TryFromJs, Context, JsResult, JsValue, }; @@ -106,7 +104,7 @@ impl JsArrayBuffer { let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::array_buffer(ArrayBuffer::from_data(block, JsValue::Undefined)), + ArrayBuffer::from_data(block, JsValue::Undefined), ); Ok(Self { inner: obj }) @@ -117,7 +115,7 @@ impl JsArrayBuffer { /// This does not clone the fields of the array buffer, it only does a shallow clone of the object. #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is_array_buffer() { + if object.is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() @@ -188,8 +186,7 @@ impl JsArrayBuffer { #[inline] pub fn detach(&self, detach_key: &JsValue) -> JsResult> { self.inner - .borrow_mut() - .as_array_buffer_mut() + .downcast_mut::() .expect("inner must be an ArrayBuffer") .detach(detach_key)? .ok_or_else(|| { diff --git a/boa_engine/src/object/builtins/jsdataview.rs b/boa_engine/src/object/builtins/jsdataview.rs index 821e27d432e..be8d214136d 100644 --- a/boa_engine/src/object/builtins/jsdataview.rs +++ b/boa_engine/src/object/builtins/jsdataview.rs @@ -1,10 +1,9 @@ //! A Rust API wrapper for Boa's `DataView` Builtin ECMAScript Object use crate::{ - builtins::DataView, + builtins::{array_buffer::ArrayBuffer, DataView}, context::intrinsics::StandardConstructors, object::{ internal_methods::get_prototype_from_constructor, JsArrayBuffer, JsObject, JsObjectType, - ObjectData, }, value::TryFromJs, Context, JsNativeError, JsResult, JsValue, @@ -47,8 +46,7 @@ impl JsDataView { context: &mut Context, ) -> JsResult { let (byte_offset, byte_length) = { - let borrowed_buffer = array_buffer.borrow(); - let buffer = borrowed_buffer.as_array_buffer().ok_or_else(|| { + let buffer = array_buffer.downcast_ref::().ok_or_else(|| { JsNativeError::typ().with_message("buffer must be an ArrayBuffer") })?; @@ -98,11 +96,11 @@ impl JsDataView { let obj = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::data_view(DataView { + DataView { viewed_array_buffer: (**array_buffer).clone(), byte_length, byte_offset, - }), + }, ); Ok(Self { inner: obj }) @@ -111,7 +109,7 @@ impl JsDataView { /// Create a new `JsDataView` object from an existing object. #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is_data_view() { + if object.is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() diff --git a/boa_engine/src/object/builtins/jsdate.rs b/boa_engine/src/object/builtins/jsdate.rs index 9034a10723e..ae06af33c9f 100644 --- a/boa_engine/src/object/builtins/jsdate.rs +++ b/boa_engine/src/object/builtins/jsdate.rs @@ -6,7 +6,7 @@ use chrono::DateTime; use crate::{ builtins::Date, - object::{JsObject, JsObjectType, ObjectData}, + object::{JsObject, JsObjectType}, value::TryFromJs, Context, JsNativeError, JsResult, JsValue, }; @@ -51,7 +51,7 @@ impl JsDate { let inner = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::date(Date::utc_now(context.host_hooks())), + Date::utc_now(context.host_hooks()), ); Self { inner } @@ -60,7 +60,7 @@ impl JsDate { /// Create a new `JsDate` object from an existing object. #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is_date() { + if object.is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() @@ -559,7 +559,7 @@ impl JsDate { inner: JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::date(date_time), + date_time, ), }) } diff --git a/boa_engine/src/object/builtins/jsfunction.rs b/boa_engine/src/object/builtins/jsfunction.rs index 403f08e8ac6..406fe958cb6 100644 --- a/boa_engine/src/object/builtins/jsfunction.rs +++ b/boa_engine/src/object/builtins/jsfunction.rs @@ -2,12 +2,7 @@ use crate::{ builtins::function::ConstructorKind, native_function::NativeFunctionObject, - object::{ - internal_methods::function::{ - NATIVE_CONSTRUCTOR_INTERNAL_METHODS, NATIVE_FUNCTION_INTERNAL_METHODS, - }, - JsObject, JsObjectType, Object, ObjectKind, - }, + object::{JsObject, JsObjectType}, value::TryFromJs, Context, JsNativeError, JsResult, JsValue, NativeFunction, }; @@ -33,16 +28,12 @@ impl JsFunction { /// [`Context`]: crate::Context pub(crate) fn empty_intrinsic_function(constructor: bool) -> Self { Self { - inner: JsObject::from_object_and_vtable( - Object::with_kind(ObjectKind::NativeFunction(NativeFunctionObject { + inner: JsObject::from_proto_and_data( + None, + NativeFunctionObject { f: NativeFunction::from_fn_ptr(|_, _, _| Ok(JsValue::undefined())), constructor: constructor.then_some(ConstructorKind::Base), realm: None, - })), - if constructor { - &NATIVE_CONSTRUCTOR_INTERNAL_METHODS - } else { - &NATIVE_FUNCTION_INTERNAL_METHODS }, ), } diff --git a/boa_engine/src/object/builtins/jsgenerator.rs b/boa_engine/src/object/builtins/jsgenerator.rs index 41b2e7a1f91..0dd03083c52 100644 --- a/boa_engine/src/object/builtins/jsgenerator.rs +++ b/boa_engine/src/object/builtins/jsgenerator.rs @@ -19,7 +19,7 @@ impl JsGenerator { /// Creates a `JsGenerator` from a generator `JsObject` #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is_generator() { + if object.is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() diff --git a/boa_engine/src/object/builtins/jsmap.rs b/boa_engine/src/object/builtins/jsmap.rs index 3764f00fd94..19347d4d5d6 100644 --- a/boa_engine/src/object/builtins/jsmap.rs +++ b/boa_engine/src/object/builtins/jsmap.rs @@ -3,7 +3,7 @@ use crate::{ builtins::map::{add_entries_from_iterable, ordered_map::OrderedMap}, builtins::Map, error::JsNativeError, - object::{JsFunction, JsMapIterator, JsObject, JsObjectType, ObjectData}, + object::{JsFunction, JsMapIterator, JsObject, JsObjectType}, string::utf16, value::TryFromJs, Context, JsResult, JsValue, @@ -145,7 +145,7 @@ impl JsMap { /// ``` /// # use boa_engine::{ /// # builtins::map::ordered_map::OrderedMap, - /// # object::{builtins::JsMap, JsObject, ObjectData}, + /// # object::{builtins::JsMap, JsObject}, /// # Context, JsValue, JsResult, /// # }; /// # fn main() -> JsResult<()> { @@ -176,7 +176,7 @@ impl JsMap { /// ``` #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is_map() { + if object.is::>() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() @@ -194,7 +194,7 @@ impl JsMap { JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ObjectData::map(OrderedMap::new()), + >::new(), ) } diff --git a/boa_engine/src/object/builtins/jsmap_iterator.rs b/boa_engine/src/object/builtins/jsmap_iterator.rs index 156c8045852..9b07148b3f3 100644 --- a/boa_engine/src/object/builtins/jsmap_iterator.rs +++ b/boa_engine/src/object/builtins/jsmap_iterator.rs @@ -20,7 +20,7 @@ impl JsMapIterator { /// Create a [`JsMapIterator`] from a [`JsObject`]. If object is not a `MapIterator`, throw `TypeError` #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is_map_iterator() { + if object.is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() diff --git a/boa_engine/src/object/builtins/jspromise.rs b/boa_engine/src/object/builtins/jspromise.rs index 8e8b7566e77..2ee7327eec8 100644 --- a/boa_engine/src/object/builtins/jspromise.rs +++ b/boa_engine/src/object/builtins/jspromise.rs @@ -9,7 +9,7 @@ use crate::{ Promise, }, job::NativeJob, - object::{JsObject, JsObjectType, ObjectData}, + object::{JsObject, JsObjectType}, value::TryFromJs, Context, JsArgs, JsError, JsNativeError, JsResult, JsValue, NativeFunction, }; @@ -168,7 +168,7 @@ impl JsPromise { let promise = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().constructors().promise().prototype(), - ObjectData::promise(Promise::new()), + Promise::new(), ); let resolvers = Promise::create_resolving_functions(&promise, context); @@ -219,7 +219,7 @@ impl JsPromise { let promise = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().constructors().promise().prototype(), - ObjectData::promise(Promise::new()), + Promise::new(), ); let resolvers = Promise::create_resolving_functions(&promise, context); let promise = @@ -262,7 +262,7 @@ impl JsPromise { /// ``` #[inline] pub fn from_object(object: JsObject) -> JsResult { - if !object.is_promise() { + if !object.is::() { return Err(JsNativeError::typ() .with_message("`object` is not a Promise") .into()); @@ -423,8 +423,7 @@ impl JsPromise { #[must_use] pub fn state(&self) -> PromiseState { self.inner - .borrow() - .as_promise() + .downcast_ref::() .expect("objects cannot change type after creation") .state() .clone() diff --git a/boa_engine/src/object/builtins/jsproxy.rs b/boa_engine/src/object/builtins/jsproxy.rs index 3a316b17196..6574114c0c3 100644 --- a/boa_engine/src/object/builtins/jsproxy.rs +++ b/boa_engine/src/object/builtins/jsproxy.rs @@ -4,7 +4,7 @@ use boa_gc::{Finalize, Trace}; use crate::{ builtins::Proxy, native_function::{NativeFunction, NativeFunctionPointer}, - object::{FunctionObjectBuilder, JsObject, JsObjectType, ObjectData}, + object::{FunctionObjectBuilder, JsObject, JsObjectType}, string::utf16, value::TryFromJs, Context, JsNativeError, JsResult, JsValue, @@ -39,7 +39,7 @@ impl JsProxy { /// `TypeError`. #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.borrow().is_proxy() { + if object.borrow().is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() @@ -528,13 +528,10 @@ impl JsProxyBuilder { .expect("new object should be writable"); } - let callable = self.target.is_callable(); - let constructor = self.target.is_constructor(); - let proxy = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context.intrinsics().constructors().object().prototype(), - ObjectData::proxy(Proxy::new(self.target, handler), callable, constructor), + Proxy::new(self.target, handler), ); JsProxy { inner: proxy } diff --git a/boa_engine/src/object/builtins/jsregexp.rs b/boa_engine/src/object/builtins/jsregexp.rs index e262e251f44..35b00809224 100644 --- a/boa_engine/src/object/builtins/jsregexp.rs +++ b/boa_engine/src/object/builtins/jsregexp.rs @@ -71,7 +71,7 @@ impl JsRegExp { /// Create a `JsRegExp` from a regular expression `JsObject` #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is_regexp() { + if object.is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() diff --git a/boa_engine/src/object/builtins/jsset.rs b/boa_engine/src/object/builtins/jsset.rs index cfc63ffd07f..3bc76232ce5 100644 --- a/boa_engine/src/object/builtins/jsset.rs +++ b/boa_engine/src/object/builtins/jsset.rs @@ -4,7 +4,7 @@ use std::ops::Deref; use boa_gc::{Finalize, Trace}; use crate::{ - builtins::Set, + builtins::{Set, set::ordered_set::OrderedSet}, error::JsNativeError, object::{JsFunction, JsObject, JsObjectType, JsSetIterator}, value::TryFromJs, @@ -144,7 +144,7 @@ impl JsSet { /// Utility: Creates `JsSet` from `JsObject`, if not a Set throw `TypeError`. #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is_set() { + if object.is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() diff --git a/boa_engine/src/object/builtins/jsset_iterator.rs b/boa_engine/src/object/builtins/jsset_iterator.rs index 8fe4fc226cc..c11753242c7 100644 --- a/boa_engine/src/object/builtins/jsset_iterator.rs +++ b/boa_engine/src/object/builtins/jsset_iterator.rs @@ -21,7 +21,7 @@ impl JsSetIterator { /// Create a `JsSetIterator` from a `JsObject`. /// If object is not a `SetIterator`, throw `TypeError`. pub fn from_object(object: JsObject) -> JsResult { - if object.is_set_iterator() { + if object.is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() diff --git a/boa_engine/src/object/builtins/jssharedarraybuffer.rs b/boa_engine/src/object/builtins/jssharedarraybuffer.rs index 6d674295c71..b9fea887611 100644 --- a/boa_engine/src/object/builtins/jssharedarraybuffer.rs +++ b/boa_engine/src/object/builtins/jssharedarraybuffer.rs @@ -2,7 +2,7 @@ use crate::{ builtins::array_buffer::SharedArrayBuffer, error::JsNativeError, - object::{JsObject, JsObjectType, ObjectData}, + object::{JsObject, JsObjectType}, value::TryFromJs, Context, JsResult, JsValue, }; @@ -42,11 +42,8 @@ impl JsSharedArrayBuffer { .shared_array_buffer() .prototype(); - let inner = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - proto, - ObjectData::shared_array_buffer(buffer), - ); + let inner = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), proto, buffer); Self { inner } } @@ -58,7 +55,7 @@ impl JsSharedArrayBuffer { /// the object. #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is_shared_array_buffer() { + if object.is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() @@ -71,8 +68,7 @@ impl JsSharedArrayBuffer { #[inline] #[must_use] pub fn byte_length(&self) -> usize { - self.borrow() - .as_shared_array_buffer() + self.downcast_ref::() .expect("should be an array buffer") .len() } @@ -81,8 +77,7 @@ impl JsSharedArrayBuffer { #[inline] #[must_use] pub fn inner(&self) -> SharedArrayBuffer { - self.borrow() - .as_shared_array_buffer() + self.downcast_ref::() .expect("should be an array buffer") .clone() } diff --git a/boa_engine/src/object/builtins/jstypedarray.rs b/boa_engine/src/object/builtins/jstypedarray.rs index b4378a3989c..c533925a338 100644 --- a/boa_engine/src/object/builtins/jstypedarray.rs +++ b/boa_engine/src/object/builtins/jstypedarray.rs @@ -1,7 +1,7 @@ //! Rust API wrappers for the `TypedArray` Builtin ECMAScript Objects use crate::{ builtins::typed_array::BuiltinTypedArray, - builtins::BuiltInConstructor, + builtins::{typed_array::TypedArray, BuiltInConstructor}, error::JsNativeError, object::{JsArrayBuffer, JsFunction, JsObject, JsObjectType}, value::{IntoOrUndefined, TryFromJs}, @@ -25,7 +25,7 @@ impl JsTypedArray { /// object. #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.is_typed_array() { + if object.is::() { Ok(Self { inner: object }) } else { Err(JsNativeError::typ() @@ -450,7 +450,7 @@ macro_rules! JsTypedArrayType { )] #[inline] pub fn from_object(object: JsObject) -> JsResult { - if object.$checker_function() { + if object.borrow().$checker_function() { Ok(Self { inner: JsTypedArray { inner: object.into(), From 12e822f223e95384c5e92f3f4da47da43d07fa91 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 04:18:44 -0600 Subject: [PATCH 07/15] Migrate published crates to erased objects --- boa_cli/src/debug/function.rs | 12 ++++-------- boa_cli/src/debug/object.rs | 2 +- boa_runtime/src/console/mod.rs | 4 ++-- boa_tester/src/exec/js262.rs | 9 +++++---- 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/boa_cli/src/debug/function.rs b/boa_cli/src/debug/function.rs index 4f8769ea3e0..bb7f60dd1fa 100644 --- a/boa_cli/src/debug/function.rs +++ b/boa_cli/src/debug/function.rs @@ -2,7 +2,7 @@ use boa_engine::{ js_string, object::ObjectInitializer, vm::flowgraph::{Direction, Graph}, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, + Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, builtins::function::OrdinaryFunction, }; use crate::FlowgraphFormat; @@ -80,9 +80,7 @@ fn flowgraph(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResu } } - let object = object.borrow(); - - let Some(function) = object.as_function() else { + let Some(function) = object.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("expected an ordinary function object") .into()); @@ -112,8 +110,7 @@ fn bytecode(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult .with_message(format!("expected object, got {}", value.type_of())) .into()); }; - let object = object.borrow(); - let Some(function) = object.as_function() else { + let Some(function) = object.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("expected an ordinary function object") .into()); @@ -124,8 +121,7 @@ fn bytecode(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult } fn set_trace_flag_in_function_object(object: &JsObject, value: bool) -> JsResult<()> { - let object = object.borrow(); - let Some(function) = object.as_function() else { + let Some(function) = object.downcast_ref::() else { return Err(JsNativeError::typ() .with_message("expected an ordinary function object") .into()); diff --git a/boa_cli/src/debug/object.rs b/boa_cli/src/debug/object.rs index 882f513b16d..da0db74813b 100644 --- a/boa_cli/src/debug/object.rs +++ b/boa_cli/src/debug/object.rs @@ -18,7 +18,7 @@ fn id(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResult { }; let ptr: *const _ = object.as_ref(); - Ok(js_string!(format!("0x{:X}", ptr as usize)).into()) + Ok(js_string!(format!("0x{:X}", ptr.cast::<()>() as usize)).into()) } pub(super) fn create_object(context: &mut Context) -> JsObject { diff --git a/boa_runtime/src/console/mod.rs b/boa_runtime/src/console/mod.rs index 05997d8aaba..26b20a27359 100644 --- a/boa_runtime/src/console/mod.rs +++ b/boa_runtime/src/console/mod.rs @@ -20,7 +20,7 @@ use boa_engine::{ object::{JsObject, ObjectInitializer}, string::utf16, value::{JsValue, Numeric}, - Context, JsArgs, JsResult, JsString, + Context, JsArgs, JsData, JsResult, JsString, }; use boa_gc::{Finalize, Trace}; // use boa_profiler::Profiler; @@ -123,7 +123,7 @@ fn formatter(data: &[JsValue], context: &mut Context) -> JsResult { } /// This is the internal console object state. -#[derive(Debug, Default, Trace, Finalize)] +#[derive(Debug, Default, Trace, Finalize, JsData)] pub struct Console { count_map: FxHashMap, timer_map: FxHashMap, diff --git a/boa_tester/src/exec/js262.rs b/boa_tester/src/exec/js262.rs index b9ed810df56..2c51627d872 100644 --- a/boa_tester/src/exec/js262.rs +++ b/boa_tester/src/exec/js262.rs @@ -7,7 +7,7 @@ use std::{ }; use boa_engine::{ - builtins::array_buffer::SharedArrayBuffer, + builtins::array_buffer::{ArrayBuffer, SharedArrayBuffer}, js_string, native_function::NativeFunction, object::{builtins::JsSharedArrayBuffer, JsObject, ObjectInitializer}, @@ -142,7 +142,9 @@ fn detach_array_buffer(_: &JsValue, args: &[JsValue], _: &mut Context) -> JsResu let mut array_buffer = array_buffer.borrow_mut(); // 1. Assert: IsSharedArrayBuffer(arrayBuffer) is false. - let array_buffer = array_buffer.as_array_buffer_mut().ok_or_else(type_err)?; + let array_buffer = array_buffer + .downcast_mut::() + .ok_or_else(type_err)?; // 2. If key is not present, set key to undefined. let key = args.get_or_undefined(1); @@ -233,8 +235,7 @@ fn agent_obj(handles: WorkerHandles, context: &mut Context) -> JsObject { JsNativeError::typ().with_message("argument was not a shared array") })?; let buffer = buffer - .borrow() - .as_shared_array_buffer() + .downcast_ref::() .ok_or_else(|| { JsNativeError::typ().with_message("argument was not a shared array") })? From 2b00b13985b19247ccc22b4edb9ba4fae13ea927 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 13:27:00 -0600 Subject: [PATCH 08/15] Fix warnings --- boa_cli/src/debug/function.rs | 3 +- .../src/builtins/array/array_iterator.rs | 2 +- boa_engine/src/builtins/array/mod.rs | 13 +- boa_engine/src/builtins/builder.rs | 2 +- boa_engine/src/builtins/error/mod.rs | 2 +- boa_engine/src/builtins/function/arguments.rs | 4 +- .../iterable/async_from_sync_iterator.rs | 2 +- boa_engine/src/builtins/map/map_iterator.rs | 2 +- .../src/builtins/object/for_in_iterator.rs | 2 +- boa_engine/src/builtins/proxy/mod.rs | 35 --- .../builtins/regexp/regexp_string_iterator.rs | 4 +- boa_engine/src/builtins/set/set_iterator.rs | 2 +- .../src/builtins/string/string_iterator.rs | 6 +- .../typed_array/integer_indexed_object.rs | 2 +- boa_engine/src/class.rs | 19 +- boa_engine/src/error.rs | 10 +- boa_engine/src/lib.rs | 2 + boa_engine/src/object/builtins/jsset.rs | 2 +- boa_engine/src/object/datatypes.rs | 23 +- .../internal_methods/immutable_prototype.rs | 2 +- boa_engine/src/object/internal_methods/mod.rs | 2 +- boa_engine/src/object/mod.rs | 11 - boa_engine/src/vm/opcode/await/mod.rs | 2 +- boa_engine/src/vm/opcode/generator/mod.rs | 5 +- .../src/vm/opcode/push/class/private.rs | 3 +- boa_examples/src/bin/classes.rs | 4 +- boa_examples/src/bin/host_defined.rs | 10 +- fuzz/Cargo.lock | 220 ++++++++++++------ 28 files changed, 215 insertions(+), 181 deletions(-) diff --git a/boa_cli/src/debug/function.rs b/boa_cli/src/debug/function.rs index bb7f60dd1fa..cb94b34b995 100644 --- a/boa_cli/src/debug/function.rs +++ b/boa_cli/src/debug/function.rs @@ -1,8 +1,9 @@ use boa_engine::{ + builtins::function::OrdinaryFunction, js_string, object::ObjectInitializer, vm::flowgraph::{Direction, Graph}, - Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, builtins::function::OrdinaryFunction, + Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, }; use crate::FlowgraphFormat; diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index 34efd14faf5..4548b5300c8 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -29,7 +29,7 @@ use boa_profiler::Profiler; /// /// [spec]: https://tc39.es/ecma262/#sec-array-iterator-objects #[derive(Debug, Clone, Finalize, Trace, JsData)] -pub struct ArrayIterator { +pub(crate) struct ArrayIterator { array: JsObject, next_index: u64, #[unsafe_ignore_trace] diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 47b5e90d7db..4cae57efe03 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -359,11 +359,8 @@ impl Array { .create(Array, vec![JsValue::new(length)])); } - let array = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - Array, - ); + let array = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, Array); // 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }). ordinary_define_own_property( @@ -406,11 +403,7 @@ impl Array { .intrinsics() .templates() .array() - .create_with_indexed_properties( - Array, - vec![JsValue::new(length)], - elements, - ) + .create_with_indexed_properties(Array, vec![JsValue::new(length)], elements) } /// Utility function for concatenating array objects. diff --git a/boa_engine/src/builtins/builder.rs b/boa_engine/src/builtins/builder.rs index a565744b567..549f78845d4 100644 --- a/boa_engine/src/builtins/builder.rs +++ b/boa_engine/src/builtins/builder.rs @@ -28,7 +28,7 @@ pub(crate) struct ConstructorNoProto; pub(crate) struct OrdinaryFunction; /// Indicates if the marker is a constructor. -trait IsConstructor { +pub(crate) trait IsConstructor { const IS_CONSTRUCTOR: bool; } diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index fdeffb5520f..eb525afed2f 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -19,7 +19,7 @@ use crate::{ property::Attribute, realm::Realm, string::{common::StaticJsStrings, utf16}, - Context, JsArgs, JsResult, JsString, JsValue, JsData, + Context, JsArgs, JsData, JsResult, JsString, JsValue, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index 8bf1e1ca70a..3746b6250e6 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -16,7 +16,7 @@ use rustc_hash::FxHashMap; #[derive(Debug, Copy, Clone, Trace, Finalize, JsData)] #[boa_gc(empty_trace)] -pub struct UnmappedArguments; +pub(crate) struct UnmappedArguments; impl UnmappedArguments { /// Creates a new unmapped Arguments ordinary object. @@ -75,7 +75,7 @@ impl UnmappedArguments { /// /// This struct stores all the data to access mapped function parameters in their environment. #[derive(Debug, Clone, Trace, Finalize)] -pub struct MappedArguments { +pub(crate) struct MappedArguments { binding_indices: Vec>, environment: Gc, } diff --git a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs index 68221e7db67..af0ed1c54ec 100644 --- a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -22,7 +22,7 @@ use boa_profiler::Profiler; /// /// [spec]: https://tc39.es/ecma262/#sec-properties-of-async-from-sync-iterator-instances #[derive(Clone, Debug, Finalize, Trace, JsData)] -pub struct AsyncFromSyncIterator { +pub(crate) struct AsyncFromSyncIterator { // The [[SyncIteratorRecord]] internal slot. sync_iterator_record: IteratorRecord, } diff --git a/boa_engine/src/builtins/map/map_iterator.rs b/boa_engine/src/builtins/map/map_iterator.rs index a10a08e7f40..32fd89d5aaa 100644 --- a/boa_engine/src/builtins/map/map_iterator.rs +++ b/boa_engine/src/builtins/map/map_iterator.rs @@ -29,7 +29,7 @@ use boa_profiler::Profiler; /// /// [spec]: https://tc39.es/ecma262/#sec-map-iterator-objects #[derive(Debug, Finalize, Trace, JsData)] -pub struct MapIterator { +pub(crate) struct MapIterator { iterated_map: Option, map_next_index: usize, #[unsafe_ignore_trace] diff --git a/boa_engine/src/builtins/object/for_in_iterator.rs b/boa_engine/src/builtins/object/for_in_iterator.rs index c6b290358fe..98af4e1eaf7 100644 --- a/boa_engine/src/builtins/object/for_in_iterator.rs +++ b/boa_engine/src/builtins/object/for_in_iterator.rs @@ -31,7 +31,7 @@ use std::collections::VecDeque; /// /// [spec]: https://tc39.es/ecma262/#sec-for-in-iterator-objects #[derive(Debug, Clone, Finalize, Trace, JsData)] -pub struct ForInIterator { +pub(crate) struct ForInIterator { object: JsValue, visited_keys: FxHashSet, remaining_keys: VecDeque, diff --git a/boa_engine/src/builtins/proxy/mod.rs b/boa_engine/src/builtins/proxy/mod.rs index 53e9888ee9d..0d654c23aeb 100644 --- a/boa_engine/src/builtins/proxy/mod.rs +++ b/boa_engine/src/builtins/proxy/mod.rs @@ -246,41 +246,6 @@ impl Proxy { } } -/// Definitions of the internal object methods for array exotic objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array-exotic-objects -pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_BASIC: InternalObjectMethods = - InternalObjectMethods { - __get_prototype_of__: proxy_exotic_get_prototype_of, - __set_prototype_of__: proxy_exotic_set_prototype_of, - __is_extensible__: proxy_exotic_is_extensible, - __prevent_extensions__: proxy_exotic_prevent_extensions, - __get_own_property__: proxy_exotic_get_own_property, - __define_own_property__: proxy_exotic_define_own_property, - __has_property__: proxy_exotic_has_property, - __get__: proxy_exotic_get, - __set__: proxy_exotic_set, - __delete__: proxy_exotic_delete, - __own_property_keys__: proxy_exotic_own_property_keys, - ..ORDINARY_INTERNAL_METHODS - }; - -pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_WITH_CALL: InternalObjectMethods = - InternalObjectMethods { - __call__: proxy_exotic_call, - ..PROXY_EXOTIC_INTERNAL_METHODS_BASIC - }; - -pub(crate) static PROXY_EXOTIC_INTERNAL_METHODS_ALL: InternalObjectMethods = - InternalObjectMethods { - __call__: proxy_exotic_call, - __construct__: proxy_exotic_construct, - ..PROXY_EXOTIC_INTERNAL_METHODS_BASIC - }; - /// `10.5.1 [[GetPrototypeOf]] ( )` /// /// More information: diff --git a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs index efac229553a..5ab9e484358 100644 --- a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs @@ -33,7 +33,7 @@ use regexp::{advance_string_index, RegExp}; /// /// [spec]: https://tc39.es/ecma262/#sec-regexp-string-iterator-objects #[derive(Debug, Clone, Finalize, Trace, JsData)] -pub struct RegExpStringIterator { +pub(crate) struct RegExpStringIterator { matcher: JsObject, string: JsString, global: bool, @@ -123,7 +123,7 @@ impl RegExpStringIterator { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-%regexpstringiteratorprototype%.next - pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let mut iterator = this .as_object() .and_then(|o| o.downcast_mut::()) diff --git a/boa_engine/src/builtins/set/set_iterator.rs b/boa_engine/src/builtins/set/set_iterator.rs index 4567492ecdc..decc305f397 100644 --- a/boa_engine/src/builtins/set/set_iterator.rs +++ b/boa_engine/src/builtins/set/set_iterator.rs @@ -29,7 +29,7 @@ use boa_profiler::Profiler; /// /// [spec]: https://tc39.es/ecma262/#sec-set-iterator-objects #[derive(Debug, Finalize, Trace, JsData)] -pub struct SetIterator { +pub(crate) struct SetIterator { iterated_set: JsValue, next_index: usize, #[unsafe_ignore_trace] diff --git a/boa_engine/src/builtins/string/string_iterator.rs b/boa_engine/src/builtins/string/string_iterator.rs index 2c8378b88d7..89dc1d6e754 100644 --- a/boa_engine/src/builtins/string/string_iterator.rs +++ b/boa_engine/src/builtins/string/string_iterator.rs @@ -26,7 +26,7 @@ use boa_profiler::Profiler; /// /// [spec]: https://tc39.es/ecma262/#sec-string-iterator-objects #[derive(Debug, Clone, Finalize, Trace, JsData)] -pub struct StringIterator { +pub(crate) struct StringIterator { string: JsString, next_index: usize, } @@ -59,7 +59,7 @@ impl IntrinsicObject for StringIterator { impl StringIterator { /// Create a new `StringIterator`. - pub fn create_string_iterator(string: JsString, context: &mut Context) -> JsObject { + pub(crate) fn create_string_iterator(string: JsString, context: &mut Context) -> JsObject { JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), context @@ -75,7 +75,7 @@ impl StringIterator { } /// `StringIterator.prototype.next( )` - pub fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { + pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let mut string_iterator = this .as_object() .and_then(|o| o.downcast_mut::()) diff --git a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs index 6939dfef3ea..893edfc8762 100644 --- a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs +++ b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs @@ -19,7 +19,7 @@ use boa_macros::utf16; use super::{is_valid_integer_index, TypedArrayKind}; -// An `IntegerIndexed` object is just an alias for a `TypedArray` object. +/// An `IntegerIndexed` object is just an alias for a `TypedArray` object. pub type IntegerIndexed = TypedArray; /// A `TypedArrayObject` is an exotic object that performs special handling of integer diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index d5b45907009..969d3c1a736 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -107,10 +107,7 @@ use crate::{ context::intrinsics::StandardConstructor, error::JsNativeError, native_function::NativeFunction, - object::{ - ConstructorBuilder, FunctionBinding, JsFunction, JsObject, NativeObject, - PROTOTYPE, - }, + object::{ConstructorBuilder, FunctionBinding, JsFunction, JsObject, NativeObject, PROTOTYPE}, property::{Attribute, PropertyDescriptor, PropertyKey}, Context, JsResult, JsValue, }; @@ -199,11 +196,8 @@ pub trait Class: NativeObject + Sized { let data = Self::data_constructor(new_target, args, context)?; - let object = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - data, - ); + let object = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, data); Self::object_constructor(&object, args, context)?; @@ -234,11 +228,8 @@ pub trait Class: NativeObject + Sized { })? .prototype(); - let object = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - data, - ); + let object = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, data); Self::object_constructor(&object, &[], context)?; diff --git a/boa_engine/src/error.rs b/boa_engine/src/error.rs index e0c580095f4..7c952d2b7ca 100644 --- a/boa_engine/src/error.rs +++ b/boa_engine/src/error.rs @@ -241,7 +241,8 @@ impl JsError { let obj = val .as_object() .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; - let error = *obj.downcast_ref::() + let error = *obj + .downcast_ref::() .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; let try_get_property = |key: JsString, name, context: &mut Context| { @@ -946,11 +947,8 @@ impl JsNativeError { } }; - let o = JsObject::from_proto_and_data_with_shared_shape( - context.root_shape(), - prototype, - tag, - ); + let o = + JsObject::from_proto_and_data_with_shared_shape(context.root_shape(), prototype, tag); o.create_non_enumerable_data_property_or_throw( js_string!("message"), diff --git a/boa_engine/src/lib.rs b/boa_engine/src/lib.rs index 9fd6c28142d..448731a5fd0 100644 --- a/boa_engine/src/lib.rs +++ b/boa_engine/src/lib.rs @@ -122,6 +122,8 @@ pub mod prelude { symbol::JsSymbol, value::JsValue, }; + pub use boa_gc::{Finalize, Trace}; + pub use boa_macros::JsData; pub use boa_parser::Source; } diff --git a/boa_engine/src/object/builtins/jsset.rs b/boa_engine/src/object/builtins/jsset.rs index 3bc76232ce5..6fcf8f64155 100644 --- a/boa_engine/src/object/builtins/jsset.rs +++ b/boa_engine/src/object/builtins/jsset.rs @@ -4,7 +4,7 @@ use std::ops::Deref; use boa_gc::{Finalize, Trace}; use crate::{ - builtins::{Set, set::ordered_set::OrderedSet}, + builtins::{set::ordered_set::OrderedSet, Set}, error::JsNativeError, object::{JsFunction, JsObject, JsObjectType, JsSetIterator}, value::TryFromJs, diff --git a/boa_engine/src/object/datatypes.rs b/boa_engine/src/object/datatypes.rs index e80f80707e7..9b892bb07ee 100644 --- a/boa_engine/src/object/datatypes.rs +++ b/boa_engine/src/object/datatypes.rs @@ -15,15 +15,34 @@ use std::{ }; use boa_gc::{Ephemeron, Gc, GcRefCell, Trace, WeakGc, WeakMap}; -pub use boa_macros::JsData; use super::internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS}; +/// Represents a type that can be stored inside a `JsObject`. +/// +/// This can be automatically derived using a macro. +/// +/// # Example +/// +/// ``` +/// use boa_engine::{Finalize, JsData, JsObject, Trace}; +/// +/// #[derive(Trace, Finalize, JsData)] +/// struct CustomStruct { +/// #[unsafe_ignore_trace] +/// counter: usize, +/// } +/// +/// let object = +/// JsObject::from_proto_and_data(None, CustomStruct { counter: 5 }); +/// +/// assert_eq!(object.downcast_ref::().unwrap().counter, 5); +/// ``` pub trait JsData { #[doc(hidden)] fn internal_methods(&self) -> &'static InternalObjectMethods where - Self: Sized, + Self: Sized, // Avoids adding this method to `NativeObject`'s vtable. { &ORDINARY_INTERNAL_METHODS } diff --git a/boa_engine/src/object/internal_methods/immutable_prototype.rs b/boa_engine/src/object/internal_methods/immutable_prototype.rs index 0dded715a91..57c3ccb898f 100644 --- a/boa_engine/src/object/internal_methods/immutable_prototype.rs +++ b/boa_engine/src/object/internal_methods/immutable_prototype.rs @@ -36,4 +36,4 @@ pub(crate) fn immutable_prototype_exotic_set_prototype_of( // 2. If SameValue(V, current) is true, return true. // 3. Return false. Ok(val == current) -} \ No newline at end of file +} diff --git a/boa_engine/src/object/internal_methods/mod.rs b/boa_engine/src/object/internal_methods/mod.rs index 8c5395e6e5b..8fb32e0d737 100644 --- a/boa_engine/src/object/internal_methods/mod.rs +++ b/boa_engine/src/object/internal_methods/mod.rs @@ -323,7 +323,7 @@ pub(crate) static ORDINARY_INTERNAL_METHODS: InternalObjectMethods = InternalObj /// resort to `dyn Object`. /// /// For a guide on how to implement exotic internal methods, see `ORDINARY_INTERNAL_METHODS`. -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] #[allow(clippy::type_complexity)] pub struct InternalObjectMethods { pub(crate) __get_prototype_of__: fn(&JsObject, &mut Context) -> JsResult, diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index c46a940603e..f1f8b1c29dc 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -247,17 +247,6 @@ pub enum PrivateElement { }, } -impl Object { - pub(crate) fn new(data: T) -> Self { - Self { - data, - properties: PropertyMap::default(), - extensible: true, - private_elements: ThinVec::new(), - } - } -} - impl Object { /// Returns the shape of the object. #[must_use] diff --git a/boa_engine/src/vm/opcode/await/mod.rs b/boa_engine/src/vm/opcode/await/mod.rs index 8f2cb5a53a5..cbad140abd3 100644 --- a/boa_engine/src/vm/opcode/await/mod.rs +++ b/boa_engine/src/vm/opcode/await/mod.rs @@ -104,7 +104,7 @@ impl Operation for Await { .and_then(|f| f.async_generator.clone()) { async_generator - .downcast_mut::() + .downcast_mut::() .expect("must be async generator") .context = Some(gen); } diff --git a/boa_engine/src/vm/opcode/generator/mod.rs b/boa_engine/src/vm/opcode/generator/mod.rs index 5fce5d68329..3e660cd72b7 100644 --- a/boa_engine/src/vm/opcode/generator/mod.rs +++ b/boa_engine/src/vm/opcode/generator/mod.rs @@ -151,10 +151,7 @@ impl Operation for AsyncGeneratorClose { gen.state = AsyncGeneratorState::Completed; gen.context = None; - let next = gen - .queue - .pop_front() - .expect("must have item in queue"); + let next = gen.queue.pop_front().expect("must have item in queue"); drop(gen); let return_value = context.vm.get_return_value(); diff --git a/boa_engine/src/vm/opcode/push/class/private.rs b/boa_engine/src/vm/opcode/push/class/private.rs index 03afab35b4c..a33ff5a47f0 100644 --- a/boa_engine/src/vm/opcode/push/class/private.rs +++ b/boa_engine/src/vm/opcode/push/class/private.rs @@ -1,10 +1,11 @@ use crate::{ + builtins::function::OrdinaryFunction, js_string, object::{internal_methods::InternalMethodContext, PrivateElement}, property::PropertyDescriptor, string::utf16, vm::{opcode::Operation, CompletionType}, - Context, JsResult, builtins::function::OrdinaryFunction, + Context, JsResult, }; /// `PushClassPrivateMethod` implements the Opcode Operation for `Opcode::PushClassPrivateMethod` diff --git a/boa_examples/src/bin/classes.rs b/boa_examples/src/bin/classes.rs index 49e98a0b565..15e6a248d26 100644 --- a/boa_examples/src/bin/classes.rs +++ b/boa_examples/src/bin/classes.rs @@ -5,7 +5,7 @@ use boa_engine::{ js_string, native_function::NativeFunction, property::Attribute, - Context, JsArgs, JsResult, JsString, JsValue, Source, + Context, JsArgs, JsData, JsResult, JsString, JsValue, Source, }; use boa_gc::{Finalize, Trace}; @@ -18,7 +18,7 @@ use boa_runtime::Console; // // The fields of the struct are not accessible by Javascript unless we create accessors for them. /// Represents a `Person` object. -#[derive(Debug, Trace, Finalize)] +#[derive(Debug, Trace, Finalize, JsData)] struct Person { /// The name of the person. name: JsString, diff --git a/boa_examples/src/bin/host_defined.rs b/boa_examples/src/bin/host_defined.rs index 69399f0cf1d..c6b12d3661d 100644 --- a/boa_examples/src/bin/host_defined.rs +++ b/boa_examples/src/bin/host_defined.rs @@ -1,20 +1,20 @@ // This example goes into the details on how to store user defined structs/state that is shared. use boa_engine::{ - js_string, native_function::NativeFunction, Context, JsArgs, JsError, JsNativeError, JsValue, - Source, + js_string, native_function::NativeFunction, Context, JsArgs, JsData, JsError, JsNativeError, + JsValue, Source, }; use boa_gc::{Finalize, Trace}; /// Custom host-defined struct that has some state, and can be shared between JavaScript and rust. -#[derive(Default, Trace, Finalize)] +#[derive(Default, Trace, Finalize, JsData)] struct CustomHostDefinedStruct { #[unsafe_ignore_trace] counter: usize, } /// Custom host-defined struct that has some state, and can be shared between JavaScript and rust. -#[derive(Trace, Finalize)] +#[derive(Trace, Finalize, JsData)] struct AnotherCustomHostDefinedStruct { #[unsafe_ignore_trace] counter: usize, @@ -27,7 +27,7 @@ impl AnotherCustomHostDefinedStruct { } /// Custom host-defined struct that tracks the number of calls to the `getRealmValue` and `setRealmValue` functions. -#[derive(Default, Trace, Finalize)] +#[derive(Default, Trace, Finalize, JsData)] struct HostDefinedMetrics { #[unsafe_ignore_trace] counter: usize, diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 9945eddcf49..dfa1abb2361 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -4,13 +4,14 @@ version = 3 [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" dependencies = [ "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] @@ -37,6 +38,12 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "autocfg" version = "1.1.0" @@ -51,16 +58,16 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "boa_ast" version = "0.17.0" dependencies = [ "arbitrary", - "bitflags 2.4.0", + "bitflags 2.4.1", "boa_interner", "boa_macros", "indexmap", @@ -72,18 +79,23 @@ dependencies = [ name = "boa_engine" version = "0.17.0" dependencies = [ - "bitflags 2.4.0", + "arrayvec", + "bitflags 2.4.1", "boa_ast", "boa_gc", "boa_interner", "boa_macros", "boa_parser", "boa_profiler", + "bytemuck", + "cfg-if", "chrono", "dashmap", "fast-float", + "hashbrown 0.14.3", "icu_normalizer", "indexmap", + "intrusive-collections", "itertools", "num-bigint", "num-integer", @@ -92,6 +104,7 @@ dependencies = [ "once_cell", "paste", "pollster", + "portable-atomic", "rand", "regress", "rustc-hash", @@ -123,7 +136,8 @@ version = "0.17.0" dependencies = [ "boa_macros", "boa_profiler", - "hashbrown 0.14.1", + "hashbrown 0.14.3", + "icu_locid", "thin-vec", ] @@ -134,7 +148,7 @@ dependencies = [ "arbitrary", "boa_gc", "boa_macros", - "hashbrown 0.14.1", + "hashbrown 0.14.3", "indexmap", "once_cell", "phf", @@ -156,7 +170,7 @@ dependencies = [ name = "boa_parser" version = "0.17.0" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "boa_ast", "boa_interner", "boa_macros", @@ -179,6 +193,26 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "cc" version = "1.0.83" @@ -220,7 +254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.1", + "hashbrown 0.14.3", "lock_api", "once_cell", "parking_lot_core", @@ -288,9 +322,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash", ] @@ -320,9 +354,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b222d891e7bd8c3fb8122cbf255c5e7763ee4824f3620d54a009077c30539fe1" +checksum = "137d96353afc8544d437e8a99eceb10ab291352699573b0de5b08bda38c78c60" dependencies = [ "displaydoc", "yoke", @@ -332,9 +366,9 @@ dependencies = [ [[package]] name = "icu_locid" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56b72c6de0121c00da9828eb3e2603041d563788289bb15feba7c3331de71b5f" +checksum = "5c0aa2536adc14c07e2a521e95512b75ed8ef832f0fdf9299d4a0a45d2be2a9d" dependencies = [ "displaydoc", "litemap", @@ -345,9 +379,9 @@ dependencies = [ [[package]] name = "icu_locid_transform" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49464337ec1e96a409e34be1d7215bdc20159193df2eb2a89fb94403996092ac" +checksum = "57c17d8f6524fdca4471101dd71f0a132eb6382b5d6d7f2970441cb25f6f435a" dependencies = [ "displaydoc", "icu_locid", @@ -359,15 +393,15 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d8151979828a4645be945302e05c903cbb5c4a86a936965f7605bd5142e06" +checksum = "545c6c3e8bf9580e2dafee8de6f9ec14826aaf359787789c7724f1f85f47d3dc" [[package]] name = "icu_normalizer" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dbdf1b59783a3c7d5fa3f7be40731e1de77d87dc224918f352a115352b4e1a8" +checksum = "419a6ef743237a64c37619def388b13f6266318a24652dff91ca046a7c4afc40" dependencies = [ "displaydoc", "icu_collections", @@ -383,15 +417,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d38769c980bc60be520708cdda18e54ab2724e7c5173f58a13ac045933851d8" +checksum = "22026918a80e6a9a330cb01b60f950e2b4e5284c59528fd0c6150076ef4c8522" [[package]] name = "icu_properties" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00a2167b0f6a44eebc1f8153e742ce07c6218d5be61eac92b9744b086b802cfc" +checksum = "976e296217453af983efa25f287a4c1da04b9a63bf1ed63719455068e4453eb5" dependencies = [ "displaydoc", "icu_collections", @@ -404,15 +438,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7f4134189d15b08d2abf068c444ba408fdc125d00d4dbd9176765a190023c2" +checksum = "f6a86c0e384532b06b6c104814f9c1b13bcd5b64409001c0d05713a1f3529d99" [[package]] name = "icu_provider" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d3810a06fce5c900f8ace41b72abf8f6308f77c9e7647211aa5f121c0c9f43" +checksum = "ba58e782287eb6950247abbf11719f83f5d4e4a5c1f2cd490d30a334bc47c2f4" dependencies = [ "displaydoc", "icu_locid", @@ -427,9 +461,9 @@ dependencies = [ [[package]] name = "icu_provider_macros" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca9be8af0b117ccf1516251daab4c9137c012646a211c2a02d2f568ea3cd0df4" +checksum = "d2abdd3a62551e8337af119c5899e600ca0c88ec8f23a46c60ba216c803dcf1a" dependencies = [ "proc-macro2", "quote", @@ -438,19 +472,28 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", - "hashbrown 0.14.1", + "hashbrown 0.14.3", +] + +[[package]] +name = "intrusive-collections" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b694dc9f70c3bda874626d2aed13b780f137aab435f4e9814121955cf706122e" +dependencies = [ + "memoffset", ] [[package]] name = "itertools" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" dependencies = [ "either", ] @@ -498,9 +541,9 @@ dependencies = [ [[package]] name = "litemap" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a1a2647d5b7134127971a6de0d533c49de2159167e7f259c427195f87168a1" +checksum = "f9d642685b028806386b2b6e75685faadd3eb65a85fff7df711ce18446a422da" [[package]] name = "lock_api" @@ -524,6 +567,15 @@ version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "num-bigint" version = "0.4.4" @@ -549,27 +601,27 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "num_enum" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70bf6736f74634d299d00086f02986875b3c2d924781a6a2cb6c201e73da0ceb" +checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ea360eafe1022f7cc56cd7b869ed57330fb2453d0c7831d99b74c65d2f5597" +checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -650,6 +702,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "portable-atomic" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bccab0e7fd7cc19f820a1c8c91720af652d0c88dc9664dd72aef2614f04af3b" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -747,9 +805,9 @@ checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "ryu-js" -version = "0.2.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" +checksum = "4950d85bc52415f8432144c97c4791bd0c4f7954de32a7270ee9cccd3c22b12b" [[package]] name = "scopeguard" @@ -759,18 +817,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -779,9 +837,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -820,9 +878,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "syn" -version = "2.0.37" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", @@ -855,18 +913,18 @@ checksum = "aac81b6fd6beb5884b0cf3321b8117e6e5d47ecb6fc89f414cfdcca8b2fe2dd8" [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", @@ -875,9 +933,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07bb54ef1f8ff27564b08b861144d3b8d40263efe07684f64987f4c0d044e3e" +checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece" dependencies = [ "displaydoc", "zerovec", @@ -1073,15 +1131,15 @@ checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0af0c3d13faebf8dda0b5256fa7096a2d5ccb662f7b9f54a40fe201077ab1c2" +checksum = "dad7bb64b8ef9c0aa27b6da38b452b0ee9fd82beaf276a87dd796fb55cbae14e" [[package]] name = "yoke" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e38c508604d6bbbd292dadb3c02559aa7fff6b654a078a36217cad871636e4" +checksum = "65e71b2e4f287f467794c671e2b8f8a5f3716b3c829079a1c44740148eff07e4" dependencies = [ "serde", "stable_deref_trait", @@ -1091,9 +1149,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5e19fb6ed40002bab5403ffa37e53e0e56f914a4450c8765f533018db1db35f" +checksum = "9e6936f0cce458098a201c245a11bef556c6a0181129c7034d10d76d1ec3a2b8" dependencies = [ "proc-macro2", "quote", @@ -1101,6 +1159,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d6f15f7ade05d2a4935e34a457b936c23dc70a05cc1d97133dc99e7a3fe0f0e" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbbad221e3f78500350ecbd7dfa4e63ef945c05f4c61cb7f4d3f84cd0bba649b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zerofrom" version = "0.1.3" @@ -1124,9 +1202,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1194130c5b155bf8ae50ab16c86ab758cd695cf9ad176d2f870b744cbdbb572e" +checksum = "eff4439ae91fb5c72b8abc12f3f2dbf51bd27e6eadb9f8a5bc8898dddb0e27ea" dependencies = [ "yoke", "zerofrom", @@ -1135,9 +1213,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acabf549809064225ff8878baedc4ce3732ac3b07e7c7ce6e5c2ccdbc485c324" +checksum = "7b4e5997cbf58990550ef1f0e5124a05e47e1ebd33a84af25739be6031a62c20" dependencies = [ "proc-macro2", "quote", From 2b34732377f992e3c1f2f6264deb62e71d14f85f Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 13:27:39 -0600 Subject: [PATCH 09/15] Decrease used space percentage for GC --- boa_gc/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boa_gc/src/lib.rs b/boa_gc/src/lib.rs index 0be00ade7c9..59cfdbad22e 100644 --- a/boa_gc/src/lib.rs +++ b/boa_gc/src/lib.rs @@ -64,7 +64,7 @@ impl Default for GcConfig { fn default() -> Self { Self { threshold: 1024, - used_space_percentage: 80, + used_space_percentage: 70, } } } From 438a53af8acf0c1ee911c7d004ff05bde4bac8c8 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 13:47:44 -0600 Subject: [PATCH 10/15] cargo clippy --- .../src/builtins/array/array_iterator.rs | 2 +- boa_engine/src/builtins/array_buffer/mod.rs | 2 +- .../src/builtins/array_buffer/shared.rs | 2 +- boa_engine/src/builtins/atomics/mod.rs | 2 +- boa_engine/src/builtins/dataview/mod.rs | 10 +-- boa_engine/src/builtins/date/mod.rs | 2 +- boa_engine/src/builtins/function/arguments.rs | 2 + boa_engine/src/builtins/intl/collator/mod.rs | 2 +- .../src/builtins/intl/segmenter/iterator.rs | 2 +- boa_engine/src/builtins/intl/segmenter/mod.rs | 2 +- .../src/builtins/intl/segmenter/segments.rs | 2 +- .../iterable/async_from_sync_iterator.rs | 6 +- boa_engine/src/builtins/map/map_iterator.rs | 2 +- .../src/builtins/object/for_in_iterator.rs | 2 +- boa_engine/src/builtins/regexp/mod.rs | 68 ++++++++++--------- .../builtins/regexp/regexp_string_iterator.rs | 2 +- boa_engine/src/builtins/regexp/tests.rs | 6 +- boa_engine/src/builtins/set/mod.rs | 10 +-- boa_engine/src/builtins/set/set_iterator.rs | 2 +- .../src/builtins/string/string_iterator.rs | 2 +- .../src/builtins/temporal/calendar/mod.rs | 64 ++++++++--------- .../src/builtins/temporal/duration/mod.rs | 19 +++--- .../src/builtins/temporal/instant/mod.rs | 20 +++--- .../src/builtins/temporal/time_zone/mod.rs | 8 +-- boa_engine/src/builtins/weak/weak_ref.rs | 8 +-- boa_engine/src/builtins/weak_map/mod.rs | 8 +-- boa_engine/src/builtins/weak_set/mod.rs | 6 +- boa_engine/src/object/jsobject.rs | 13 ++-- boa_engine/src/object/mod.rs | 10 ++- boa_macros/src/lib.rs | 1 + 30 files changed, 146 insertions(+), 141 deletions(-) diff --git a/boa_engine/src/builtins/array/array_iterator.rs b/boa_engine/src/builtins/array/array_iterator.rs index 4548b5300c8..7525ba3b582 100644 --- a/boa_engine/src/builtins/array/array_iterator.rs +++ b/boa_engine/src/builtins/array/array_iterator.rs @@ -105,7 +105,7 @@ impl ArrayIterator { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let mut array_iterator = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an ArrayIterator"))?; let index = array_iterator.next_index; if array_iterator.done { diff --git a/boa_engine/src/builtins/array_buffer/mod.rs b/boa_engine/src/builtins/array_buffer/mod.rs index 6d896d4f211..65689c08047 100644 --- a/boa_engine/src/builtins/array_buffer/mod.rs +++ b/boa_engine/src/builtins/array_buffer/mod.rs @@ -259,7 +259,7 @@ impl ArrayBuffer { // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let buf = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("ArrayBuffer.byteLength called with non `ArrayBuffer` object") diff --git a/boa_engine/src/builtins/array_buffer/shared.rs b/boa_engine/src/builtins/array_buffer/shared.rs index c7eb0e48447..128bb4daa5e 100644 --- a/boa_engine/src/builtins/array_buffer/shared.rs +++ b/boa_engine/src/builtins/array_buffer/shared.rs @@ -158,7 +158,7 @@ impl SharedArrayBuffer { // 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. let buf = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("SharedArrayBuffer.byteLength called with invalid value") diff --git a/boa_engine/src/builtins/atomics/mod.rs b/boa_engine/src/builtins/atomics/mod.rs index a3b7c72c518..246ed7ad271 100644 --- a/boa_engine/src/builtins/atomics/mod.rs +++ b/boa_engine/src/builtins/atomics/mod.rs @@ -521,7 +521,7 @@ fn validate_integer_typed_array( // 2. Perform ? ValidateTypedArray(typedArray). let ii = array .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("value is not a typed array object"))?; if ii.is_detached() { return Err(JsNativeError::typ() diff --git a/boa_engine/src/builtins/dataview/mod.rs b/boa_engine/src/builtins/dataview/mod.rs index 05ad748a529..2b7c6d31257 100644 --- a/boa_engine/src/builtins/dataview/mod.rs +++ b/boa_engine/src/builtins/dataview/mod.rs @@ -245,7 +245,7 @@ impl DataView { // 2. Perform ? RequireInternalSlot(O, [[DataView]]). let view = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. @@ -273,7 +273,7 @@ impl DataView { // 2. Perform ? RequireInternalSlot(O, [[DataView]]). let view = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. @@ -313,7 +313,7 @@ impl DataView { // 2. Perform ? RequireInternalSlot(O, [[DataView]]). let view = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Assert: O has a [[ViewedArrayBuffer]] internal slot. // 4. Let buffer be O.[[ViewedArrayBuffer]]. @@ -353,7 +353,7 @@ impl DataView { // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. let view = view .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; @@ -664,7 +664,7 @@ impl DataView { // 2. Assert: view has a [[ViewedArrayBuffer]] internal slot. let view = view .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a DataView"))?; // 3. Let getIndex be ? ToIndex(requestIndex). let get_index = request_index.to_index(context)?; diff --git a/boa_engine/src/builtins/date/mod.rs b/boa_engine/src/builtins/date/mod.rs index ca6cca1d911..0727632e041 100644 --- a/boa_engine/src/builtins/date/mod.rs +++ b/boa_engine/src/builtins/date/mod.rs @@ -50,7 +50,7 @@ macro_rules! some_or_nan { macro_rules! get_mut_date { ($val:expr) => { $val.as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("'this' is not a Date"))? }; } diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index 3746b6250e6..7168e3d82ae 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -25,6 +25,7 @@ impl UnmappedArguments { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-createunmappedargumentsobject + #[allow(clippy::new_ret_no_self)] pub(crate) fn new(arguments_list: &[JsValue], context: &mut Context) -> JsObject { // 1. Let len be the number of elements in argumentsList. let len = arguments_list.len(); @@ -139,6 +140,7 @@ impl MappedArguments { /// Creates a new mapped Arguments exotic object. /// /// + #[allow(clippy::new_ret_no_self)] pub(crate) fn new( func: &JsObject, formals: &FormalParameterList, diff --git a/boa_engine/src/builtins/intl/collator/mod.rs b/boa_engine/src/builtins/intl/collator/mod.rs index 9f95dd17055..a6520efeb79 100644 --- a/boa_engine/src/builtins/intl/collator/mod.rs +++ b/boa_engine/src/builtins/intl/collator/mod.rs @@ -478,7 +478,7 @@ impl Collator { // 2. Perform ? RequireInternalSlot(collator, [[InitializedCollator]]). let collator = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("`resolvedOptions` can only be called on a `Collator` object") diff --git a/boa_engine/src/builtins/intl/segmenter/iterator.rs b/boa_engine/src/builtins/intl/segmenter/iterator.rs index 2777e78ede5..b7e6dfe133f 100644 --- a/boa_engine/src/builtins/intl/segmenter/iterator.rs +++ b/boa_engine/src/builtins/intl/segmenter/iterator.rs @@ -104,7 +104,7 @@ impl SegmentIterator { // 2. Perform ? RequireInternalSlot(iterator, [[IteratingSegmenter]]). let mut iter = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| { JsNativeError::typ() .with_message("`next` can only be called on a `Segment Iterator` object") diff --git a/boa_engine/src/builtins/intl/segmenter/mod.rs b/boa_engine/src/builtins/intl/segmenter/mod.rs index 0271510f538..279c3052749 100644 --- a/boa_engine/src/builtins/intl/segmenter/mod.rs +++ b/boa_engine/src/builtins/intl/segmenter/mod.rs @@ -226,7 +226,7 @@ impl Segmenter { // 2. Perform ? RequireInternalSlot(segmenter, [[InitializedSegmenter]]). let segmenter = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message( "`resolved_options` can only be called on an `Intl.Segmenter` object", diff --git a/boa_engine/src/builtins/intl/segmenter/segments.rs b/boa_engine/src/builtins/intl/segmenter/segments.rs index 4209c366386..7710904cff0 100644 --- a/boa_engine/src/builtins/intl/segmenter/segments.rs +++ b/boa_engine/src/builtins/intl/segmenter/segments.rs @@ -58,7 +58,7 @@ impl Segments { // 2. Perform ? RequireInternalSlot(segments, [[SegmentsSegmenter]]). let segments = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("`containing` can only be called on a `Segments` object") diff --git a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs index af0ed1c54ec..94fe5fbdb52 100644 --- a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -100,7 +100,7 @@ impl AsyncFromSyncIterator { // 4. Let syncIteratorRecord be O.[[SyncIteratorRecord]]. let sync_iterator_record = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .expect("async from sync iterator prototype must be object") .sync_iterator_record .clone(); @@ -145,7 +145,7 @@ impl AsyncFromSyncIterator { // 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]]. let sync_iterator = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .expect("async from sync iterator prototype must be object") .sync_iterator_record .iterator() @@ -215,7 +215,7 @@ impl AsyncFromSyncIterator { // 4. Let syncIterator be O.[[SyncIteratorRecord]].[[Iterator]]. let sync_iterator = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .expect("async from sync iterator prototype must be object") .sync_iterator_record .iterator() diff --git a/boa_engine/src/builtins/map/map_iterator.rs b/boa_engine/src/builtins/map/map_iterator.rs index 32fd89d5aaa..5a6a1ed4017 100644 --- a/boa_engine/src/builtins/map/map_iterator.rs +++ b/boa_engine/src/builtins/map/map_iterator.rs @@ -110,7 +110,7 @@ impl MapIterator { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let mut map_iterator = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a MapIterator"))?; let item_kind = map_iterator.map_iteration_kind; diff --git a/boa_engine/src/builtins/object/for_in_iterator.rs b/boa_engine/src/builtins/object/for_in_iterator.rs index 98af4e1eaf7..cbaf5adf0b7 100644 --- a/boa_engine/src/builtins/object/for_in_iterator.rs +++ b/boa_engine/src/builtins/object/for_in_iterator.rs @@ -100,7 +100,7 @@ impl ForInIterator { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let mut iterator = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not a ForInIterator"))?; let mut object = iterator.object.to_object(context)?; loop { diff --git a/boa_engine/src/builtins/regexp/mod.rs b/boa_engine/src/builtins/regexp/mod.rs index c46b4392c8c..eb7c62cdb27 100644 --- a/boa_engine/src/builtins/regexp/mod.rs +++ b/boa_engine/src/builtins/regexp/mod.rs @@ -211,42 +211,44 @@ impl BuiltInConstructor for RegExp { } // 4. If pattern is an Object and pattern has a [[RegExpMatcher]] internal slot, then - let (p, f) = - if let Some(pattern) = pattern.as_object().and_then(|o| o.downcast_ref::()) { - // a. Let P be pattern.[[OriginalSource]]. - let p = pattern.original_source.clone().into(); - - // b. If flags is undefined, let F be pattern.[[OriginalFlags]]. - let f = if flags.is_undefined() { - pattern.original_flags.clone().into() - // c. Else, let F be flags. - } else { - flags.clone() - }; - - (p, f) - } else if let Some(pattern) = pattern_is_regexp { - // a. Let P be ? Get(pattern, "source"). - let p = pattern.get(js_string!("source"), context)?; - - // b. If flags is undefined, then - let f = if flags.is_undefined() { - // i. Let F be ? Get(pattern, "flags"). - pattern.get(js_string!("flags"), context)? - // c. Else, - } else { - // i. Let F be flags. - flags.clone() - }; + let (p, f) = if let Some(pattern) = pattern + .as_object() + .and_then(JsObject::downcast_ref::) + { + // a. Let P be pattern.[[OriginalSource]]. + let p = pattern.original_source.clone().into(); - (p, f) - // 6. Else, + // b. If flags is undefined, let F be pattern.[[OriginalFlags]]. + let f = if flags.is_undefined() { + pattern.original_flags.clone().into() + // c. Else, let F be flags. } else { - // a. Let P be pattern. - // b. Let F be flags. - (pattern.clone(), flags.clone()) + flags.clone() }; + (p, f) + } else if let Some(pattern) = pattern_is_regexp { + // a. Let P be ? Get(pattern, "source"). + let p = pattern.get(js_string!("source"), context)?; + + // b. If flags is undefined, then + let f = if flags.is_undefined() { + // i. Let F be ? Get(pattern, "flags"). + pattern.get(js_string!("flags"), context)? + // c. Else, + } else { + // i. Let F be flags. + flags.clone() + }; + + (p, f) + // 6. Else, + } else { + // a. Let P be pattern. + // b. Let F be flags. + (pattern.clone(), flags.clone()) + }; + // 7. Let O be ? RegExpAlloc(newTarget). let proto = get_prototype_from_constructor(new_target, StandardConstructors::regexp, context)?; @@ -1269,7 +1271,7 @@ impl RegExp { pub(crate) fn to_string(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let (body, flags) = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .map(|rx| (rx.original_source.clone(), rx.original_flags.clone())) .ok_or_else(|| { JsNativeError::typ().with_message(format!( diff --git a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs index 5ab9e484358..79772ab710a 100644 --- a/boa_engine/src/builtins/regexp/regexp_string_iterator.rs +++ b/boa_engine/src/builtins/regexp/regexp_string_iterator.rs @@ -126,7 +126,7 @@ impl RegExpStringIterator { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let mut iterator = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| { JsNativeError::typ().with_message("`this` is not a RegExpStringIterator") })?; diff --git a/boa_engine/src/builtins/regexp/tests.rs b/boa_engine/src/builtins/regexp/tests.rs index b4c8642441e..710234d91b5 100644 --- a/boa_engine/src/builtins/regexp/tests.rs +++ b/boa_engine/src/builtins/regexp/tests.rs @@ -1,6 +1,6 @@ use crate::{ - js_string, native_function::NativeFunctionObject, run_test_actions, JsNativeErrorKind, JsValue, - TestAction, + js_string, native_function::NativeFunctionObject, run_test_actions, JsNativeErrorKind, + JsObject, JsValue, TestAction, }; use indoc::indoc; @@ -44,7 +44,7 @@ fn species() { TestAction::assert_eq("descriptor.set", JsValue::undefined()), TestAction::assert_with_op("accessor", |v, _| { v.as_object() - .map_or(false, |o| o.is::()) + .map_or(false, JsObject::is::) }), TestAction::assert("!descriptor.enumerable"), TestAction::assert("descriptor.configurable"), diff --git a/boa_engine/src/builtins/set/mod.rs b/boa_engine/src/builtins/set/mod.rs index 855278dee91..4c3d52d208e 100644 --- a/boa_engine/src/builtins/set/mod.rs +++ b/boa_engine/src/builtins/set/mod.rs @@ -231,7 +231,7 @@ impl Set { // 2. Perform ? RequireInternalSlot(S, [[SetData]]). let Some(mut set) = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.add called on incompatible receiver") @@ -268,7 +268,7 @@ impl Set { pub(crate) fn clear(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let Some(mut set) = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) else { return Err(JsNativeError::typ() .with_message("'this' is not a Set") @@ -298,7 +298,7 @@ impl Set { // 2. Perform ? RequireInternalSlot(S, [[SetData]]). let Some(mut set) = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.delete called on incompatible receiver") @@ -396,7 +396,7 @@ impl Set { // a. Let e be entries[index]. let Some(set) = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.forEach called on incompatible receiver") @@ -445,7 +445,7 @@ impl Set { // 2. Perform ? RequireInternalSlot(S, [[SetData]]). let Some(set) = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) else { return Err(JsNativeError::typ() .with_message("Method Set.prototype.has called on incompatible receiver") diff --git a/boa_engine/src/builtins/set/set_iterator.rs b/boa_engine/src/builtins/set/set_iterator.rs index decc305f397..6926b9fac36 100644 --- a/boa_engine/src/builtins/set/set_iterator.rs +++ b/boa_engine/src/builtins/set/set_iterator.rs @@ -107,7 +107,7 @@ impl SetIterator { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let mut set_iterator = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an SetIterator"))?; // The borrow checker cannot see that we're splitting the `GcRefMut` in two diff --git a/boa_engine/src/builtins/string/string_iterator.rs b/boa_engine/src/builtins/string/string_iterator.rs index 89dc1d6e754..aeafafff73d 100644 --- a/boa_engine/src/builtins/string/string_iterator.rs +++ b/boa_engine/src/builtins/string/string_iterator.rs @@ -78,7 +78,7 @@ impl StringIterator { pub(crate) fn next(this: &JsValue, _: &[JsValue], context: &mut Context) -> JsResult { let mut string_iterator = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| JsNativeError::typ().with_message("`this` is not an ArrayIterator"))?; if string_iterator.string.is_empty() { diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index 5517e881ce2..d3a92f1dc8d 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -190,7 +190,7 @@ impl Calendar { // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -273,7 +273,7 @@ impl Calendar { ) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -351,7 +351,7 @@ impl Calendar { // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -424,7 +424,7 @@ impl Calendar { // 3. Assert: calendar.[[Identifier]] is "iso8601". let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -466,7 +466,7 @@ impl Calendar { // 3. Assert: calendar.[[Identifier]] is "iso8601". let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -505,7 +505,7 @@ impl Calendar { fn era(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -529,7 +529,7 @@ impl Calendar { fn era_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -553,7 +553,7 @@ impl Calendar { fn year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -575,7 +575,7 @@ impl Calendar { fn month(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -602,7 +602,7 @@ impl Calendar { fn month_code(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -624,7 +624,7 @@ impl Calendar { fn day(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -648,7 +648,7 @@ impl Calendar { // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -671,7 +671,7 @@ impl Calendar { fn day_of_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -694,7 +694,7 @@ impl Calendar { fn week_of_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -717,7 +717,7 @@ impl Calendar { fn year_of_week(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -740,7 +740,7 @@ impl Calendar { fn days_in_week(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -763,7 +763,7 @@ impl Calendar { fn days_in_month(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -785,7 +785,7 @@ impl Calendar { fn days_in_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -810,7 +810,7 @@ impl Calendar { ) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -832,7 +832,7 @@ impl Calendar { fn in_leap_year(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -856,7 +856,7 @@ impl Calendar { // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -946,7 +946,7 @@ impl Calendar { // 2. Perform ? RequireInternalSlot(calendar, [[InitializedTemporalCalendar]]). let calendar = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ() .with_message("this value of Calendar must be a Calendar object.") @@ -1069,11 +1069,11 @@ pub(crate) fn create_temporal_calendar( fn extract_from_temporal_type( object: &JsObject, - df: DF, - dtf: DTF, - ymf: YMF, - mdf: MDF, - zdtf: ZDTF, + date_f: DF, + datetime_f: DTF, + year_month_f: YMF, + month_day_f: MDF, + zoned_datetime_f: ZDTF, ) -> JsResult> where DF: FnOnce(&PlainDate) -> JsResult>, @@ -1085,15 +1085,15 @@ where let o = object.borrow(); if let Some(date) = o.downcast_ref::() { - return df(date); + return date_f(date); } else if let Some(dt) = o.downcast_ref::() { - return dtf(dt); + return datetime_f(dt); } else if let Some(ym) = o.downcast_ref::() { - return ymf(ym); + return year_month_f(ym); } else if let Some(md) = o.downcast_ref::() { - return mdf(md); + return month_day_f(md); } else if let Some(dt) = o.downcast_ref::() { - return zdtf(dt); + return zoned_datetime_f(dt); } Ok(None) diff --git a/boa_engine/src/builtins/temporal/duration/mod.rs b/boa_engine/src/builtins/temporal/duration/mod.rs index 7022a249bd1..7cd5423163e 100644 --- a/boa_engine/src/builtins/temporal/duration/mod.rs +++ b/boa_engine/src/builtins/temporal/duration/mod.rs @@ -269,7 +269,7 @@ impl Duration { fn get_internal_field(this: &JsValue, field: &DateTimeValues) -> JsResult { let duration = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Duration object.") })?; @@ -349,7 +349,7 @@ impl Duration { // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). let duration = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Duration object.") })?; @@ -366,7 +366,7 @@ impl Duration { // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). let duration = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Duration object.") })?; @@ -398,7 +398,7 @@ impl Duration { // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). let duration = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Duration object.") })?; @@ -544,7 +544,7 @@ impl Duration { // abs(duration.[[Seconds]]), abs(duration.[[Milliseconds]]), abs(duration.[[Microseconds]]), abs(duration.[[Nanoseconds]])). let duration = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Duration object.") })?; @@ -579,7 +579,7 @@ impl Duration { // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). let duration = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Duration object.") })?; @@ -800,7 +800,7 @@ impl Duration { // 2. Perform ? RequireInternalSlot(duration, [[InitializedTemporalDuration]]). let _duration = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Duration object.") })?; @@ -879,7 +879,10 @@ impl Duration { pub(crate) fn to_temporal_duration(item: &JsValue) -> JsResult { // 1a. If Type(item) is Object // 1b. and item has an [[InitializedTemporalDuration]] internal slot, then - if let Some(duration) = item.as_object().and_then(|o| o.downcast_ref::()) { + if let Some(duration) = item + .as_object() + .and_then(JsObject::downcast_ref::) + { return Ok(duration.inner); } diff --git a/boa_engine/src/builtins/temporal/instant/mod.rs b/boa_engine/src/builtins/temporal/instant/mod.rs index 8c6671ae84f..31d368fbcdf 100644 --- a/boa_engine/src/builtins/temporal/instant/mod.rs +++ b/boa_engine/src/builtins/temporal/instant/mod.rs @@ -154,7 +154,7 @@ impl Instant { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). let instant = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be an instant object.") })?; @@ -176,7 +176,7 @@ impl Instant { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). let instant = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be an instant object.") })?; @@ -198,7 +198,7 @@ impl Instant { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). let instant = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be an instant object.") })?; @@ -223,7 +223,7 @@ impl Instant { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). let instant = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be an instant object.") })?; @@ -243,7 +243,7 @@ impl Instant { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). let instant = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be an instant object.") })?; @@ -263,7 +263,7 @@ impl Instant { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). let instant = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be an instant object.") })?; @@ -283,7 +283,7 @@ impl Instant { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). let instant = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be an instant object.") })?; @@ -304,7 +304,7 @@ impl Instant { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). let instant = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be an instant object.") })?; @@ -325,7 +325,7 @@ impl Instant { // 2. Perform ? RequireInternalSlot(instant, [[InitializedTemporalInstant]]). let instant = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be an instant object.") })?; @@ -425,7 +425,7 @@ impl Instant { // 5. Return true. let instant = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be an instant object.") })?; diff --git a/boa_engine/src/builtins/temporal/time_zone/mod.rs b/boa_engine/src/builtins/temporal/time_zone/mod.rs index 76c1044ff92..6407dc76ec8 100644 --- a/boa_engine/src/builtins/temporal/time_zone/mod.rs +++ b/boa_engine/src/builtins/temporal/time_zone/mod.rs @@ -136,7 +136,7 @@ impl TimeZone { pub(crate) fn get_id(this: &JsValue, _: &[JsValue], _: &mut Context) -> JsResult { let tz = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") })?; @@ -152,7 +152,7 @@ impl TimeZone { // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). let _tz = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") })?; @@ -177,7 +177,7 @@ impl TimeZone { // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). let _tz = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") })?; @@ -247,7 +247,7 @@ impl TimeZone { // 2. Perform ? RequireInternalSlot(timeZone, [[InitializedTemporalTimeZone]]). let tz = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("this value must be a Temporal.TimeZone") })?; diff --git a/boa_engine/src/builtins/weak/weak_ref.rs b/boa_engine/src/builtins/weak/weak_ref.rs index f4d8866ee9b..de4a9e0d862 100644 --- a/boa_engine/src/builtins/weak/weak_ref.rs +++ b/boa_engine/src/builtins/weak/weak_ref.rs @@ -110,11 +110,11 @@ impl WeakRef { // 2. Perform ? RequireInternalSlot(weakRef, [[WeakRefTarget]]). let weak_ref = this .as_object() - .and_then(|o| o.downcast_ref::>()) + .and_then(JsObject::downcast_ref::>) .ok_or_else(|| { - JsNativeError::typ().with_message(format!( - "WeakRef.prototype.deref: expected `this` to be a `WeakRef` object" - )) + JsNativeError::typ().with_message( + "WeakRef.prototype.deref: expected `this` to be a `WeakRef` object", + ) })?; // 3. Return WeakRefDeref(weakRef). diff --git a/boa_engine/src/builtins/weak_map/mod.rs b/boa_engine/src/builtins/weak_map/mod.rs index cafca94b5ce..6b2905b11fc 100644 --- a/boa_engine/src/builtins/weak_map/mod.rs +++ b/boa_engine/src/builtins/weak_map/mod.rs @@ -132,7 +132,7 @@ impl WeakMap { // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). let mut map = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| { JsNativeError::typ().with_message("WeakMap.delete: called with non-object value") })?; @@ -169,7 +169,7 @@ impl WeakMap { // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). let map = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("WeakMap.get: called with non-object value") })?; @@ -203,7 +203,7 @@ impl WeakMap { // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). let map = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("WeakMap.has: called with non-object value") })?; @@ -237,7 +237,7 @@ impl WeakMap { // 2. Perform ? RequireInternalSlot(M, [[WeakMapData]]). let mut map = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| { JsNativeError::typ().with_message("WeakMap.set: called with non-object value") })?; diff --git a/boa_engine/src/builtins/weak_set/mod.rs b/boa_engine/src/builtins/weak_set/mod.rs index 7d6ba9595d4..3bcf87d202f 100644 --- a/boa_engine/src/builtins/weak_set/mod.rs +++ b/boa_engine/src/builtins/weak_set/mod.rs @@ -144,7 +144,7 @@ impl WeakSet { // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). let mut set = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| { JsNativeError::typ().with_message("WeakSet.add: called with non-object value") })?; @@ -193,7 +193,7 @@ impl WeakSet { // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). let mut set = this .as_object() - .and_then(|o| o.downcast_mut::()) + .and_then(JsObject::downcast_mut::) .ok_or_else(|| { JsNativeError::typ().with_message("WeakSet.delete: called with non-object value") })?; @@ -232,7 +232,7 @@ impl WeakSet { // 2. Perform ? RequireInternalSlot(S, [[WeakSetData]]). let set = this .as_object() - .and_then(|o| o.downcast_ref::()) + .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("WeakSet.has: called with non-object value") })?; diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index b99e0318b3f..9679eb98d28 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -332,7 +332,7 @@ impl JsObject { #[must_use] #[track_caller] pub fn downcast_ref(&self) -> Option> { - Ref::try_map(self.borrow(), |x| x.downcast_ref()) + Ref::try_map(self.borrow(), ErasedObject::downcast_ref) } /// Downcast a mutable reference to the object, @@ -344,7 +344,7 @@ impl JsObject { #[must_use] #[track_caller] pub fn downcast_mut(&self) -> Option> { - RefMut::try_map(self.borrow_mut(), |x| x.downcast_mut()) + RefMut::try_map(self.borrow_mut(), ErasedObject::downcast_mut) } /// Get the prototype of the object. @@ -664,7 +664,7 @@ Cannot both specify accessors and a value or writable attribute", /// Create a new private name with this object as the unique identifier. pub(crate) fn private_name(&self, description: JsString) -> PrivateName { let ptr: *const _ = self.as_ref(); - PrivateName::new(description, (ptr as *const ()) as usize) + PrivateName::new(description, ptr.cast::<()>() as usize) } } @@ -781,9 +781,8 @@ impl RecursionLimiter { /// visited. The first `T` visited will clear the hashset, while any others will check if they are contained /// by the hashset. pub fn new(o: &T) -> Self { - // We shouldn't have to worry too much about this being moved during Debug::fmt. - #[allow(trivial_casts)] - let ptr = (o as *const _ as *const ()) as usize; + let ptr: *const _ = o; + let ptr = ptr.cast::<()>() as usize; let (top_level, visited, live) = SEEN.with(|hm| { let mut hm = hm.borrow_mut(); let top_level = hm.is_empty(); @@ -817,7 +816,7 @@ impl Debug for JsObject { // at most once, hopefully making things a bit clearer. if !limiter.visited && !limiter.live { let ptr: *const _ = self.as_ref(); - let ptr = ptr as *const (); + let ptr = ptr.cast::<()>(); let obj = self.borrow(); let kind = obj.data.type_name_of_value(); if obj.is::() { diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index f1f8b1c29dc..42bff06dca4 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -369,12 +369,10 @@ impl Object { #[must_use] pub(crate) fn as_buffer(&self) -> Option> { if let Some(buffer) = self.downcast_ref::() { - Some(BufferRef::Buffer(buffer)) - } else if let Some(buffer) = self.downcast_ref::() { - Some(BufferRef::SharedBuffer(buffer)) - } else { - None + return Some(BufferRef::Buffer(buffer)); } + self.downcast_ref::() + .map(BufferRef::SharedBuffer) } /// Gets the mutable buffer data if the object is an `ArrayBuffer` or a `SharedArrayBuffer`. @@ -390,7 +388,7 @@ impl Object { } self.downcast_mut::() - .map(|buf| BufferRefMut::SharedBuffer(buf)) + .map(BufferRefMut::SharedBuffer) } /// Checks if this object is an `Arguments` object. diff --git a/boa_macros/src/lib.rs b/boa_macros/src/lib.rs index cd4a40796f5..bd2e7d03707 100644 --- a/boa_macros/src/lib.rs +++ b/boa_macros/src/lib.rs @@ -175,6 +175,7 @@ decl_derive! { } /// Derives the `Trace` trait. +#[allow(clippy::too_many_lines)] fn derive_trace(mut s: Structure<'_>) -> proc_macro2::TokenStream { struct EmptyTrace { copy: bool, From 7d83079d0a63b5102771e714a15a4462e64401e8 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 15:00:26 -0600 Subject: [PATCH 11/15] Fix docs --- boa_engine/src/builtins/error/aggregate.rs | 4 +- boa_engine/src/builtins/error/eval.rs | 4 +- boa_engine/src/builtins/error/mod.rs | 7 +- boa_engine/src/builtins/error/range.rs | 4 +- boa_engine/src/builtins/error/reference.rs | 4 +- boa_engine/src/builtins/error/syntax.rs | 4 +- boa_engine/src/builtins/error/type.rs | 4 +- boa_engine/src/builtins/error/uri.rs | 4 +- boa_engine/src/builtins/object/mod.rs | 4 +- .../src/builtins/temporal/calendar/mod.rs | 8 +-- boa_engine/src/class.rs | 4 +- boa_engine/src/error.rs | 64 ++++++++++--------- boa_engine/src/object/builtins/jsmap.rs | 2 +- boa_engine/src/object/jsobject.rs | 8 +-- boa_engine/src/value/display.rs | 4 +- boa_runtime/src/lib.rs | 2 +- 16 files changed, 68 insertions(+), 63 deletions(-) diff --git a/boa_engine/src/builtins/error/aggregate.rs b/boa_engine/src/builtins/error/aggregate.rs index 7f57618c170..8abeefd3bc3 100644 --- a/boa_engine/src/builtins/error/aggregate.rs +++ b/boa_engine/src/builtins/error/aggregate.rs @@ -22,7 +22,7 @@ use crate::{ }; use boa_profiler::Profiler; -use super::{Error, ErrorKind}; +use super::{Error, ErrorObject}; #[derive(Debug, Clone, Copy)] pub(crate) struct AggregateError; @@ -85,7 +85,7 @@ impl BuiltInConstructor for AggregateError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ErrorKind::Aggregate, + ErrorObject::Aggregate, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/eval.rs b/boa_engine/src/builtins/error/eval.rs index fda408fd08b..ece3277f4a4 100644 --- a/boa_engine/src/builtins/error/eval.rs +++ b/boa_engine/src/builtins/error/eval.rs @@ -23,7 +23,7 @@ use crate::{ }; use boa_profiler::Profiler; -use super::{Error, ErrorKind}; +use super::{Error, ErrorObject}; /// JavaScript `EvalError` implementation. #[derive(Debug, Clone, Copy)] @@ -84,7 +84,7 @@ impl BuiltInConstructor for EvalError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ErrorKind::Eval, + ErrorObject::Eval, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/mod.rs b/boa_engine/src/builtins/error/mod.rs index eb525afed2f..398dafdf971 100644 --- a/boa_engine/src/builtins/error/mod.rs +++ b/boa_engine/src/builtins/error/mod.rs @@ -45,7 +45,7 @@ pub(crate) use self::uri::UriError; use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; -/// The kind of a `NativeError` object, per the [ECMAScript spec][spec]. +/// A `NativeError` object, per the [ECMAScript spec][spec]. /// /// This is used internally to convert between [`JsObject`] and /// [`JsNativeError`] correctly, but it can also be used to manually create `Error` @@ -59,7 +59,8 @@ use super::{BuiltInBuilder, BuiltInConstructor, IntrinsicObject}; /// [spec]: https://tc39.es/ecma262/#sec-error-objects #[derive(Debug, Copy, Clone, Eq, PartialEq, Trace, Finalize, JsData)] #[boa_gc(empty_trace)] -pub enum ErrorKind { +#[non_exhaustive] +pub enum ErrorObject { /// The `AggregateError` object type. /// /// More information: @@ -180,7 +181,7 @@ impl BuiltInConstructor for Error { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ErrorKind::Error, + ErrorObject::Error, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/range.rs b/boa_engine/src/builtins/error/range.rs index c784b1f4508..985269df825 100644 --- a/boa_engine/src/builtins/error/range.rs +++ b/boa_engine/src/builtins/error/range.rs @@ -21,7 +21,7 @@ use crate::{ }; use boa_profiler::Profiler; -use super::{Error, ErrorKind}; +use super::{Error, ErrorObject}; /// JavaScript `RangeError` implementation. #[derive(Debug, Clone, Copy)] @@ -82,7 +82,7 @@ impl BuiltInConstructor for RangeError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ErrorKind::Range, + ErrorObject::Range, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/reference.rs b/boa_engine/src/builtins/error/reference.rs index 1b741f98338..5785bda863a 100644 --- a/boa_engine/src/builtins/error/reference.rs +++ b/boa_engine/src/builtins/error/reference.rs @@ -21,7 +21,7 @@ use crate::{ }; use boa_profiler::Profiler; -use super::{Error, ErrorKind}; +use super::{Error, ErrorObject}; #[derive(Debug, Clone, Copy)] pub(crate) struct ReferenceError; @@ -84,7 +84,7 @@ impl BuiltInConstructor for ReferenceError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ErrorKind::Reference, + ErrorObject::Reference, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/syntax.rs b/boa_engine/src/builtins/error/syntax.rs index 19240208a88..cf35acc44c0 100644 --- a/boa_engine/src/builtins/error/syntax.rs +++ b/boa_engine/src/builtins/error/syntax.rs @@ -23,7 +23,7 @@ use crate::{ }; use boa_profiler::Profiler; -use super::{Error, ErrorKind}; +use super::{Error, ErrorObject}; /// JavaScript `SyntaxError` implementation. #[derive(Debug, Clone, Copy)] @@ -87,7 +87,7 @@ impl BuiltInConstructor for SyntaxError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ErrorKind::Syntax, + ErrorObject::Syntax, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/type.rs b/boa_engine/src/builtins/error/type.rs index 4cca5876588..0ee946070b6 100644 --- a/boa_engine/src/builtins/error/type.rs +++ b/boa_engine/src/builtins/error/type.rs @@ -29,7 +29,7 @@ use crate::{ }; use boa_profiler::Profiler; -use super::{Error, ErrorKind}; +use super::{Error, ErrorObject}; /// JavaScript `TypeError` implementation. #[derive(Debug, Clone, Copy)] @@ -90,7 +90,7 @@ impl BuiltInConstructor for TypeError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ErrorKind::Type, + ErrorObject::Type, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/error/uri.rs b/boa_engine/src/builtins/error/uri.rs index c96fc0fb6df..73e41ee9e45 100644 --- a/boa_engine/src/builtins/error/uri.rs +++ b/boa_engine/src/builtins/error/uri.rs @@ -22,7 +22,7 @@ use crate::{ }; use boa_profiler::Profiler; -use super::{Error, ErrorKind}; +use super::{Error, ErrorObject}; /// JavaScript `URIError` implementation. #[derive(Debug, Clone, Copy)] @@ -83,7 +83,7 @@ impl BuiltInConstructor for UriError { let o = JsObject::from_proto_and_data_with_shared_shape( context.root_shape(), prototype, - ErrorKind::Uri, + ErrorObject::Uri, ); // 3. If message is not undefined, then diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 6372d90c1e2..46fd85b4cc2 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -16,7 +16,7 @@ use std::ops::Deref; use super::{ - error::ErrorKind, Array, BuiltInBuilder, BuiltInConstructor, Date, IntrinsicObject, RegExp, + error::ErrorObject, Array, BuiltInBuilder, BuiltInConstructor, Date, IntrinsicObject, RegExp, }; use crate::{ builtins::{map, BuiltInObject}, @@ -850,7 +850,7 @@ impl OrdinaryObject { } else if o.is_callable() { // 7. Else if O has a [[Call]] internal method, let builtinTag be "Function". utf16!("Function") - } else if o.is::() { + } else if o.is::() { // 8. Else if O has an [[ErrorData]] internal slot, let builtinTag be "Error". utf16!("Error") } else if o.is::() { diff --git a/boa_engine/src/builtins/temporal/calendar/mod.rs b/boa_engine/src/builtins/temporal/calendar/mod.rs index d3a92f1dc8d..44daafc980b 100644 --- a/boa_engine/src/builtins/temporal/calendar/mod.rs +++ b/boa_engine/src/builtins/temporal/calendar/mod.rs @@ -1146,22 +1146,22 @@ pub(crate) fn to_temporal_calendar_slot_value( if let Some(calendar) = extract_from_temporal_type( calendar_like, |d| Ok(Some(d.inner.calendar().clone())), - |dt| { + |_dt| { Err(JsNativeError::range() .with_message("Not yet implemented.") .into()) }, - |ym| { + |_ym| { Err(JsNativeError::range() .with_message("Not yet implemented.") .into()) }, - |md| { + |_md| { Err(JsNativeError::range() .with_message("Not yet implemented.") .into()) }, - |zdt| { + |_zdt| { Err(JsNativeError::range() .with_message("Not yet implemented.") .into()) diff --git a/boa_engine/src/class.rs b/boa_engine/src/class.rs index 969d3c1a736..4939e0cddd1 100644 --- a/boa_engine/src/class.rs +++ b/boa_engine/src/class.rs @@ -11,12 +11,12 @@ //! # class::{Class, ClassBuilder}, //! # Context, JsResult, JsValue, //! # JsArgs, Source, JsObject, js_string, -//! # JsNativeError, +//! # JsNativeError, JsData, //! # }; //! # use boa_gc::{Finalize, Trace}; //! # //! // Can also be a struct containing `Trace` types. -//! #[derive(Debug, Trace, Finalize)] +//! #[derive(Debug, Trace, Finalize, JsData)] //! enum Animal { //! Cat, //! Dog, diff --git a/boa_engine/src/error.rs b/boa_engine/src/error.rs index 7c952d2b7ca..21a1c1b9ff9 100644 --- a/boa_engine/src/error.rs +++ b/boa_engine/src/error.rs @@ -3,7 +3,7 @@ use std::{error, fmt}; use crate::{ - builtins::{error::ErrorKind, Array}, + builtins::{error::ErrorObject, Array}, js_string, object::JsObject, property::PropertyDescriptor, @@ -182,12 +182,13 @@ impl JsError { /// /// ```rust /// # use boa_engine::{Context, JsError, JsNativeError}; + /// # use boa_engine::builtins::error::ErrorObject; /// let context = &mut Context::default(); /// let error: JsError = /// JsNativeError::eval().with_message("invalid script").into(); /// let error_val = error.to_opaque(context); /// - /// assert!(error_val.as_object().unwrap().borrow().is_error()); + /// assert!(error_val.as_object().unwrap().is::()); /// ``` pub fn to_opaque(&self, context: &mut Context) -> JsValue { match &self.inner { @@ -242,7 +243,7 @@ impl JsError { .as_object() .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; let error = *obj - .downcast_ref::() + .downcast_ref::() .ok_or_else(|| TryNativeError::NotAnErrorObject(val.clone()))?; let try_get_property = |key: JsString, name, context: &mut Context| { @@ -275,14 +276,14 @@ impl JsError { let cause = try_get_property(js_string!("cause"), "cause", context)?; let kind = match error { - ErrorKind::Error => JsNativeErrorKind::Error, - ErrorKind::Eval => JsNativeErrorKind::Eval, - ErrorKind::Type => JsNativeErrorKind::Type, - ErrorKind::Range => JsNativeErrorKind::Range, - ErrorKind::Reference => JsNativeErrorKind::Reference, - ErrorKind::Syntax => JsNativeErrorKind::Syntax, - ErrorKind::Uri => JsNativeErrorKind::Uri, - ErrorKind::Aggregate => { + ErrorObject::Error => JsNativeErrorKind::Error, + ErrorObject::Eval => JsNativeErrorKind::Eval, + ErrorObject::Type => JsNativeErrorKind::Type, + ErrorObject::Range => JsNativeErrorKind::Range, + ErrorObject::Reference => JsNativeErrorKind::Reference, + ErrorObject::Syntax => JsNativeErrorKind::Syntax, + ErrorObject::Uri => JsNativeErrorKind::Uri, + ErrorObject::Aggregate => { let errors = obj.get(utf16!("errors"), context).map_err(|e| { TryNativeError::InaccessibleProperty { property: "errors", @@ -892,12 +893,13 @@ impl JsNativeError { /// /// ```rust /// # use boa_engine::{Context, JsError, JsNativeError, js_string}; + /// # use boa_engine::builtins::error::ErrorObject; /// let context = &mut Context::default(); /// /// let error = JsNativeError::error().with_message("error!"); /// let error_obj = error.to_opaque(context); /// - /// assert!(error_obj.borrow().is_error()); + /// assert!(error_obj.is::()); /// assert_eq!( /// error_obj.get(js_string!("message"), context).unwrap(), /// js_string!("error!").into() @@ -922,20 +924,22 @@ impl JsNativeError { let (prototype, tag) = match kind { JsNativeErrorKind::Aggregate(_) => ( constructors.aggregate_error().prototype(), - ErrorKind::Aggregate, + ErrorObject::Aggregate, ), - JsNativeErrorKind::Error => (constructors.error().prototype(), ErrorKind::Error), - JsNativeErrorKind::Eval => (constructors.eval_error().prototype(), ErrorKind::Eval), - JsNativeErrorKind::Range => (constructors.range_error().prototype(), ErrorKind::Range), + JsNativeErrorKind::Error => (constructors.error().prototype(), ErrorObject::Error), + JsNativeErrorKind::Eval => (constructors.eval_error().prototype(), ErrorObject::Eval), + JsNativeErrorKind::Range => { + (constructors.range_error().prototype(), ErrorObject::Range) + } JsNativeErrorKind::Reference => ( constructors.reference_error().prototype(), - ErrorKind::Reference, + ErrorObject::Reference, ), JsNativeErrorKind::Syntax => { - (constructors.syntax_error().prototype(), ErrorKind::Syntax) + (constructors.syntax_error().prototype(), ErrorObject::Syntax) } - JsNativeErrorKind::Type => (constructors.type_error().prototype(), ErrorKind::Type), - JsNativeErrorKind::Uri => (constructors.uri_error().prototype(), ErrorKind::Uri), + JsNativeErrorKind::Type => (constructors.type_error().prototype(), ErrorObject::Type), + JsNativeErrorKind::Uri => (constructors.uri_error().prototype(), ErrorObject::Uri), #[cfg(feature = "fuzz")] JsNativeErrorKind::NoInstructionsRemain => { unreachable!( @@ -1140,18 +1144,18 @@ impl JsNativeErrorKind { } } -impl PartialEq for JsNativeErrorKind { - fn eq(&self, other: &ErrorKind) -> bool { +impl PartialEq for JsNativeErrorKind { + fn eq(&self, other: &ErrorObject) -> bool { matches!( (self, other), - (Self::Aggregate(_), ErrorKind::Aggregate) - | (Self::Error, ErrorKind::Error) - | (Self::Eval, ErrorKind::Eval) - | (Self::Range, ErrorKind::Range) - | (Self::Reference, ErrorKind::Reference) - | (Self::Syntax, ErrorKind::Syntax) - | (Self::Type, ErrorKind::Type) - | (Self::Uri, ErrorKind::Uri) + (Self::Aggregate(_), ErrorObject::Aggregate) + | (Self::Error, ErrorObject::Error) + | (Self::Eval, ErrorObject::Eval) + | (Self::Range, ErrorObject::Range) + | (Self::Reference, ErrorObject::Reference) + | (Self::Syntax, ErrorObject::Syntax) + | (Self::Type, ErrorObject::Type) + | (Self::Uri, ErrorObject::Uri) ) } } diff --git a/boa_engine/src/object/builtins/jsmap.rs b/boa_engine/src/object/builtins/jsmap.rs index 19347d4d5d6..f462ceb4336 100644 --- a/boa_engine/src/object/builtins/jsmap.rs +++ b/boa_engine/src/object/builtins/jsmap.rs @@ -153,7 +153,7 @@ impl JsMap { /// // `some_object` can be any JavaScript `Map` object. /// let some_object = JsObject::from_proto_and_data( /// context.intrinsics().constructors().map().prototype(), - /// ObjectData::map(OrderedMap::new()), + /// OrderedMap::::new(), /// ); /// /// // Create `JsMap` object with incoming object. diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 9679eb98d28..dbe0a036740 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -118,8 +118,8 @@ impl JsObject { /// Creates a new object with the provided prototype and object data. /// /// This is equivalent to calling the specification's abstract operation [`OrdinaryObjectCreate`], - /// with the difference that the `additionalInternalSlotsList` parameter is automatically set by - /// the [`ObjectData`] provided. + /// with the difference that the `additionalInternalSlotsList` parameter is determined by + /// the provided `data`. /// /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate pub fn from_proto_and_data>, T: NativeObject>( @@ -151,8 +151,8 @@ impl JsObject { /// Creates a new object with the provided prototype and object data. /// /// This is equivalent to calling the specification's abstract operation [`OrdinaryObjectCreate`], - /// with the difference that the `additionalInternalSlotsList` parameter is automatically set by - /// the [`ObjectData`] provided. + /// with the difference that the `additionalInternalSlotsList` parameter is determined by + /// the provided `data`. /// /// [`OrdinaryObjectCreate`]: https://tc39.es/ecma262/#sec-ordinaryobjectcreate pub(crate) fn from_proto_and_data_with_shared_shape>, T: NativeObject>( diff --git a/boa_engine/src/value/display.rs b/boa_engine/src/value/display.rs index 6bf058127a6..97e478b3b7c 100644 --- a/boa_engine/src/value/display.rs +++ b/boa_engine/src/value/display.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; use crate::{ builtins::{ - error::ErrorKind, map::ordered_map::OrderedMap, promise::PromiseState, + error::ErrorObject, map::ordered_map::OrderedMap, promise::PromiseState, set::ordered_set::OrderedSet, Array, Promise, }, property::PropertyDescriptor, @@ -193,7 +193,7 @@ pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children } else { format!("Set({size})") } - } else if v_bor.is::() { + } else if v_bor.is::() { drop(v_bor); let name: Cow<'static, str> = v .get_property(&utf16!("name").into()) diff --git a/boa_runtime/src/lib.rs b/boa_runtime/src/lib.rs index 93ff44feff6..16d0da97524 100644 --- a/boa_runtime/src/lib.rs +++ b/boa_runtime/src/lib.rs @@ -93,7 +93,7 @@ pub(crate) mod test { }, AssertNativeError { source: Cow<'static, str>, - kind: builtins::error::ErrorKind, + kind: builtins::error::ErrorObject, message: &'static str, }, AssertContext { From 075931045db43cfff628f6740160f4ada2c9cce5 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sun, 3 Dec 2023 22:56:42 -0600 Subject: [PATCH 12/15] cargo fmt --- boa_engine/src/builtins/temporal/duration/mod.rs | 2 +- boa_engine/src/builtins/temporal/plain_month_day/mod.rs | 5 +---- boa_engine/src/builtins/temporal/plain_year_month/mod.rs | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/boa_engine/src/builtins/temporal/duration/mod.rs b/boa_engine/src/builtins/temporal/duration/mod.rs index 7cd5423163e..f30437cca87 100644 --- a/boa_engine/src/builtins/temporal/duration/mod.rs +++ b/boa_engine/src/builtins/temporal/duration/mod.rs @@ -937,7 +937,7 @@ pub(crate) fn create_temporal_duration( // 12. Set object.[[Microseconds]] to ℝ(𝔽(microseconds)). // 13. Set object.[[Nanoseconds]] to ℝ(𝔽(nanoseconds)). - let obj = JsObject::from_proto_and_data(prototype, Duration::new( inner)); + let obj = JsObject::from_proto_and_data(prototype, Duration::new(inner)); // 14. Return object. Ok(obj) } diff --git a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs index 0c2f18cc848..7e3c9a75ac3 100644 --- a/boa_engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_month_day/mod.rs @@ -105,10 +105,7 @@ pub(crate) fn create_temporal_month_day( // 6. Set object.[[ISODay]] to isoDay. // 7. Set object.[[Calendar]] to calendar. // 8. Set object.[[ISOYear]] to referenceISOYear. - let obj = JsObject::from_proto_and_data( - proto, - PlainMonthDay::new(inner), - ); + let obj = JsObject::from_proto_and_data(proto, PlainMonthDay::new(inner)); // 9. Return object. Ok(obj.into()) diff --git a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs index e628e099657..55ff12ceaba 100644 --- a/boa_engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/boa_engine/src/builtins/temporal/plain_year_month/mod.rs @@ -301,8 +301,7 @@ pub(crate) fn create_temporal_year_month( // 7. Set object.[[Calendar]] to calendar. // 8. Set object.[[ISODay]] to referenceISODay. - let obj = - JsObject::from_proto_and_data(proto, PlainYearMonth::new(ym)); + let obj = JsObject::from_proto_and_data(proto, PlainYearMonth::new(ym)); // 9. Return object. Ok(obj.into()) From 76ae53ad8e01f4564226d0d6fd5a036f22d80b9e Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Mon, 4 Dec 2023 01:52:08 -0600 Subject: [PATCH 13/15] Extract `upcast` function --- boa_engine/src/object/jsobject.rs | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index dbe0a036740..31218b43767 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -137,15 +137,7 @@ impl JsObject { vtable: internal_methods, }); - // SAFETY: This just makes the casting from sized to unsized. Should eventually be replaced by - // https://github.com/rust-lang/rust/issues/18598 - let gc: Gc> = unsafe { - let ptr = Gc::into_raw(gc); - let ptr: NonNull>> = ptr; - Gc::from_raw(ptr) - }; - - Self { inner: gc } + Self { inner: upcast(gc) } } /// Creates a new object with the provided prototype and object data. @@ -174,15 +166,7 @@ impl JsObject { vtable: internal_methods, }); - // SAFETY: This just makes the casting from sized to unsized. Should eventually be replaced by - // https://github.com/rust-lang/rust/issues/18598 - let gc: Gc> = unsafe { - let ptr = Gc::into_raw(gc); - let ptr: NonNull>> = ptr; - Gc::from_raw(ptr) - }; - - Self { inner: gc } + Self { inner: upcast(gc) } } /// Immutably borrows the `Object`. @@ -841,3 +825,14 @@ impl Debug for JsObject { } } } + +/// Upcasts the reference to an object from a specific type `T` to an erased type `dyn NativeObject`. +fn upcast(ptr: Gc>) -> Gc> { + // SAFETY: This just makes the casting from sized to unsized. Should eventually be replaced by + // https://github.com/rust-lang/rust/issues/18598 + unsafe { + let ptr = Gc::into_raw(ptr); + let ptr: NonNull>> = ptr; + Gc::from_raw(ptr) + } +} From 736f477fa3ea926faa5936e8e085e53810776b69 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Mon, 4 Dec 2023 02:06:13 -0600 Subject: [PATCH 14/15] Add missing upcast usage --- boa_engine/src/object/jsobject.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index 31218b43767..de9dadd69d6 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -77,15 +77,7 @@ impl JsObject { vtable, }); - // SAFETY: This just makes the casting from sized to unsized. Should eventually be replaced by - // https://github.com/rust-lang/rust/issues/18598 - let gc: Gc> = unsafe { - let ptr = Gc::into_raw(gc); - let ptr: NonNull>> = ptr; - Gc::from_raw(ptr) - }; - - Self { inner: gc } + Self { inner: upcast(gc) } } /// Creates a new ordinary object with its prototype set to the `Object` prototype. From eca8048d0a4e26f72711e7f95468f0ce48cbd49b Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Tue, 5 Dec 2023 09:41:55 -0600 Subject: [PATCH 15/15] cargo fmt --- boa_engine/src/builtins/array/mod.rs | 4 ++-- boa_engine/src/builtins/function/arguments.rs | 2 +- boa_engine/src/builtins/object/mod.rs | 4 ++-- .../src/builtins/typed_array/integer_indexed_object.rs | 3 ++- boa_engine/src/module/namespace.rs | 4 ++-- boa_engine/src/object/jsobject.rs | 4 +++- boa_engine/src/vm/opcode/set/class_prototype.rs | 7 +++---- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/boa_engine/src/builtins/array/mod.rs b/boa_engine/src/builtins/array/mod.rs index 4cae57efe03..f9f31c0aa4c 100644 --- a/boa_engine/src/builtins/array/mod.rs +++ b/boa_engine/src/builtins/array/mod.rs @@ -24,8 +24,8 @@ use crate::{ object::{ internal_methods::{ get_prototype_from_constructor, ordinary_define_own_property, - ordinary_get_own_property, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, - InternalMethodContext + ordinary_get_own_property, InternalMethodContext, InternalObjectMethods, + ORDINARY_INTERNAL_METHODS, }, JsData, JsObject, CONSTRUCTOR, }, diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index 7168e3d82ae..001185c24b0 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -3,7 +3,7 @@ use crate::{ object::{ internal_methods::{ ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property, - ordinary_set, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, InternalMethodContext, + ordinary_set, InternalMethodContext, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, }, JsObject, }, diff --git a/boa_engine/src/builtins/object/mod.rs b/boa_engine/src/builtins/object/mod.rs index 46fd85b4cc2..a56ef71ee8f 100644 --- a/boa_engine/src/builtins/object/mod.rs +++ b/boa_engine/src/builtins/object/mod.rs @@ -25,8 +25,8 @@ use crate::{ js_string, native_function::NativeFunction, object::{ -internal_methods::{get_prototype_from_constructor, InternalMethodContext}, FunctionObjectBuilder, IntegrityLevel, - JsObject, + internal_methods::{get_prototype_from_constructor, InternalMethodContext}, + FunctionObjectBuilder, IntegrityLevel, JsObject, }, property::{Attribute, PropertyDescriptor, PropertyKey, PropertyNameKind}, realm::Realm, diff --git a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs index 893edfc8762..ad526d37323 100644 --- a/boa_engine/src/builtins/typed_array/integer_indexed_object.rs +++ b/boa_engine/src/builtins/typed_array/integer_indexed_object.rs @@ -7,7 +7,8 @@ use crate::{ object::{ internal_methods::{ ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property, - ordinary_has_property, ordinary_set, InternalObjectMethods, ORDINARY_INTERNAL_METHODS, InternalMethodContext, + ordinary_has_property, ordinary_set, InternalMethodContext, InternalObjectMethods, + ORDINARY_INTERNAL_METHODS, }, JsData, JsObject, }, diff --git a/boa_engine/src/module/namespace.rs b/boa_engine/src/module/namespace.rs index 4a4777dbd83..fff27d75743 100644 --- a/boa_engine/src/module/namespace.rs +++ b/boa_engine/src/module/namespace.rs @@ -8,8 +8,8 @@ use boa_gc::{Finalize, Trace}; use crate::object::internal_methods::immutable_prototype::immutable_prototype_exotic_set_prototype_of; use crate::object::internal_methods::{ ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_get_own_property, - ordinary_has_property, ordinary_own_property_keys, InternalObjectMethods, - ORDINARY_INTERNAL_METHODS, InternalMethodContext, + ordinary_has_property, ordinary_own_property_keys, InternalMethodContext, + InternalObjectMethods, ORDINARY_INTERNAL_METHODS, }; use crate::object::{JsData, JsPrototype}; use crate::property::{PropertyDescriptor, PropertyKey}; diff --git a/boa_engine/src/object/jsobject.rs b/boa_engine/src/object/jsobject.rs index de9dadd69d6..3aa2fba6da3 100644 --- a/boa_engine/src/object/jsobject.rs +++ b/boa_engine/src/object/jsobject.rs @@ -3,7 +3,9 @@ //! The `JsObject` is a garbage collected Object. use super::{ - internal_methods::{non_existant_call, non_existant_construct, InternalMethodContext, InternalObjectMethods}, + internal_methods::{ + non_existant_call, non_existant_construct, InternalMethodContext, InternalObjectMethods, + }, shape::RootShape, JsPrototype, NativeObject, Object, PrivateName, PropertyMap, }; diff --git a/boa_engine/src/vm/opcode/set/class_prototype.rs b/boa_engine/src/vm/opcode/set/class_prototype.rs index 9e7c96f3e98..be0d89c2054 100644 --- a/boa_engine/src/vm/opcode/set/class_prototype.rs +++ b/boa_engine/src/vm/opcode/set/class_prototype.rs @@ -1,10 +1,9 @@ use crate::{ - object::{ - internal_methods::InternalMethodContext, JsObject, CONSTRUCTOR, PROTOTYPE, - }, + builtins::{function::OrdinaryFunction, OrdinaryObject}, + object::{internal_methods::InternalMethodContext, JsObject, CONSTRUCTOR, PROTOTYPE}, property::PropertyDescriptorBuilder, vm::{opcode::Operation, CompletionType}, - Context, JsResult, JsValue, builtins::{function::OrdinaryFunction, OrdinaryObject}, + Context, JsResult, JsValue, }; /// `SetClassProtoType` implements the Opcode Operation for `Opcode::SetClassPrototype`