supply 0.3.1

Provider API for arbitrary number of lifetimes.
Documentation
/// There is a issue with the provide API. It needs to return a T infected want
/// from an object safe trait. We get around this by erasing a mutable borrow to the
/// [`WantFor`] trait. This module implements that type erasure.
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;

/// Type erased `&'u mut dyn WantFor<T::Reified>` without the `T::Reified`.
///
/// `L` contains all the lifetimes the `T::Reified` may contain.
///
/// This type exists only to return a generic `T` infected type from a dyn compatible trait method.
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> {
    /// Erase the mutable trait borrow.
    ///
    /// `T::UsedTag` will be the tag used to check if downcasting is allowed.
    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>(),
        }
    }

    /// Downcast back to the mutable trait borrow.
    ///
    /// `T` here doesn't need to be the `T` used in new, however the `UsedTag` needs to match.
    ///
    /// # Errors
    ///
    /// If the type is not `T` then `self` is returned as is.
    pub fn downcast<T: ?Sized + Reify<L>>(self) -> Result<DynWantFor<'u, T::Reified>, Self> {
        // Its important this uses the same tag as the new function.
        if self.tag_type_id == TagTypeId::<L>::of_reify::<T>() {
            // SAFETY:
            // We know that T::Reified (and thus DynWantFor as a whole) must be what was used in
            // new by the invariant the TagTypeId provides when it is equal.
            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);

            // This should never trigger because pointers used in this crate should
            // never have metadata that needs to be dropped.
            assert!(!needs_drop::<T>());
        }

        let mut erased = Self {
            bytes: MaybeUninit::<[u8; FAT_PTR_SIZE]>::uninit(),
            _align: [],
        };

        // SAFETY: We have checked that a T fits in the number of bytes.
        // We can also write a fat pointer a maybe uninit array as even
        // if the metadata part of the fat pointer has padding the maybe uninit
        // handles that.
        //
        // The alignment is correct for a T because we place the bytes in a Self.
        unsafe {
            erased.bytes.as_mut_ptr().cast::<T>().write(value);
        }

        erased
    }

    /// # Safety
    ///
    /// The value stored in `self` must be exactly type `T`.
    /// This `T` must be the one used with `new`.
    unsafe fn into_inner<T>(self) -> T {
        const {
            // This should already be covered by new, but here for completeness.
            assert!(size_of::<T>() <= FAT_PTR_SIZE);
            assert!(!needs_drop::<T>());
        }

        // SAFETY: Caller promises that self is a value of type T.
        // We can read the value out of the maybe uninit bytes because new wrote it there.
        // And we don't need to worry about aliasing because self is destroyed when this function
        // ends.
        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);

            // Erase type, keep lifetimes.
            let a: ErasedWantFor<L1> = ErasedWantFor::<L1>::new::<&str>(&mut opt);

            // Get back the want for a &str.
            let Ok(z) = a.downcast::<&str>() else {
                panic!()
            };

            // Insert a &str.
            z.fulfill(&x);

            // Unwrap the want.
            opt.into_inner().unwrap()
        };

        assert_eq!(z, "hello");
    }
}