extern crate alloc;
use alloc::boxed::Box;
use core::marker::{Send, Sync};
use core::{
any::{Any, TypeId},
fmt,
};
use hashbrown::HashMap;
type AnyMap = HashMap<TypeId, Box<dyn Any + Send + Sync>>;
#[derive(Debug)]
pub struct Extensions<'a> {
inner: &'a ExtensionsInner,
}
impl<'a> Extensions<'a> {
#[inline]
pub(crate) fn new(inner: &'a ExtensionsInner) -> Self {
Self { inner }
}
#[inline]
pub fn get<T: 'static>(&self) -> Option<&T> {
self.inner.get::<T>()
}
}
#[derive(Debug)]
pub struct ExtensionsMut<'a> {
inner: &'a mut ExtensionsInner,
}
impl<'a> ExtensionsMut<'a> {
#[inline]
pub(crate) fn new(inner: &'a mut ExtensionsInner) -> Self {
Self { inner }
}
#[inline]
pub fn insert<T: Send + Sync + 'static>(&mut self, val: T) {
assert!(self.replace(val).is_none())
}
#[inline]
pub fn replace<T: Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
self.inner.insert(val)
}
#[inline]
pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.inner.get_mut::<T>()
}
#[inline]
pub fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
self.inner.remove::<T>()
}
}
#[derive(Default)]
pub(crate) struct ExtensionsInner {
map: AnyMap,
}
impl ExtensionsInner {
#[inline]
pub(crate) fn reserve(&mut self, capacity: usize) {
self.map.reserve(capacity);
}
#[inline]
pub(crate) fn new() -> ExtensionsInner {
ExtensionsInner { map: AnyMap::new() }
}
pub(crate) fn insert<T: Send + Sync + 'static>(&mut self, val: T) -> Option<T> {
self.map
.insert(TypeId::of::<T>(), Box::new(val))
.and_then(|boxed| {
#[allow(warnings)]
{
(boxed as Box<dyn Any + 'static>)
.downcast()
.ok()
.map(|boxed| *boxed)
}
})
}
pub(crate) fn get<T: 'static>(&self) -> Option<&T> {
self.map
.get(&TypeId::of::<T>())
.and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref())
}
pub(crate) fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.map
.get_mut(&TypeId::of::<T>())
.and_then(|boxed| (&mut **boxed as &mut (dyn Any + 'static)).downcast_mut())
}
pub(crate) fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> {
self.map.remove(&TypeId::of::<T>()).and_then(|boxed| {
#[allow(warnings)]
{
(boxed as Box<dyn Any + 'static>)
.downcast()
.ok()
.map(|boxed| *boxed)
}
})
}
#[allow(dead_code)]
pub(crate) fn clear(&mut self) {
self.map.clear();
}
}
impl fmt::Debug for ExtensionsInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Extensions")
.field("len", &self.map.len())
.field("capacity", &self.map.capacity())
.finish()
}
}
pub trait Extension {
fn extensions(&self) -> Extensions<'_>;
fn extensions_mut(&mut self) -> ExtensionsMut<'_>;
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq)]
struct MyType(i32);
#[test]
fn test_extensions() {
let mut extensions = ExtensionsInner::new();
extensions.insert(5i32);
extensions.insert(MyType(10));
assert_eq!(extensions.get(), Some(&5i32));
assert_eq!(extensions.get_mut(), Some(&mut 5i32));
assert_eq!(extensions.remove::<i32>(), Some(5i32));
assert!(extensions.get::<i32>().is_none());
assert_eq!(extensions.get::<bool>(), None);
assert_eq!(extensions.get(), Some(&MyType(10)));
}
#[test]
fn clear_retains_capacity() {
let mut extensions = ExtensionsInner::new();
extensions.insert(5i32);
extensions.insert(MyType(10));
extensions.insert(true);
assert_eq!(extensions.map.len(), 3);
let prev_capacity = extensions.map.capacity();
extensions.clear();
assert_eq!(
extensions.map.len(),
0,
"after clear(), extensions map should have length 0"
);
assert_eq!(
extensions.map.capacity(),
prev_capacity,
"after clear(), extensions map should retain prior capacity"
);
}
#[test]
fn clear_drops_elements() {
use alloc::sync::Arc;
struct DropMePlease(Arc<()>);
struct DropMeTooPlease(Arc<()>);
let mut extensions = ExtensionsInner::new();
let val1 = DropMePlease(Arc::new(()));
let val2 = DropMeTooPlease(Arc::new(()));
let val1_dropped = Arc::downgrade(&val1.0);
let val2_dropped = Arc::downgrade(&val2.0);
extensions.insert(val1);
extensions.insert(val2);
assert!(val1_dropped.upgrade().is_some());
assert!(val2_dropped.upgrade().is_some());
extensions.clear();
assert!(
val1_dropped.upgrade().is_none(),
"after clear(), val1 should be dropped"
);
assert!(
val2_dropped.upgrade().is_none(),
"after clear(), val2 should be dropped"
);
}
}