supply 0.1.0

Provider API for arbitrary number of lifetimes.
Documentation
//! Wants to be given to a provider to fulfill.

mod want_one;

use core::marker::PhantomData;

pub use want_one::WantOne;

use crate::erased_wide::MutErasedWide;
use crate::lt_list::EmptyLt;
use crate::tag::{ReifySized, Tag, TagTypeId, Tagged, WithLt};
use crate::{Lt0, Lt1, LtList};

/// A want for some number of values.
pub trait Want<'r, L: LtList = Lt0> {
    /// Try to get a [`WantFor`] for the type described by `tag_type_id`.
    ///
    /// Returning `Some` from this method means the want will accept a value
    /// of the described type to become partially or fully satisfied.
    ///
    /// In most cases `<dyn Want>::try_for` should be used instead.
    fn try_for_id(
        &mut self,
        tag_type_id: TagTypeId<Lt1<'r, L>>,
    ) -> Option<ErasedWantFor<'_, Lt1<'r, L>>>;

    /// Checks if the want is fully satisfied.
    ///
    /// Once satisfied, no more values need to be provided.
    fn is_satisfied(&self) -> bool;
}

/// A want for a `T`.
pub trait WantFor<L: LtList, T: ?Sized + ReifySized<L>> {
    /// Fulfill the want with a value.
    ///
    /// Calling this multiple times is likely to just replace the value in the want.
    fn fulfill(&mut self, value: T::Reified);

    /// Checks if the want for `T` is fully satisfied.
    ///
    /// Once satisfied, no more values of type `T` need to be provided.
    fn is_satisfied(&self) -> bool;
}

/// Tag erased `&'u mut dyn WantFor<L, T>`.
///
/// This type exists to allow returning the generic type from a object safe method.
pub struct ErasedWantFor<'u, L: LtList> {
    // We want a `&'u mut (dyn WantFor<L, T> + 'u)` so we add 'u to L.
    // This doesn't effect the wanted L from the erased T because
    // DynWantFor removes 'u.
    //
    // MutErasedWide adds the &'u mut part.
    erased: MutErasedWide<'u, Lt1<'u, L>>,
}

impl<'u, L: LtList> ErasedWantFor<'u, L> {
    /// Erase a `&'u mut dyn WantFor<L, T>` so `T` does not appear in the type.
    pub fn new<T: ?Sized + ReifySized<L>>(
        want_for: &'u mut dyn WantFor<T::Lifetimes, T::Tag>,
    ) -> ErasedWantFor<'u, L> {
        ErasedWantFor {
            erased: MutErasedWide::<'u, Lt1<'u, L>>::new::<DynWantFor<T::Tag>, Lt1<'u, T::Lifetimes>>(
                want_for,
            ),
        }
    }

    /// Downcast back into a `&'u mut dyn WantFor<L, T>`.
    ///
    /// If `Err` is returned then `T` was wrong. When used with [`Want::try_for_id`],
    /// this means the implementer of [`Want`] returned the wrong implementation of
    /// [`WantFor`].
    pub fn downcast<T: ?Sized + ReifySized<L>>(
        self,
    ) -> Result<&'u mut dyn WantFor<T::Lifetimes, T::Tag>, Self> {
        self.erased
            .downcast::<DynWantFor<T::Tag>, Lt1<'u, T::Lifetimes>>()
            .map_err(|erased| Self { erased })
    }
}

// Extra non-object safe methods.
impl<'u, 'r, L: LtList> dyn Want<'r, L> + 'u {
    /// Provide a value using it's [`Tagged`] tag.
    ///
    /// Not all values implement [`Tagged`], and you may want to use a custom tag
    /// instead of the value's normal tag. In those cases use `Self::provide_tag`.
    ///
    /// This method can be chained with other provide methods.
    pub fn provide_value<T>(&mut self, value: T) -> &mut Self
    where
        T: ReifySized<Lt1<'r, L>, SizedReified = T>,
    {
        if let Some(want) = self.try_for::<T>() {
            want.fulfill(value);
        }

        self
    }

    /// Provide a tagged value to the want.
    ///
    /// `T` is a type tag as defined in [`crate::tag`].
    ///
    /// This method can be chained with other provide methods.
    ///
    /// If `value` is expensive to create, use `Self::provide`.
    // pub fn provide_tag<T: ?Sized + Tagged>(
    //     &mut self,
    //     value: SizedReifiedOf<ReifyLt<T::Lifetimes, Lt1<'r, L>>, T::Tag>,
    // ) -> &mut Self
    // where
    //     LifetimesOf<T>: LtTag<Lt1<'r, L>>,
    //     TagOf<T>: Tag<ReifyLt<T::Lifetimes, Lt1<'r, L>>>,
    // {
    //     if let Some(want) =
    //         self.try_for::<T::Tag, ReifyLt<T::Lifetimes, Lt1<'r, L>>>()
    //     {
    //         want.fulfill(value);
    //     }
    //
    //     self
    // }

    pub fn provide_tag<T: ?Sized + ReifySized<Lt1<'r, L>>>(
        &mut self,
        value: T::Reified,
    ) -> &mut Self {
        if let Some(want) = self.try_for::<T>() {
            want.fulfill(value);
        }

        self
    }

    /// Provide a tagged value to the want.
    ///
    /// `T` is a type tag as defined in [`crate::tag`].
    ///
    /// This method can be chained with other provide methods.
    ///
    /// The value is given as the result of a function. This allows
    /// not generating the value if the want does not need the value.
    /// This should be used for values that are expensive to create.
    ///
    /// If you need to pass in a context into `f`, use `Self::provide_with`.
    pub fn provide<T, F>(&mut self, f: F) -> &mut Self
    where
        T: ReifySized<Lt1<'r, L>>,
        F: FnOnce() -> <T as ReifySized<Lt1<'r, L>>>::SizedReified,
    {
        if let Some(want) = self.try_for::<T>() {
            want.fulfill(f());
        }

        self
    }

    /// Provide a tagged value to the want.
    ///
    /// `T` is a type tag as defined in [`crate::tag`].
    ///
    /// This provide method does not allow chaining as it returns the context
    /// value if the want does not want a `T`. Instead the next provide call will
    /// need to start a new chain.
    ///
    /// The value is given as the result of a function. This allows
    /// not generating the value if the want does not need the value.
    /// This should be used for values that are expensive to create.
    ///
    /// If you don't need a context, use `Self::provide_with`.
    pub fn provide_with<T, C, F>(&mut self, ctx: C, f: F) -> Option<C>
    where
        T: ReifySized<Lt1<'r, L>>,
        F: FnOnce(C) -> <T as ReifySized<Lt1<'r, L>>>::SizedReified,
    {
        if let Some(want) = self.try_for::<T>() {
            want.fulfill(f(ctx));
            None
        } else {
            Some(ctx)
        }
    }

    /// Wrapper of [`Want::try_for_id`] that takes a tag instead of a [`TagTypeId`].
    ///
    /// This method should only be used if one of the provide methods doesn't work.
    pub fn try_for<T: ?Sized + ReifySized<Lt1<'r, L>>>(
        &mut self,
    ) -> Option<&mut dyn WantFor<T::Lifetimes, T::Tag>> {
        // Call try_for_id with the id of the T passed in.
        let erased = self.try_for_id(TagTypeId::<Lt1<'r, L>>::of::<T::Tag, T::Lifetimes>())?;

        // If the want gave a bad WantFor we just ignore it.
        erased.downcast::<T>().ok()
    }
}

/// Tag for a [`WantFor`] trait object.
struct DynWantFor<T: ?Sized>(PhantomData<fn() -> *const T>);

// We need a lifetime for the trait object's usable lifetime.
impl<'u, T: ?Sized + WithLt<L>, L: LtList> WithLt<Lt1<'u, L>> for DynWantFor<T> {
    type Reified = dyn WantFor<L, T> + 'u;
}

impl<T: ?Sized + Tagged + 'static> Tagged for DynWantFor<T> {
    type Tag = DynWantFor<T::Tag>;
}

impl<T: ?Sized + Tag + 'static> Tag for DynWantFor<T> {
    type LifetimesTag = EmptyLt<T::LifetimesTag>;
}