Skip to main content

boa_engine/object/
datatypes.rs

1use std::{
2    any::TypeId,
3    borrow::Cow,
4    cell::Cell,
5    collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque},
6    hash::{BuildHasher, Hash},
7    marker::PhantomData,
8    num::{
9        NonZeroI8, NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI128, NonZeroIsize, NonZeroU8,
10        NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU128, NonZeroUsize,
11    },
12    path::{Path, PathBuf},
13    rc::Rc,
14    sync::atomic,
15};
16
17use boa_gc::{Ephemeron, Finalize, Gc, GcRefCell, Trace, WeakGc, WeakMap};
18
19use super::internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
20
21/// Represents a type that can be stored inside a `JsObject`.
22///
23/// This can be automatically derived using a macro.
24///
25/// # Example
26///
27/// ```
28/// use boa_engine::{Finalize, JsData, JsObject, Trace};
29///
30/// #[derive(Trace, Finalize, JsData)]
31/// struct CustomStruct {
32///     #[unsafe_ignore_trace]
33///     counter: usize,
34/// }
35///
36/// let object =
37///     JsObject::from_proto_and_data(None, CustomStruct { counter: 5 });
38///
39/// assert_eq!(object.downcast_ref::<CustomStruct>().unwrap().counter, 5);
40/// ```
41pub trait JsData {
42    #[doc(hidden)]
43    fn internal_methods(&self) -> &'static InternalObjectMethods
44    where
45        Self: Sized, // Avoids adding this method to `NativeObject`'s vtable.
46    {
47        &ORDINARY_INTERNAL_METHODS
48    }
49}
50
51macro_rules! default_impls {
52    ($($T:ty),*$(,)?) => {
53        $(
54            impl JsData for $T {}
55        )*
56    }
57}
58
59default_impls![
60    (),
61    bool,
62    isize,
63    usize,
64    i8,
65    u8,
66    i16,
67    u16,
68    i32,
69    u32,
70    i64,
71    u64,
72    i128,
73    u128,
74    f32,
75    f64,
76    char,
77    TypeId,
78    String,
79    Path,
80    PathBuf,
81    NonZeroIsize,
82    NonZeroUsize,
83    NonZeroI8,
84    NonZeroU8,
85    NonZeroI16,
86    NonZeroU16,
87    NonZeroI32,
88    NonZeroU32,
89    NonZeroI64,
90    NonZeroU64,
91    NonZeroI128,
92    NonZeroU128,
93];
94
95#[cfg(target_has_atomic = "8")]
96default_impls![atomic::AtomicBool, atomic::AtomicI8, atomic::AtomicU8];
97
98#[cfg(target_has_atomic = "16")]
99default_impls![atomic::AtomicI16, atomic::AtomicU16];
100
101#[cfg(target_has_atomic = "32")]
102default_impls![atomic::AtomicI32, atomic::AtomicU32];
103
104#[cfg(target_has_atomic = "64")]
105default_impls![atomic::AtomicI64, atomic::AtomicU64];
106
107#[cfg(target_has_atomic = "ptr")]
108default_impls![atomic::AtomicIsize, atomic::AtomicUsize];
109
110impl<T, const N: usize> JsData for [T; N] {}
111
112macro_rules! fn_one {
113    ($ty:ty $(,$args:ident)*) => {
114        impl<Ret $(,$args)*> JsData for $ty {}
115    }
116}
117
118macro_rules! fn_impls {
119    () => {
120        fn_one!(extern "Rust" fn () -> Ret);
121        fn_one!(extern "C" fn () -> Ret);
122        fn_one!(unsafe extern "Rust" fn () -> Ret);
123        fn_one!(unsafe extern "C" fn () -> Ret);
124    };
125    ($($args:ident),*) => {
126        fn_one!(extern "Rust" fn ($($args),*) -> Ret, $($args),*);
127        fn_one!(extern "C" fn ($($args),*) -> Ret, $($args),*);
128        fn_one!(extern "C" fn ($($args),*, ...) -> Ret, $($args),*);
129        fn_one!(unsafe extern "Rust" fn ($($args),*) -> Ret, $($args),*);
130        fn_one!(unsafe extern "C" fn ($($args),*) -> Ret, $($args),*);
131        fn_one!(unsafe extern "C" fn ($($args),*, ...) -> Ret, $($args),*);
132    }
133}
134
135macro_rules! tuple_impls {
136    () => {}; // This case is handled above, by default_impls!().
137    ($($args:ident),*) => {
138        impl<$($args),*> JsData for ($($args,)*) {}
139    }
140}
141
142macro_rules! type_arg_tuple_based_impls {
143    ($(($($args:ident),*);)*) => {
144        $(
145            fn_impls!($($args),*);
146            tuple_impls!($($args),*);
147        )*
148    }
149}
150
151type_arg_tuple_based_impls![
152    ();
153    (A);
154    (A, B);
155    (A, B, C);
156    (A, B, C, D);
157    (A, B, C, D, E);
158    (A, B, C, D, E, F);
159    (A, B, C, D, E, F, G);
160    (A, B, C, D, E, F, G, H);
161    (A, B, C, D, E, F, G, H, I);
162    (A, B, C, D, E, F, G, H, I, J);
163    (A, B, C, D, E, F, G, H, I, J, K);
164    (A, B, C, D, E, F, G, H, I, J, K, L);
165];
166
167impl<T: ?Sized> JsData for Box<T> {}
168
169impl<T: ?Sized> JsData for Rc<T> {}
170
171impl<T> JsData for Vec<T> {}
172
173impl<T> JsData for thin_vec::ThinVec<T> {}
174
175impl<T> JsData for Option<T> {}
176
177impl<T, E> JsData for Result<T, E> {}
178
179impl<T: Ord> JsData for BinaryHeap<T> {}
180
181impl<K, V> JsData for BTreeMap<K, V> {}
182
183impl<T> JsData for BTreeSet<T> {}
184
185impl<K: Eq + Hash, V, S: BuildHasher> JsData for hashbrown::hash_map::HashMap<K, V, S> {}
186
187impl<K: Eq + Hash, V, S: BuildHasher> JsData for HashMap<K, V, S> {}
188
189impl<T: Eq + Hash, S: BuildHasher> JsData for HashSet<T, S> {}
190
191impl<T: Eq + Hash> JsData for LinkedList<T> {}
192
193impl<T> JsData for PhantomData<T> {}
194
195impl<T> JsData for VecDeque<T> {}
196
197impl<T: ToOwned + ?Sized> JsData for Cow<'static, T> {}
198
199impl<T> JsData for Cell<Option<T>> {}
200
201#[cfg(feature = "intl")]
202default_impls!(icu_locale::Locale);
203
204impl<T: Trace + ?Sized> JsData for Gc<T> {}
205
206impl<T: Trace + ?Sized> JsData for WeakGc<T> {}
207
208impl<T: Trace + ?Sized, V: Trace> JsData for Ephemeron<T, V> {}
209
210impl<T: Trace + ?Sized> JsData for GcRefCell<T> {}
211
212impl<K: Trace + ?Sized, V: Trace> JsData for WeakMap<K, V> {}
213
214/// Wrapper type to enforce consistent alignment for all [`JsData`] types.
215///
216/// Ensures alignment is exactly 8 bytes:
217/// - Minimum alignment is set via `#[repr(align(8))]`.
218/// - Maximum alignment is enforced at compile time with a const assertion.
219///
220///
221/// Use [`ObjectData::new`] to construct safely. Inner data is accessible via [`AsRef`] and [`AsMut`].
222#[derive(Debug, Finalize, Trace)]
223// SAFETY: This does not implement drop, so this is safe.
224#[boa_gc(unsafe_no_drop)]
225#[repr(C, align(8))]
226#[non_exhaustive]
227pub(crate) struct ObjectData<T: ?Sized> {
228    // MUST BE PRIVATE, should not be constructed directly. i.e. { data: ... }
229    // Because we want to trigger the compile-time const assertion below.
230    //
231    // It is fine if we have as_ref/as_mut to it or any access.
232    data: T,
233}
234
235impl<T: Default> Default for ObjectData<T> {
236    #[inline]
237    fn default() -> Self {
238        Self::new(T::default())
239    }
240}
241
242static_assertions::const_assert!(align_of::<Box<()>>() <= 8);
243
244impl<T> ObjectData<T> {
245    const OBJECT_DATA_ALIGNMENT_REQUIREMENT: () = assert!(
246        align_of::<T>() <= 8,
247        "Alignment of JsData must be <= 8, consider wrapping the data in a Box<T>."
248    );
249
250    pub(crate) fn new(value: T) -> Self {
251        // force assertion to trigger when we instantiate `ObjectData<T>::new`.
252        let () = Self::OBJECT_DATA_ALIGNMENT_REQUIREMENT;
253
254        Self { data: value }
255    }
256}
257
258impl<T: ?Sized> AsRef<T> for ObjectData<T> {
259    #[inline]
260    fn as_ref(&self) -> &T {
261        &self.data
262    }
263}
264
265impl<T: ?Sized> AsMut<T> for ObjectData<T> {
266    #[inline]
267    fn as_mut(&mut self) -> &mut T {
268        &mut self.data
269    }
270}