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}