Skip to main content

magicstatemachines/macros/
transition.rs

1/// Performs a transition inside a module that invoked
2/// [`StateMachineImpl!`](macro@crate::StateMachineImpl).
3///
4/// This is the public transition surface for implementation methods. The
5/// macro expands to private helper methods generated by
6/// [`StateMachineImpl!`](macro@crate::StateMachineImpl), so it can only be
7/// used from the module where that implementation macro was invoked. Callers
8/// outside the module can only use the ordinary methods you expose.
9///
10/// There are six call forms:
11///
12/// - `transition!(state)` performs a concrete-state transition. The receiver
13///   state type must be a concrete marker such as `Disconnected`, and the
14///   target is inferred from the method's return type. This is the simplest
15///   and cheapest form: the compiler already knows the exact source state.
16/// - `transition!(const Online state)` performs a static union transition.
17///   This is accepted only when every member of `Online` has the same target
18///   transition, the same argument signature, and the same implementation
19///   effect body. No runtime branch is needed for the effect; the union proof
20///   is entirely type-level.
21/// - `transition!(dyn Online state)` performs a discriminated union
22///   transition. The current concrete variant is recovered first, then that
23///   variant's exact effect is run. Use this when the union members may have
24///   distinct bodies for the same target, or when the state is already stored
25///   as `DiscriminatedState<_, _, Online>`.
26/// - `transition!(pin state)` performs a concrete transition whose
27///   implementation effect was declared with `pinned transition` in
28///   [`StateMachineImpl!`](macro@crate::StateMachineImpl). The storage backend
29///   must implement [`SPinMut`](crate::SPinMut), so the effect receives
30///   `Pin<&mut Runtime>` rather than `&mut Runtime`. If the implementation
31///   declared both a normal `transition From => To` and a
32///   `pinned transition From => To`, this form selects the pinned body.
33/// - `transition!(pin const Online state)` performs the pinned version of a
34///   static union transition. It requires every member of `Online` to share
35///   the same pinned effect body and signature for the selected target, just
36///   as `transition!(const Online state)` requires a shared normal body.
37/// - `transition!(pin dyn Online state)` performs the pinned version of a
38///   discriminated union transition. It recovers the concrete variant and runs
39///   that variant's pinned effect, so different union members may have
40///   different pinned bodies for the same target.
41///
42/// Arguments are positional only. The names written in
43/// [`StateMachineDefinition!`](macro@crate::StateMachineDefinition) and
44/// [`StateMachineImpl!`](macro@crate::StateMachineImpl) document the contract,
45/// but Rust still checks only the argument order and types. Trailing commas are
46/// accepted in all forms.
47///
48/// The macro expands to a generated helper followed by `.call((args, ...))`.
49/// That keeps downstream crates away from the transition token while avoiding
50/// the unstable `Fn*` traits. In practice, callers should read
51/// `transition!(self, user.into())` as "run the declared transition effect with
52/// this positional argument and retag the state to the return type".
53///
54/// Typical implementation methods look like this:
55///
56/// ```ignore
57/// use magicstatemachines::{transition, SMut, State};
58/// use test_def::{InOnline, Online};
59/// use test_def::states::{Authenticated, Connected, Disconnected};
60///
61/// impl Connection {
62///     fn connect<S>(self: State<S, Self, Disconnected>) -> State<S, Self, Connected>
63///     where
64///         S: SMut,
65///     {
66///         transition!(self)
67///     }
68///
69///     fn authenticate<S>(
70///         self: State<S, Self, Connected>,
71///         user: impl Into<String>,
72///     ) -> State<S, Self, Authenticated>
73///     where
74///         S: SMut,
75///     {
76///         transition!(self, user.into(),)
77///     }
78///
79///     fn disconnect<S>(self: State<S, Self, impl InOnline>) -> State<S, Self, Disconnected>
80///     where
81///         S: SMut,
82///     {
83///         transition!(dyn Online self,)
84///     }
85/// }
86/// ```
87///
88/// A trailing comma is accepted even for zero-argument union transitions. This
89/// is useful in macro-generated methods where the argument list may be empty:
90///
91/// ```ignore
92/// transition!(self,);
93/// transition!(const Online self,);
94/// transition!(dyn Online self,);
95/// transition!(pin self);
96/// transition!(pin const Online self);
97/// transition!(pin dyn Online self,);
98/// ```
99///
100/// The path form is available when the marker is not imported. The path form
101/// uses a comma between the marker path and the state expression so Rust can
102/// parse the macro input unambiguously:
103///
104/// ```ignore
105/// transition!(const test_def::Online, self);
106/// transition!(dyn test_def::Online, self, user_id);
107/// transition!(pin const test_def::Online, self);
108/// transition!(pin dyn test_def::Online, self, user_id);
109/// ```
110///
111/// Named-argument syntax is intentionally not part of this macro. Even if a
112/// transition is declared as `transition Connected => Authenticated(user:
113/// String);`, the call is still positional:
114///
115/// ```ignore
116/// transition!(self, user);
117/// ```
118#[macro_export]
119macro_rules! transition {
120    (pin const $marker:path, $state:expr) => {
121        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
122    };
123    (pin const $marker:path, $state:expr,) => {
124        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
125    };
126    (pin const $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
127        $crate::transition!(@call $state._magicsm_transitionPinConst($marker), $($arg),+)
128    };
129    (pin dyn $marker:path, $state:expr) => {
130        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
131    };
132    (pin dyn $marker:path, $state:expr,) => {
133        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
134    };
135    (pin dyn $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
136        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker), $($arg),+)
137    };
138    (pin const $marker:ident $state:expr) => {
139        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
140    };
141    (pin const $marker:ident $state:expr,) => {
142        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
143    };
144    (pin const $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
145        $crate::transition!(@call $state._magicsm_transitionPinConst($marker), $($arg),+)
146    };
147    (pin dyn $marker:ident $state:expr) => {
148        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
149    };
150    (pin dyn $marker:ident $state:expr,) => {
151        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
152    };
153    (pin dyn $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
154        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker), $($arg),+)
155    };
156    (pin $state:expr) => {
157        $crate::transition!(@call $state._magicsm_transitionPin())
158    };
159    (pin $state:expr,) => {
160        $crate::transition!(@call $state._magicsm_transitionPin())
161    };
162    (pin $state:expr, $($arg:expr),+ $(,)?) => {
163        $crate::transition!(@call $state._magicsm_transitionPin(), $($arg),+)
164    };
165    (const $marker:path, $state:expr) => {
166        $crate::transition!(@call $state._magicsm_transitionConst($marker))
167    };
168    (const $marker:path, $state:expr,) => {
169        $crate::transition!(@call $state._magicsm_transitionConst($marker))
170    };
171    (const $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
172        $crate::transition!(@call $state._magicsm_transitionConst($marker), $($arg),+)
173    };
174    (dyn $marker:path, $state:expr) => {
175        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
176    };
177    (dyn $marker:path, $state:expr,) => {
178        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
179    };
180    (dyn $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
181        $crate::transition!(@call $state._magicsm_transitionDyn($marker), $($arg),+)
182    };
183    (const $marker:ident $state:expr) => {
184        $crate::transition!(@call $state._magicsm_transitionConst($marker))
185    };
186    (const $marker:ident $state:expr,) => {
187        $crate::transition!(@call $state._magicsm_transitionConst($marker))
188    };
189    (const $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
190        $crate::transition!(@call $state._magicsm_transitionConst($marker), $($arg),+)
191    };
192    (dyn $marker:ident $state:expr) => {
193        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
194    };
195    (dyn $marker:ident $state:expr,) => {
196        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
197    };
198    (dyn $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
199        $crate::transition!(@call $state._magicsm_transitionDyn($marker), $($arg),+)
200    };
201    ($state:expr) => {
202        $crate::transition!(@call $state._magicsm_transition())
203    };
204    ($state:expr,) => {
205        $crate::transition!(@call $state._magicsm_transition())
206    };
207    ($state:expr, $($arg:expr),+ $(,)?) => {
208        $crate::transition!(@call $state._magicsm_transition(), $($arg),+)
209    };
210    (@call $call:expr) => {
211        $call.call(())
212    };
213    (@call $call:expr, $($arg:expr),+ $(,)?) => {
214        $call.call(($($arg,)+))
215    };
216}