MagicStateMachines 0.1.0

Ergonomic typestate wrappers for compiler-enforced state machines with separable contracts
Documentation
/// Performs a transition inside a module that invoked
/// [`StateMachineImpl!`](macro@crate::StateMachineImpl).
///
/// This is the public transition surface for implementation methods. The
/// macro expands to private helper methods generated by
/// [`StateMachineImpl!`](macro@crate::StateMachineImpl), so it can only be
/// used from the module where that implementation macro was invoked. Callers
/// outside the module can only use the ordinary methods you expose.
///
/// There are six call forms:
///
/// - `transition!(state)` performs a concrete-state transition. The receiver
///   state type must be a concrete marker such as `Disconnected`, and the
///   target is inferred from the method's return type. This is the simplest
///   and cheapest form: the compiler already knows the exact source state.
/// - `transition!(const Online state)` performs a static union transition.
///   This is accepted only when every member of `Online` has the same target
///   transition, the same argument signature, and the same implementation
///   effect body. No runtime branch is needed for the effect; the union proof
///   is entirely type-level.
/// - `transition!(dyn Online state)` performs a discriminated union
///   transition. The current concrete variant is recovered first, then that
///   variant's exact effect is run. Use this when the union members may have
///   distinct bodies for the same target, or when the state is already stored
///   as `DiscriminatedState<_, _, Online>`.
/// - `transition!(pin state)` performs a concrete transition whose
///   implementation effect was declared with `pinned transition` in
///   [`StateMachineImpl!`](macro@crate::StateMachineImpl). The storage backend
///   must implement [`SPinMut`](crate::SPinMut), so the effect receives
///   `Pin<&mut Runtime>` rather than `&mut Runtime`. If the implementation
///   declared both a normal `transition From => To` and a
///   `pinned transition From => To`, this form selects the pinned body.
/// - `transition!(pin const Online state)` performs the pinned version of a
///   static union transition. It requires every member of `Online` to share
///   the same pinned effect body and signature for the selected target, just
///   as `transition!(const Online state)` requires a shared normal body.
/// - `transition!(pin dyn Online state)` performs the pinned version of a
///   discriminated union transition. It recovers the concrete variant and runs
///   that variant's pinned effect, so different union members may have
///   different pinned bodies for the same target.
///
/// Arguments are positional only. The names written in
/// [`StateMachineDefinition!`](macro@crate::StateMachineDefinition) and
/// [`StateMachineImpl!`](macro@crate::StateMachineImpl) document the contract,
/// but Rust still checks only the argument order and types. Trailing commas are
/// accepted in all forms.
///
/// The macro expands to a generated helper followed by `.call((args, ...))`.
/// That keeps downstream crates away from the transition token while avoiding
/// the unstable `Fn*` traits. In practice, callers should read
/// `transition!(self, user.into())` as "run the declared transition effect with
/// this positional argument and retag the state to the return type".
///
/// Typical implementation methods look like this:
///
/// ```ignore
/// use magicstatemachines::{transition, SMut, State};
/// use test_def::{InOnline, Online};
/// use test_def::states::{Authenticated, Connected, Disconnected};
///
/// impl Connection {
///     fn connect<S>(self: State<S, Self, Disconnected>) -> State<S, Self, Connected>
///     where
///         S: SMut,
///     {
///         transition!(self)
///     }
///
///     fn authenticate<S>(
///         self: State<S, Self, Connected>,
///         user: impl Into<String>,
///     ) -> State<S, Self, Authenticated>
///     where
///         S: SMut,
///     {
///         transition!(self, user.into(),)
///     }
///
///     fn disconnect<S>(self: State<S, Self, impl InOnline>) -> State<S, Self, Disconnected>
///     where
///         S: SMut,
///     {
///         transition!(dyn Online self,)
///     }
/// }
/// ```
///
/// A trailing comma is accepted even for zero-argument union transitions. This
/// is useful in macro-generated methods where the argument list may be empty:
///
/// ```ignore
/// transition!(self,);
/// transition!(const Online self,);
/// transition!(dyn Online self,);
/// transition!(pin self);
/// transition!(pin const Online self);
/// transition!(pin dyn Online self,);
/// ```
///
/// The path form is available when the marker is not imported. The path form
/// uses a comma between the marker path and the state expression so Rust can
/// parse the macro input unambiguously:
///
/// ```ignore
/// transition!(const test_def::Online, self);
/// transition!(dyn test_def::Online, self, user_id);
/// transition!(pin const test_def::Online, self);
/// transition!(pin dyn test_def::Online, self, user_id);
/// ```
///
/// Named-argument syntax is intentionally not part of this macro. Even if a
/// transition is declared as `transition Connected => Authenticated(user:
/// String);`, the call is still positional:
///
/// ```ignore
/// transition!(self, user);
/// ```
#[macro_export]
macro_rules! transition {
    (pin const $marker:path, $state:expr) => {
        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
    };
    (pin const $marker:path, $state:expr,) => {
        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
    };
    (pin const $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
        $crate::transition!(@call $state._magicsm_transitionPinConst($marker), $($arg),+)
    };
    (pin dyn $marker:path, $state:expr) => {
        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
    };
    (pin dyn $marker:path, $state:expr,) => {
        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
    };
    (pin dyn $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker), $($arg),+)
    };
    (pin const $marker:ident $state:expr) => {
        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
    };
    (pin const $marker:ident $state:expr,) => {
        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
    };
    (pin const $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
        $crate::transition!(@call $state._magicsm_transitionPinConst($marker), $($arg),+)
    };
    (pin dyn $marker:ident $state:expr) => {
        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
    };
    (pin dyn $marker:ident $state:expr,) => {
        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
    };
    (pin dyn $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker), $($arg),+)
    };
    (pin $state:expr) => {
        $crate::transition!(@call $state._magicsm_transitionPin())
    };
    (pin $state:expr,) => {
        $crate::transition!(@call $state._magicsm_transitionPin())
    };
    (pin $state:expr, $($arg:expr),+ $(,)?) => {
        $crate::transition!(@call $state._magicsm_transitionPin(), $($arg),+)
    };
    (const $marker:path, $state:expr) => {
        $crate::transition!(@call $state._magicsm_transitionConst($marker))
    };
    (const $marker:path, $state:expr,) => {
        $crate::transition!(@call $state._magicsm_transitionConst($marker))
    };
    (const $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
        $crate::transition!(@call $state._magicsm_transitionConst($marker), $($arg),+)
    };
    (dyn $marker:path, $state:expr) => {
        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
    };
    (dyn $marker:path, $state:expr,) => {
        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
    };
    (dyn $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
        $crate::transition!(@call $state._magicsm_transitionDyn($marker), $($arg),+)
    };
    (const $marker:ident $state:expr) => {
        $crate::transition!(@call $state._magicsm_transitionConst($marker))
    };
    (const $marker:ident $state:expr,) => {
        $crate::transition!(@call $state._magicsm_transitionConst($marker))
    };
    (const $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
        $crate::transition!(@call $state._magicsm_transitionConst($marker), $($arg),+)
    };
    (dyn $marker:ident $state:expr) => {
        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
    };
    (dyn $marker:ident $state:expr,) => {
        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
    };
    (dyn $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
        $crate::transition!(@call $state._magicsm_transitionDyn($marker), $($arg),+)
    };
    ($state:expr) => {
        $crate::transition!(@call $state._magicsm_transition())
    };
    ($state:expr,) => {
        $crate::transition!(@call $state._magicsm_transition())
    };
    ($state:expr, $($arg:expr),+ $(,)?) => {
        $crate::transition!(@call $state._magicsm_transition(), $($arg),+)
    };
    (@call $call:expr) => {
        $call.call(())
    };
    (@call $call:expr, $($arg:expr),+ $(,)?) => {
        $call.call(($($arg,)+))
    };
}