MagicStateMachines 0.1.2

Ergonomic typestate wrappers for compiler-enforced state machines with separable contracts
Documentation
use super::{SharedStateError, SharedStorageView, SharedValue, WrongStateError};
use crate::{
    InnerInference, MayTransition, SMut, SRef, State, StateMachineImpl, StateStorage, StateTrait,
    StateUnionRuntime, StateUnionState, Transition, TransitionCallsite,
    state_trait::{self, ErasedState},
};
use core::marker::PhantomData;
use core::ops::{Deref, DerefMut};

/// Immutable shared-state read guard used as [`State`] storage.
///
/// This is the inner value for [`StorageStateRef`]. Public shared immutable
/// borrows return `State<StorageStateRef<...>, T, S>`, not this type directly.
/// The storage implements [`SRef`] but deliberately does not implement
/// [`SMut`], so read-only arbitrary-self methods can run while generated
/// transition calls cannot complete.
///
/// ```ignore
/// let connected = shared.borrow::<Connected>()?;
/// assert_eq!(connected.endpoint(), "localhost:8080"); // `self: &State<impl SRef, ...>`
///
/// // Union borrows work too; the runtime marker is checked against the union.
/// let online = shared.borrow::<Online>()?;
/// ```
pub struct StateRef<G, T, S> {
    guard: G,
    marker: PhantomData<fn() -> (T, S)>,
}

impl<G, T, S> StateRef<G, T, S>
where
    G: Deref<Target = SharedValue<T>>,
    S: SharedBorrowState,
{
    pub(super) fn from_guard<StorageError>(
        guard: G,
    ) -> Result<Self, SharedStateError<StorageError>> {
        S::ensure_state(&guard.state)?;
        Ok(Self {
            guard,
            marker: PhantomData,
        })
    }
}

impl<G, T, S> Deref for StateRef<G, T, S>
where
    G: Deref<Target = SharedValue<T>>,
{
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.guard.value
    }
}

/// Generic [`State`] backend for an immutable shared-state guard.
///
/// This storage can expose `&T` through [`SRef`], but it cannot expose `&mut T`
/// and therefore does not implement [`SMut`]. A `State<StorageStateRef<...>>`
/// is useful for read-only methods such as
/// `fn endpoint(self: &State<impl SRef, Self, impl InOnline>) -> &str`, but it
/// cannot be transitioned by the generated [`transition!`](macro@crate::transition)
/// macro.
pub struct StorageStateRef<'a, Backend>(PhantomData<&'a Backend>);

impl<'a, Backend> StateStorage for StorageStateRef<'a, Backend>
where
    Backend: SharedStorageView + 'a,
{
    type Inference = InnerInference;

    type Inner<T, S>
        = StateRef<Backend::ReadGuard<'a, T>, T, S>
    where
        T: StateMachineImpl;
    type Machine<T>
        = T
    where
        T: StateMachineImpl;

    fn retag<T, From, To>(inner: Self::Inner<T, From>) -> Self::Inner<T, To>
    where
        T: StateMachineImpl,
    {
        StateRef {
            guard: inner.guard,
            marker: PhantomData,
        }
    }

    fn inferred_state<T, S>(inner: &Self::Inner<T, S>) -> ErasedState
    where
        T: StateMachineImpl,
        S: StateTrait,
    {
        state_trait::clone_erased(&inner.guard.state)
    }
}

impl<'a, Backend> SRef for StorageStateRef<'a, Backend>
where
    Backend: SharedStorageView + 'a,
{
    fn s_ref<T, S>(inner: &Self::Inner<T, S>) -> &T
    where
        T: StateMachineImpl,
    {
        inner
    }
}

/// Mutable typed view into shared state.
///
/// Transitions performed through this guard update an internal pending state.
/// When the guard is dropped, that pending state is committed back to the
/// shared container.
///
/// The returned guard is used through the generic [`State`] alias
/// [`SMutView`](crate::SMutView), so implementation methods can keep the same
/// `State<S, Self, Current>` receiver shape they use for owned values:
///
/// ```ignore
/// {
///     let connected = shared.borrow_mut::<Connected>()?;
///     let authenticated = magicstatemachines::transition!(
///         connected,
///         "alice".to_owned(),
///     );
///     drop(authenticated); // shared state is now `Authenticated`
/// }
/// ```
///
/// The commit happens when the final guard state is dropped. Constructing a
/// transition call object is not a commit by itself.
pub struct StateMut<G, T, S>
where
    G: DerefMut<Target = SharedValue<T>>,
{
    guard: Option<G>,
    pending: ErasedState,
    marker: PhantomData<fn() -> (T, S)>,
}

/// Generic [`State`] backend for a mutable shared-state guard.
pub struct StorageStateMut<'a, Backend>(PhantomData<&'a Backend>);

impl<'a, Backend> StateStorage for StorageStateMut<'a, Backend>
where
    Backend: SharedStorageView + 'a,
{
    type Inference = InnerInference;

    type Inner<T, S>
        = StateMut<Backend::WriteGuard<'a, T>, T, S>
    where
        T: StateMachineImpl;
    type Machine<T>
        = T
    where
        T: StateMachineImpl;

    fn retag<T, From, To>(mut inner: Self::Inner<T, From>) -> Self::Inner<T, To>
    where
        T: StateMachineImpl,
    {
        StateMut {
            guard: inner.guard.take(),
            pending: state_trait::clone_erased(&inner.pending),
            marker: PhantomData,
        }
    }

    fn inferred_state<T, S>(inner: &Self::Inner<T, S>) -> ErasedState
    where
        T: StateMachineImpl,
        S: StateTrait,
    {
        state_trait::clone_erased(&inner.pending)
    }
}

impl<'a, Backend> MayTransition for StorageStateMut<'a, Backend>
where
    Backend: SharedStorageView + 'a,
{
    fn complete_transition<T, From, To, Args>(
        mut state: State<Self, T, From>,
        _args: Args,
        _callsite: TransitionCallsite,
    ) -> State<Self, T, To>
    where
        T: StateMachineImpl,
        From: StateTrait,
        To: crate::ConcreteStateTrait,
        T::Standin: Transition<From, To>,
        <T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
    {
        State {
            inner: StateMut {
                guard: state.inner.guard.take(),
                pending: state_trait::erased_state::<To>(),
                marker: PhantomData,
            },
            marker: PhantomData,
        }
    }

    fn complete_transition_after_effect<T, From, To>(
        mut state: State<Self, T, From>,
        _callsite: TransitionCallsite,
    ) -> State<Self, T, To>
    where
        T: StateMachineImpl,
        From: StateTrait,
        To: crate::ConcreteStateTrait,
    {
        State {
            inner: StateMut {
                guard: state.inner.guard.take(),
                pending: state_trait::erased_state::<To>(),
                marker: PhantomData,
            },
            marker: PhantomData,
        }
    }
}

impl<'a, Backend> SRef for StorageStateMut<'a, Backend>
where
    Backend: SharedStorageView + 'a,
{
    fn s_ref<T, S>(inner: &Self::Inner<T, S>) -> &T
    where
        T: StateMachineImpl,
    {
        inner
    }
}

impl<'a, Backend> SMut for StorageStateMut<'a, Backend>
where
    Backend: SharedStorageView + 'a,
{
    fn s_mut<T, S>(inner: &mut Self::Inner<T, S>) -> &mut T
    where
        T: StateMachineImpl,
    {
        inner
    }
}

impl<G, T, S> StateMut<G, T, S>
where
    G: DerefMut<Target = SharedValue<T>>,
    S: SharedBorrowState,
{
    pub(super) fn from_guard<StorageError>(
        guard: G,
    ) -> Result<Self, SharedStateError<StorageError>> {
        S::ensure_state(&guard.state)?;
        let pending = S::initial_pending(&guard.state);
        Ok(Self {
            guard: Some(guard),
            pending,
            marker: PhantomData,
        })
    }
}

/// Creates a callable transition for a mutable shared-state guard.
///
/// This is the guarded-state counterpart to [`crate::transition()`].
#[must_use]
pub fn transition_mut<G, T, S, Next>(
    state: StateMut<G, T, S>,
    _token: T::TransitionToken,
) -> StateMutTransitionCall<G, T, S, Next>
where
    G: DerefMut<Target = SharedValue<T>>,
    T: StateMachineImpl,
    T::Standin: Transition<S, Next>,
{
    StateMutTransitionCall {
        state,
        to: PhantomData,
    }
}

impl<G, T, S> Deref for StateMut<G, T, S>
where
    G: DerefMut<Target = SharedValue<T>>,
{
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.guard.as_ref().expect("guard is present").value
    }
}

impl<G, T, S> DerefMut for StateMut<G, T, S>
where
    G: DerefMut<Target = SharedValue<T>>,
{
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.guard.as_mut().expect("guard is present").value
    }
}

impl<G, T, S> Drop for StateMut<G, T, S>
where
    G: DerefMut<Target = SharedValue<T>>,
{
    fn drop(&mut self) {
        if let Some(guard) = self.guard.as_mut() {
            guard.state = state_trait::clone_erased(&self.pending);
        }
    }
}

/// Low-level transition call object for [`StateMut`].
#[doc(hidden)]
pub struct StateMutTransitionCall<G, T, From, To>
where
    G: DerefMut<Target = SharedValue<T>>,
{
    state: StateMut<G, T, From>,
    to: PhantomData<fn() -> To>,
}

impl<G, T, From, To> StateMutTransitionCall<G, T, From, To>
where
    G: DerefMut<Target = SharedValue<T>>,
{
    pub fn call<Args>(mut self, _args: Args) -> StateMut<G, T, To>
    where
        T: StateMachineImpl,
        T::Standin: Transition<From, To>,
        <T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
        To: crate::ConcreteStateTrait,
    {
        StateMut {
            guard: self.state.guard.take(),
            pending: state_trait::erased_state::<To>(),
            marker: PhantomData,
        }
    }
}

/// State marker that can validate a shared-storage runtime marker.
#[doc(hidden)]
pub trait SharedBorrowState: StateTrait {
    fn ensure_state(actual: &ErasedState) -> Result<(), WrongStateError>;
    fn initial_pending(actual: &ErasedState) -> ErasedState;
}

pub auto trait ExactSharedBorrowState {}

impl<Marker> !ExactSharedBorrowState for StateUnionState<Marker> {}

impl<S> SharedBorrowState for S
where
    S: crate::ConcreteStateTrait + ExactSharedBorrowState,
{
    fn ensure_state(actual: &ErasedState) -> Result<(), WrongStateError> {
        if state_trait::is_state::<S>(actual) {
            Ok(())
        } else {
            Err(WrongStateError {
                expected: core::any::type_name::<S>(),
                actual: actual.type_name(),
            })
        }
    }

    fn initial_pending(_actual: &ErasedState) -> ErasedState {
        state_trait::erased_state::<S>()
    }
}

impl<Marker> SharedBorrowState for StateUnionState<Marker>
where
    Marker: StateUnionRuntime + 'static,
    StateUnionState<Marker>: StateTrait,
{
    fn ensure_state(actual: &ErasedState) -> Result<(), WrongStateError> {
        if Marker::contains(&**actual) {
            Ok(())
        } else {
            Err(WrongStateError {
                expected: Marker::expected_type_name(),
                actual: actual.type_name(),
            })
        }
    }

    fn initial_pending(actual: &ErasedState) -> ErasedState {
        state_trait::clone_erased(actual)
    }
}