use core::marker::PhantomData;
use core::mem::{needs_drop, MaybeUninit};
use ty_tag::lifetime_list::LifetimeList;
use ty_tag::{Reify, TagTypeId};
use crate::want::WantFor;
pub struct ErasedWantFor<'u, L: LifetimeList> {
_lt: PhantomData<DynWantFor<'u, ()>>,
borrow: ErasedFatPtr,
tag_type_id: TagTypeId<L>,
}
type DynWantFor<'u, T> = &'u mut (dyn WantFor<T> + 'u);
impl<'u, L: LifetimeList> ErasedWantFor<'u, L> {
pub const fn new<T: ?Sized + Reify<L>>(borrow: DynWantFor<'u, T::Reified>) -> Self {
Self {
_lt: PhantomData,
borrow: ErasedFatPtr::new::<DynWantFor<'u, T::Reified>>(borrow),
tag_type_id: TagTypeId::<L>::of_reify::<T>(),
}
}
pub fn downcast<T: ?Sized + Reify<L>>(self) -> Result<DynWantFor<'u, T::Reified>, Self> {
if self.tag_type_id == TagTypeId::<L>::of_reify::<T>() {
Ok(unsafe { self.borrow.into_inner::<DynWantFor<'u, T::Reified>>() })
} else {
Err(self)
}
}
}
const FAT_PTR_SIZE: usize = size_of::<&mut dyn WantFor<()>>();
struct ErasedFatPtr {
bytes: MaybeUninit<[u8; FAT_PTR_SIZE]>,
_align: [*const dyn WantFor<()>; 0],
}
const _: () = {
assert!(align_of::<ErasedFatPtr>() == align_of::<*const dyn WantFor<()>>());
assert!(size_of::<ErasedFatPtr>() == size_of::<*const dyn WantFor<()>>());
};
impl ErasedFatPtr {
const fn new<T>(value: T) -> Self {
const {
assert!(size_of::<T>() <= FAT_PTR_SIZE);
assert!(!needs_drop::<T>());
}
let mut erased = Self {
bytes: MaybeUninit::<[u8; FAT_PTR_SIZE]>::uninit(),
_align: [],
};
unsafe {
erased.bytes.as_mut_ptr().cast::<T>().write(value);
}
erased
}
unsafe fn into_inner<T>(self) -> T {
const {
assert!(size_of::<T>() <= FAT_PTR_SIZE);
assert!(!needs_drop::<T>());
}
unsafe { self.bytes.as_ptr().cast::<T>().read() }
}
}
#[cfg(test)]
mod tests {
use ty_tag::lifetime_list::L1;
use super::*;
use crate::want::WantOne;
#[test]
fn demo() {
let x = String::from("hello");
let z = {
let mut opt = WantOne::<L1, &str>::new(None);
let a: ErasedWantFor<L1> = ErasedWantFor::<L1>::new::<&str>(&mut opt);
let Ok(z) = a.downcast::<&str>() else {
panic!()
};
z.fulfill(&x);
opt.into_inner().unwrap()
};
assert_eq!(z, "hello");
}
}