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}