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///
119/// The `const` and `dyn` forms always need the union/state marker before the
120/// receiver. Omitting it is rejected with a targeted compiler error:
121///
122/// ```compile_fail
123/// struct Example;
124///
125/// impl Example {
126///     fn bad(self) {
127///         magicstatemachines::transition!(const self);
128///     }
129/// }
130/// ```
131///
132/// ```compile_fail
133/// struct Example;
134///
135/// impl Example {
136///     fn bad(self) {
137///         magicstatemachines::transition!(dyn self);
138///     }
139/// }
140/// ```
141///
142/// ```compile_fail
143/// struct Example;
144///
145/// impl Example {
146///     fn bad(self) {
147///         magicstatemachines::transition!(pin const self);
148///     }
149/// }
150/// ```
151///
152/// ```compile_fail
153/// struct Example;
154///
155/// impl Example {
156///     fn bad(self) {
157///         magicstatemachines::transition!(pin dyn self);
158///     }
159/// }
160/// ```
161#[macro_export]
162macro_rules! transition {
163    (pin const self $($rest:tt)*) => {
164        ::core::compile_error!(
165            "the From state needs to be provided: use `transition!(pin const FromState self, ...)` or `transition!(pin const path::To::FromState, self, ...)`"
166        )
167    };
168    (pin dyn self $($rest:tt)*) => {
169        ::core::compile_error!(
170            "the From state needs to be provided: use `transition!(pin dyn FromState self, ...)` or `transition!(pin dyn path::To::FromState, self, ...)`"
171        )
172    };
173    (const self $($rest:tt)*) => {
174        ::core::compile_error!(
175            "the From state needs to be provided: use `transition!(const FromState self, ...)` or `transition!(const path::To::FromState, self, ...)`"
176        )
177    };
178    (dyn self $($rest:tt)*) => {
179        ::core::compile_error!(
180            "the From state needs to be provided: use `transition!(dyn FromState self, ...)` or `transition!(dyn path::To::FromState, self, ...)`"
181        )
182    };
183    (pin const $marker:path, $state:expr) => {
184        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
185    };
186    (pin const $marker:path, $state:expr,) => {
187        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
188    };
189    (pin const $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
190        $crate::transition!(@call $state._magicsm_transitionPinConst($marker), $($arg),+)
191    };
192    (pin dyn $marker:path, $state:expr) => {
193        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
194    };
195    (pin dyn $marker:path, $state:expr,) => {
196        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
197    };
198    (pin dyn $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
199        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker), $($arg),+)
200    };
201    (pin const $marker:ident $state:expr) => {
202        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
203    };
204    (pin const $marker:ident $state:expr,) => {
205        $crate::transition!(@call $state._magicsm_transitionPinConst($marker))
206    };
207    (pin const $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
208        $crate::transition!(@call $state._magicsm_transitionPinConst($marker), $($arg),+)
209    };
210    (pin dyn $marker:ident $state:expr) => {
211        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
212    };
213    (pin dyn $marker:ident $state:expr,) => {
214        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker))
215    };
216    (pin dyn $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
217        $crate::transition!(@call $state._magicsm_transitionPinDyn($marker), $($arg),+)
218    };
219    (pin $state:expr) => {
220        $crate::transition!(@call $state._magicsm_transitionPin())
221    };
222    (pin $state:expr,) => {
223        $crate::transition!(@call $state._magicsm_transitionPin())
224    };
225    (pin $state:expr, $($arg:expr),+ $(,)?) => {
226        $crate::transition!(@call $state._magicsm_transitionPin(), $($arg),+)
227    };
228    (const $marker:path, $state:expr) => {
229        $crate::transition!(@call $state._magicsm_transitionConst($marker))
230    };
231    (const $marker:path, $state:expr,) => {
232        $crate::transition!(@call $state._magicsm_transitionConst($marker))
233    };
234    (const $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
235        $crate::transition!(@call $state._magicsm_transitionConst($marker), $($arg),+)
236    };
237    (dyn $marker:path, $state:expr) => {
238        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
239    };
240    (dyn $marker:path, $state:expr,) => {
241        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
242    };
243    (dyn $marker:path, $state:expr, $($arg:expr),+ $(,)?) => {
244        $crate::transition!(@call $state._magicsm_transitionDyn($marker), $($arg),+)
245    };
246    (const $marker:ident $state:expr) => {
247        $crate::transition!(@call $state._magicsm_transitionConst($marker))
248    };
249    (const $marker:ident $state:expr,) => {
250        $crate::transition!(@call $state._magicsm_transitionConst($marker))
251    };
252    (const $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
253        $crate::transition!(@call $state._magicsm_transitionConst($marker), $($arg),+)
254    };
255    (dyn $marker:ident $state:expr) => {
256        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
257    };
258    (dyn $marker:ident $state:expr,) => {
259        $crate::transition!(@call $state._magicsm_transitionDyn($marker))
260    };
261    (dyn $marker:ident $state:expr, $($arg:expr),+ $(,)?) => {
262        $crate::transition!(@call $state._magicsm_transitionDyn($marker), $($arg),+)
263    };
264    ($state:expr) => {
265        $crate::transition!(@call $state._magicsm_transition())
266    };
267    ($state:expr,) => {
268        $crate::transition!(@call $state._magicsm_transition())
269    };
270    ($state:expr, $($arg:expr),+ $(,)?) => {
271        $crate::transition!(@call $state._magicsm_transition(), $($arg),+)
272    };
273    (@call $call:expr) => {
274        $call.call(())
275    };
276    (@call $call:expr, $($arg:expr),+ $(,)?) => {
277        $call.call(($($arg,)+))
278    };
279}