mirror of https://github.com/boa-dev/boa.git
Browse Source
* Refactor the `[[HostDefined]]` implementation. Currently `[[HostDefined]]` doesn't permit you to mutably borrow two objects from the `[[HostDefined]]` field since the `FxHashMap` is wrapped under a `GcRefCell`. This commit implements a `get_mut_many` (from `hashbrown`'s `HashMap`) method to permit accessing several `NativeObject`s using a `NativeTuple`. Additionally, this commit takes the opportunity to provide automatic downcasting on the `insert` and `remove` methods for `[[HostDefined]]`. * Update `[[HostDefined]]` example.pull/3462/head
Alistair
1 year ago
committed by
GitHub
6 changed files with 204 additions and 85 deletions
@ -1,111 +1,154 @@ |
|||||||
use std::any::TypeId; |
use std::any::TypeId; |
||||||
|
|
||||||
use boa_gc::{GcRef, GcRefCell, GcRefMut}; |
|
||||||
use boa_macros::{Finalize, Trace}; |
use boa_macros::{Finalize, Trace}; |
||||||
use rustc_hash::FxHashMap; |
use hashbrown::hash_map::HashMap; |
||||||
|
|
||||||
use crate::object::NativeObject; |
use crate::object::NativeObject; |
||||||
|
|
||||||
/// Map used to store the host defined objects.
|
|
||||||
#[doc(hidden)] |
|
||||||
type HostDefinedMap = FxHashMap<TypeId, Box<dyn NativeObject>>; |
|
||||||
|
|
||||||
/// This represents a `ECMASCript` specification \[`HostDefined`\] field.
|
/// This represents a `ECMASCript` specification \[`HostDefined`\] field.
|
||||||
///
|
///
|
||||||
/// This allows storing types which are mapped by their [`TypeId`].
|
/// This allows storing types which are mapped by their [`TypeId`].
|
||||||
#[derive(Default, Trace, Finalize)] |
#[derive(Default, Trace, Finalize)] |
||||||
#[allow(missing_debug_implementations)] |
#[allow(missing_debug_implementations)] |
||||||
pub struct HostDefined { |
pub struct HostDefined { |
||||||
state: GcRefCell<HostDefinedMap>, |
// INVARIANT: All key-value pairs `(id, obj)` satisfy:
|
||||||
|
// `id == TypeId::of::<T>() && obj.is::<T>()`
|
||||||
|
// for some type `T : NativeObject`.
|
||||||
|
types: HashMap<TypeId, Box<dyn NativeObject>>, |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: Track https://github.com/rust-lang/rust/issues/65991 and
|
||||||
|
// https://github.com/rust-lang/rust/issues/90850 to remove this
|
||||||
|
// when those are stabilized.
|
||||||
|
fn downcast_boxed_native_object_unchecked<T: NativeObject>(obj: Box<dyn NativeObject>) -> Box<T> { |
||||||
|
let raw: *mut dyn NativeObject = Box::into_raw(obj); |
||||||
|
|
||||||
|
// SAFETY: We know that `obj` is of type `T` (due to the INVARIANT of `HostDefined`).
|
||||||
|
// See `HostDefined::insert`, `HostDefined::insert_default` and `HostDefined::remove`.
|
||||||
|
unsafe { Box::from_raw(raw.cast::<T>()) } |
||||||
} |
} |
||||||
|
|
||||||
impl HostDefined { |
impl HostDefined { |
||||||
/// Insert a type into the [`HostDefined`].
|
/// Insert a type into the [`HostDefined`].
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if [`HostDefined`] field is borrowed.
|
|
||||||
#[track_caller] |
#[track_caller] |
||||||
pub fn insert_default<T: NativeObject + Default>(&self) -> Option<Box<dyn NativeObject>> { |
pub fn insert_default<T: NativeObject + Default>(&mut self) -> Option<Box<T>> { |
||||||
self.state |
self.types |
||||||
.borrow_mut() |
|
||||||
.insert(TypeId::of::<T>(), Box::<T>::default()) |
.insert(TypeId::of::<T>(), Box::<T>::default()) |
||||||
|
.map(downcast_boxed_native_object_unchecked) |
||||||
} |
} |
||||||
|
|
||||||
/// Insert a type into the [`HostDefined`].
|
/// Insert a type into the [`HostDefined`].
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if [`HostDefined`] field is borrowed.
|
|
||||||
#[track_caller] |
#[track_caller] |
||||||
pub fn insert<T: NativeObject>(&self, value: T) -> Option<Box<dyn NativeObject>> { |
pub fn insert<T: NativeObject>(&mut self, value: T) -> Option<Box<T>> { |
||||||
self.state |
self.types |
||||||
.borrow_mut() |
|
||||||
.insert(TypeId::of::<T>(), Box::new(value)) |
.insert(TypeId::of::<T>(), Box::new(value)) |
||||||
|
.map(downcast_boxed_native_object_unchecked) |
||||||
} |
} |
||||||
|
|
||||||
/// Check if the [`HostDefined`] has type T.
|
/// Check if the [`HostDefined`] has type T.
|
||||||
///
|
#[must_use] |
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if [`HostDefined`] field is borrowed mutably.
|
|
||||||
#[track_caller] |
#[track_caller] |
||||||
pub fn has<T: NativeObject>(&self) -> bool { |
pub fn has<T: NativeObject>(&self) -> bool { |
||||||
self.state.borrow().contains_key(&TypeId::of::<T>()) |
self.types.contains_key(&TypeId::of::<T>()) |
||||||
} |
} |
||||||
|
|
||||||
/// Remove type T from [`HostDefined`], if it exists.
|
/// Remove type T from [`HostDefined`], if it exists.
|
||||||
///
|
///
|
||||||
/// Returns [`Some`] with the object if it exits, [`None`] otherwise.
|
/// Returns [`Some`] with the object if it exits, [`None`] otherwise.
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if [`HostDefined`] field is borrowed.
|
|
||||||
#[track_caller] |
#[track_caller] |
||||||
pub fn remove<T: NativeObject>(&self) -> Option<Box<dyn NativeObject>> { |
pub fn remove<T: NativeObject>(&mut self) -> Option<Box<T>> { |
||||||
self.state.borrow_mut().remove(&TypeId::of::<T>()) |
self.types |
||||||
|
.remove(&TypeId::of::<T>()) |
||||||
|
.map(downcast_boxed_native_object_unchecked) |
||||||
} |
} |
||||||
|
|
||||||
/// Get type T from [`HostDefined`], if it exits.
|
/// Get type T from [`HostDefined`], if it exists.
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if [`HostDefined`] field is borrowed.
|
|
||||||
#[track_caller] |
#[track_caller] |
||||||
pub fn get<T: NativeObject>(&self) -> Option<GcRef<'_, T>> { |
pub fn get<T: NativeObject>(&self) -> Option<&T> { |
||||||
GcRef::try_map(self.state.borrow(), |state| { |
self.types |
||||||
state |
.get(&TypeId::of::<T>()) |
||||||
.get(&TypeId::of::<T>()) |
.map(Box::as_ref) |
||||||
.map(Box::as_ref) |
.and_then(<dyn NativeObject>::downcast_ref::<T>) |
||||||
.and_then(<dyn NativeObject>::downcast_ref::<T>) |
|
||||||
}) |
|
||||||
} |
} |
||||||
|
|
||||||
/// Get type T from [`HostDefined`], if it exits.
|
/// Get type T from [`HostDefined`], if it exists.
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if [`HostDefined`] field is borrowed.
|
|
||||||
#[track_caller] |
#[track_caller] |
||||||
pub fn get_mut<T: NativeObject>(&self) -> Option<GcRefMut<'_, HostDefinedMap, T>> { |
pub fn get_mut<T: NativeObject>(&mut self) -> Option<&mut T> { |
||||||
GcRefMut::try_map( |
self.types |
||||||
self.state.borrow_mut(), |
.get_mut(&TypeId::of::<T>()) |
||||||
|state: &mut FxHashMap<TypeId, Box<dyn NativeObject>>| { |
.map(Box::as_mut) |
||||||
state |
.and_then(<dyn NativeObject>::downcast_mut::<T>) |
||||||
.get_mut(&TypeId::of::<T>()) |
} |
||||||
.map(Box::as_mut) |
|
||||||
.and_then(<dyn NativeObject>::downcast_mut::<T>) |
/// Get type a tuple of types from [`HostDefined`], if they exist.
|
||||||
}, |
#[track_caller] |
||||||
) |
pub fn get_many_mut<T, const SIZE: usize>(&mut self) -> Option<T::NativeTupleMutRef<'_>> |
||||||
|
where |
||||||
|
T: NativeTuple<SIZE>, |
||||||
|
{ |
||||||
|
let ids = T::as_type_ids(); |
||||||
|
let refs: [&TypeId; SIZE] = ids |
||||||
|
.iter() |
||||||
|
.collect::<Vec<_>>() |
||||||
|
.try_into() |
||||||
|
.expect("tuple should be of size `SIZE`"); |
||||||
|
|
||||||
|
self.types.get_many_mut(refs).and_then(T::mut_ref_from_anys) |
||||||
} |
} |
||||||
|
|
||||||
/// Clears all the objects.
|
/// Clears all the objects.
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if [`HostDefined`] field is borrowed.
|
|
||||||
#[track_caller] |
#[track_caller] |
||||||
pub fn clear(&self) { |
pub fn clear(&mut self) { |
||||||
self.state.borrow_mut().clear(); |
self.types.clear(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// This trait represents a tuple of [`NativeObject`]s capable of being
|
||||||
|
/// used in [`HostDefined`].
|
||||||
|
///
|
||||||
|
/// This allows accessing multiple types from [`HostDefined`] at once.
|
||||||
|
pub trait NativeTuple<const SIZE: usize> { |
||||||
|
type NativeTupleMutRef<'a>; |
||||||
|
|
||||||
|
fn as_type_ids() -> Vec<TypeId>; |
||||||
|
|
||||||
|
fn mut_ref_from_anys( |
||||||
|
anys: [&'_ mut Box<dyn NativeObject>; SIZE], |
||||||
|
) -> Option<Self::NativeTupleMutRef<'_>>; |
||||||
|
} |
||||||
|
|
||||||
|
macro_rules! impl_native_tuple { |
||||||
|
($size:literal $(,$name:ident)* ) => { |
||||||
|
impl<$($name: NativeObject,)*> NativeTuple<$size> for ($($name,)*) { |
||||||
|
type NativeTupleMutRef<'a> = ($(&'a mut $name,)*); |
||||||
|
|
||||||
|
fn as_type_ids() -> Vec<TypeId> { |
||||||
|
vec![$(TypeId::of::<$name>(),)*] |
||||||
|
} |
||||||
|
|
||||||
|
fn mut_ref_from_anys( |
||||||
|
anys: [&'_ mut Box<dyn NativeObject>; $size], |
||||||
|
) -> Option<Self::NativeTupleMutRef<'_>> { |
||||||
|
#[allow(unused_variables, unused_mut)] |
||||||
|
let mut anys = anys.into_iter(); |
||||||
|
Some(($( |
||||||
|
anys.next().expect("Expect `anys` to be of length `SIZE`").downcast_mut::<$name>()?, |
||||||
|
)*)) |
||||||
|
} |
||||||
|
} |
||||||
} |
} |
||||||
} |
} |
||||||
|
|
||||||
|
impl_native_tuple!(0); |
||||||
|
impl_native_tuple!(1, A); |
||||||
|
impl_native_tuple!(2, A, B); |
||||||
|
impl_native_tuple!(3, A, B, C); |
||||||
|
impl_native_tuple!(4, A, B, C, D); |
||||||
|
impl_native_tuple!(5, A, B, C, D, E); |
||||||
|
impl_native_tuple!(6, A, B, C, D, E, F); |
||||||
|
impl_native_tuple!(7, A, B, C, D, E, F, G); |
||||||
|
impl_native_tuple!(8, A, B, C, D, E, F, G, H); |
||||||
|
impl_native_tuple!(9, A, B, C, D, E, F, G, H, I); |
||||||
|
impl_native_tuple!(10, A, B, C, D, E, F, G, H, I, J); |
||||||
|
impl_native_tuple!(11, A, B, C, D, E, F, G, H, I, J, K); |
||||||
|
impl_native_tuple!(12, A, B, C, D, E, F, G, H, I, J, K, L); |
||||||
|
Loading…
Reference in new issue