Skip to main content

boa_engine/
host_defined.rs

1use std::any::{Any, TypeId};
2
3use boa_gc::{Finalize, Trace, custom_trace};
4use hashbrown::hash_map::HashMap;
5
6use crate::object::NativeObject;
7
8/// This represents a `ECMAScript` specification \[`HostDefined`\] field.
9///
10/// This allows storing types which are mapped by their [`TypeId`].
11#[allow(missing_debug_implementations)]
12pub struct HostDefined<T: ?Sized = dyn NativeObject> {
13    // INVARIANT: All key-value pairs `(id, obj)` satisfy:
14    //  `id == TypeId::of::<T>() && obj.is::<T>()`
15    // for some type `T : NativeObject`.
16    types: HashMap<TypeId, Box<T>>,
17}
18
19impl<T: ?Sized> Default for HostDefined<T> {
20    fn default() -> Self {
21        Self {
22            types: HashMap::default(),
23        }
24    }
25}
26
27// SAFETY: All traceable values are marked here, making this implementation
28// safe.
29unsafe impl<T: ?Sized + Trace> Trace for HostDefined<T> {
30    custom_trace!(this, mark, {
31        for value in this.types.values() {
32            mark(value);
33        }
34    });
35}
36
37impl<T: ?Sized + Finalize> Finalize for HostDefined<T> {}
38
39impl HostDefined<dyn Any> {
40    /// Insert a type into the [`HostDefined`].
41    #[track_caller]
42    pub fn insert_default<T: Any + Default>(&mut self) -> Option<Box<T>> {
43        self.types
44            .insert(TypeId::of::<T>(), Box::<T>::default())
45            .and_then(|t| t.downcast().ok())
46    }
47
48    /// Insert a type into the [`HostDefined`].
49    #[track_caller]
50    pub fn insert<T: Any>(&mut self, value: T) -> Option<Box<T>> {
51        self.types
52            .insert(TypeId::of::<T>(), Box::new(value))
53            .and_then(|t| t.downcast().ok())
54    }
55
56    /// Remove type T from [`HostDefined`], if it exists.
57    ///
58    /// Returns [`Some`] with the object if it exits, [`None`] otherwise.
59    #[track_caller]
60    pub fn remove<T: Any>(&mut self) -> Option<Box<T>> {
61        self.types
62            .remove(&TypeId::of::<T>())
63            .and_then(|t| t.downcast().ok())
64    }
65
66    /// Get type T from [`HostDefined`], if it exists.
67    #[track_caller]
68    pub fn get<T: Any>(&self) -> Option<&T> {
69        self.types
70            .get(&TypeId::of::<T>())
71            .map(Box::as_ref)
72            .and_then(<dyn Any>::downcast_ref::<T>)
73    }
74
75    /// Get type T from [`HostDefined`], if it exists.
76    #[track_caller]
77    pub fn get_mut<T: Any>(&mut self) -> Option<&mut T> {
78        self.types
79            .get_mut(&TypeId::of::<T>())
80            .map(Box::as_mut)
81            .and_then(<dyn Any>::downcast_mut::<T>)
82    }
83
84    /// Get a tuple of types from [`HostDefined`], returning `None` for the types that are not on the map.
85    #[track_caller]
86    pub fn get_many_mut<T, const SIZE: usize>(&mut self) -> T::NativeTupleMutRef<'_>
87    where
88        T: NativeTuple<SIZE>,
89    {
90        let ids = T::as_type_ids();
91        let refs: [&TypeId; SIZE] = std::array::from_fn(|i| &ids[i]);
92        let anys = self
93            .types
94            .get_disjoint_mut(refs)
95            .map(|o| o.map(|v| &mut **v));
96
97        T::mut_ref_from_anys(anys)
98    }
99}
100
101impl HostDefined<dyn NativeObject> {
102    /// Insert a type into the [`HostDefined`].
103    #[track_caller]
104    pub fn insert_default<T: NativeObject + Default>(&mut self) -> Option<Box<T>> {
105        self.types
106            .insert(TypeId::of::<T>(), Box::<T>::default())
107            .and_then(|t| {
108                // triggers downcast from NativeObject to Any
109                let t: Box<dyn Any> = t;
110                t.downcast().ok()
111            })
112    }
113
114    /// Insert a type into the [`HostDefined`].
115    #[track_caller]
116    pub fn insert<T: NativeObject>(&mut self, value: T) -> Option<Box<T>> {
117        self.types
118            .insert(TypeId::of::<T>(), Box::new(value))
119            .and_then(|t| {
120                // triggers downcast from NativeObject to Any
121                let t: Box<dyn Any> = t;
122                t.downcast().ok()
123            })
124    }
125
126    /// Remove type T from [`HostDefined`], if it exists.
127    ///
128    /// Returns [`Some`] with the object if it exits, [`None`] otherwise.
129    #[track_caller]
130    pub fn remove<T: NativeObject>(&mut self) -> Option<Box<T>> {
131        self.types.remove(&TypeId::of::<T>()).and_then(|t| {
132            // triggers downcast from NativeObject to Any
133            let t: Box<dyn Any> = t;
134            t.downcast().ok()
135        })
136    }
137
138    /// Get type T from [`HostDefined`], if it exists.
139    #[track_caller]
140    pub fn get<T: NativeObject>(&self) -> Option<&T> {
141        self.types
142            .get(&TypeId::of::<T>())
143            .map(Box::as_ref)
144            .and_then(<dyn NativeObject>::downcast_ref::<T>)
145    }
146
147    /// Get type T from [`HostDefined`], if it exists.
148    #[track_caller]
149    pub fn get_mut<T: NativeObject>(&mut self) -> Option<&mut T> {
150        self.types
151            .get_mut(&TypeId::of::<T>())
152            .map(Box::as_mut)
153            .and_then(<dyn NativeObject>::downcast_mut::<T>)
154    }
155
156    /// Get a tuple of types from [`HostDefined`], returning `None` for the types that are not on the map.
157    #[track_caller]
158    pub fn get_many_mut<T, const SIZE: usize>(&mut self) -> T::NativeTupleMutRef<'_>
159    where
160        T: NativeTuple<SIZE>,
161    {
162        let ids = T::as_type_ids();
163        let refs: [&TypeId; SIZE] = std::array::from_fn(|i| &ids[i]);
164        let anys = self.types.get_disjoint_mut(refs).map(|o| {
165            o.map(|v| {
166                let v: &mut dyn Any = &mut **v;
167                v
168            })
169        });
170
171        T::mut_ref_from_anys(anys)
172    }
173}
174
175impl<T: ?Sized> HostDefined<T> {
176    /// Check if the [`HostDefined`] has type T.
177    #[must_use]
178    #[track_caller]
179    pub fn has<U: 'static>(&self) -> bool {
180        self.types.contains_key(&TypeId::of::<U>())
181    }
182
183    /// Clears all the objects.
184    #[track_caller]
185    pub fn clear(&mut self) {
186        self.types.clear();
187    }
188}
189
190/// This trait represents a tuple of [`NativeObject`]s capable of being
191/// used in [`HostDefined`].
192///
193/// This allows accessing multiple types from [`HostDefined`] at once.
194pub trait NativeTuple<const SIZE: usize> {
195    type NativeTupleMutRef<'a>;
196
197    fn as_type_ids() -> [TypeId; SIZE];
198
199    fn mut_ref_from_anys(anys: [Option<&'_ mut dyn Any>; SIZE]) -> Self::NativeTupleMutRef<'_>;
200}
201
202macro_rules! impl_native_tuple {
203    ($size:literal $(,$name:ident)* ) => {
204        impl<$($name: Any,)*> NativeTuple<$size> for ($($name,)*) {
205            type NativeTupleMutRef<'a> = ($(Option<&'a mut $name>,)*);
206
207            fn as_type_ids() -> [TypeId; $size] {
208                [$(TypeId::of::<$name>(),)*]
209            }
210
211            #[allow(unused_variables, unused_mut, clippy::unused_unit)]
212            fn mut_ref_from_anys(
213                anys: [Option<&'_ mut dyn Any>; $size],
214            ) -> Self::NativeTupleMutRef<'_> {
215                let mut anys = anys.into_iter();
216                ($(
217                    anys.next().flatten().and_then(|v| v.downcast_mut::<$name>()),
218                )*)
219            }
220        }
221    }
222}
223
224impl_native_tuple!(0);
225impl_native_tuple!(1, A);
226impl_native_tuple!(2, A, B);
227impl_native_tuple!(3, A, B, C);
228impl_native_tuple!(4, A, B, C, D);
229impl_native_tuple!(5, A, B, C, D, E);
230impl_native_tuple!(6, A, B, C, D, E, F);
231impl_native_tuple!(7, A, B, C, D, E, F, G);
232impl_native_tuple!(8, A, B, C, D, E, F, G, H);
233impl_native_tuple!(9, A, B, C, D, E, F, G, H, I);
234impl_native_tuple!(10, A, B, C, D, E, F, G, H, I, J);
235impl_native_tuple!(11, A, B, C, D, E, F, G, H, I, J, K);
236impl_native_tuple!(12, A, B, C, D, E, F, G, H, I, J, K, L);