magicstatemachines/macros/state_machine_definition.rs
1/// Defines the state-machine contract for a stand-in type.
2///
3/// Use this macro in the crate that owns the state marker types and the
4/// stand-in ZST. The generated code contains only contract information:
5/// [`Initial`] implementations, [`Transition`] implementations, and optional
6/// state-union declarations. It deliberately contains no runtime type and no
7/// transition bodies. Think of this as the public state-machine interface: it
8/// says which states exist, which states may be constructed initially, and
9/// which edges are legal. If no state should be constructible from raw runtime
10/// data, omit the `pub Initial:` line entirely; values can still enter the
11/// machine through target-owned constructors that build a
12/// [`ConcreteStated`](crate::ConcreteStated) and pass it to
13/// [`State::from_concrete`](crate::State::from_concrete).
14///
15/// That separation is the main enforcement mechanism. A downstream crate can
16/// implement behavior for its runtime type, but it cannot add new legal
17/// transitions unless it owns the stand-in or the state markers. In the normal
18/// split-crate layout, the definition crate owns both, so Rust's orphan rules
19/// make the transition graph a hard public contract.
20///
21/// The transition argument list declares the signature required by
22/// [`transition!`](macro@crate::transition). Argument names are documentation
23/// for the contract; only their types participate in the generated
24/// [`Transition::F`](crate::Transition::F) signature. For example,
25/// `transition Connected => Authenticated(user: String);` emits an impl whose
26/// signature is equivalent to:
27///
28/// ```ignore
29/// impl magicstatemachines::Transition<Connected, Authenticated> for ConnectionStandin {
30/// type F = fn(String);
31/// }
32/// ```
33///
34/// It does not say what happens to the runtime data. The implementation crate
35/// supplies that effect later with [`StateMachineImpl!`](macro@crate::StateMachineImpl).
36///
37/// ```ignore
38/// use magicstatemachines::{StateMachineDefinition, States};
39///
40/// pub struct ConnectionStandin;
41///
42/// pub mod states {
43/// use magicstatemachines::States;
44///
45/// States! {
46/// Disconnected;
47/// Reconnecting;
48/// Connected;
49/// Authenticated;
50/// Failed;
51/// }
52/// }
53///
54/// use states::*;
55///
56/// StateMachineDefinition! {
57/// for ConnectionStandin;
58///
59/// pub Initial: Disconnected | Reconnecting;
60///
61/// transition Disconnected => Connected | Failed();
62/// transition Connected => Authenticated(user: String);
63/// transition Connected | Authenticated => Disconnected();
64/// transition Authenticated => Connected();
65///
66/// union All: Disconnected | Connected | Authenticated;
67/// union Online: All, Connected | Authenticated;
68/// }
69/// ```
70///
71/// `|` on the left or right of a transition expands to every pair. For
72/// example, `transition Authenticated => Connected | Failed();` declares both
73/// `Authenticated -> Connected` and `Authenticated -> Failed` with the same
74/// empty signature. `transition Connected | Authenticated => Disconnected();`
75/// declares two incoming edges into `Disconnected`. This is only a declaration;
76/// whether the implementation shares a body is decided later by
77/// [`StateMachineImpl!`](macro@crate::StateMachineImpl).
78///
79/// Union declarations are forwarded to [`StateUnion!`](macro@crate::StateUnion).
80/// They can be written here for convenience, but they are still independent of
81/// the stand-in and may also be written separately. A union such as
82/// `union Online: Connected | Authenticated;` does not add transitions by
83/// itself; it only gives APIs a way to name "any online state".
84///
85/// `pub Initial:` is intentionally a public raw-construction capability. If a
86/// state is listed there, any code that can obtain raw implementation data can
87/// call `State::new` or the generated `Runtime::with_state` for that state.
88/// Do not list a state as public initial if entering that state must be
89/// controlled by the implementation module. For that case, omit `pub Initial:`
90/// and use
91/// `priv Initial: StateName;` inside
92/// [`StateMachineImpl!`](macro@crate::StateMachineImpl) to create a private
93/// target-owned construction helper.
94///
95/// A transition declaration must end in `;`. Bodies are rejected on purpose:
96///
97/// ```compile_fail
98/// use magicstatemachines::{StateMachineDefinition, States};
99///
100/// pub struct Standin;
101/// States! { A; B; }
102///
103/// StateMachineDefinition! {
104/// for Standin;
105///
106/// pub Initial: A;
107///
108/// transition A => B() {
109/// // Effects belong in `StateMachineImpl!`, not in the definition.
110/// }
111/// }
112/// ```
113///
114/// [`Initial`]: crate::Initial
115/// [`Transition`]: crate::Transition
116#[macro_export]
117macro_rules! StateMachineDefinition {
118 (
119 for $standin:ty;
120 pub Initial: $first_initial:ident $(| $initial:ident)*;
121 $($transitions:tt)*
122 ) => {
123 $crate::__StateMachineDefinition!(@initial_impls $standin; $first_initial $(| $initial)*);
124
125 $crate::__StateMachineDefinition!(
126 @parse $standin;
127 $($transitions)*
128 );
129 };
130 (
131 for $standin:ty;
132 Initial: $first_initial:ident $(| $initial:ident)*;
133 $($transitions:tt)*
134 ) => {
135 ::core::compile_error!(
136 "`Initial:` in `StateMachineDefinition!` must be written as `pub Initial:` because public initial states allow anyone with raw owned implementation data to force this state onto an owned instance. Omit it for no public initial states, or use `priv Initial:` inside `StateMachineImpl!` for implementation-private construction."
137 );
138 };
139 (
140 for $standin:ty;
141 $($transitions:tt)*
142 ) => {
143 $crate::__StateMachineDefinition!(
144 @parse $standin;
145 $($transitions)*
146 );
147 };
148}
149
150#[doc(hidden)]
151#[macro_export]
152macro_rules! __StateMachineDefinition {
153 (@initial_impls $standin:ty; $first_initial:ident $(| $initial:ident)*) => {
154 impl $crate::Initial<$first_initial> for $standin {}
155 $(
156 impl $crate::Initial<$initial> for $standin {}
157 )*
158 };
159 (@parse $standin:ty;) => {};
160 (
161 @parse $standin:ty;
162 Initial: $first_initial:ident $(| $initial:ident)*;
163 $($rest:tt)*
164 ) => {
165 ::core::compile_error!(
166 "`Initial:` in `StateMachineDefinition!` must be written as `pub Initial:` because public initial states allow anyone with raw owned implementation data to force this state onto an owned instance. Omit it for no public initial states, or use `priv Initial:` inside `StateMachineImpl!` for implementation-private construction."
167 );
168 };
169 (
170 @parse $standin:ty;
171 union $name:ident:
172 $first:ident $(| $state:ident)*;
173 $($rest:tt)*
174 ) => {
175 $crate::StateUnion!($name: $first $(| $state)*);
176 $crate::__StateMachineDefinition!(@parse $standin; $($rest)*);
177 };
178 (
179 @parse $standin:ty;
180 union $name:ident:
181 $first_super:ident $(+ $supertrait:ident)*,
182 $first:ident $(| $state:ident)*;
183 $($rest:tt)*
184 ) => {
185 $crate::StateUnion!($name: $first_super $(+ $supertrait)*, $first $(| $state)*);
186 $crate::__StateMachineDefinition!(@parse $standin; $($rest)*);
187 };
188 (
189 @parse $standin:ty;
190 transition $first_from:ident $(| $from:ident)* => $first_to:ident $(| $to:ident)*
191 ($($arg:ident : $arg_ty:ty),* $(,)?);
192 $($rest:tt)*
193 ) => {
194 $crate::__StateMachineDefinition!(
195 @transition_impls $standin; [$first_from $(| $from)*] => [$first_to $(| $to)*]
196 ($($arg : $arg_ty),*)
197 );
198 $crate::__StateMachineDefinition!(@parse $standin; $($rest)*);
199 };
200 (
201 @parse $standin:ty;
202 transition $first_from:ident $(| $from:ident)* => $first_to:ident $(| $to:ident)*
203 ($($arg:ident : $arg_ty:ty),* $(,)?),
204 $($rest:tt)*
205 ) => {
206 ::core::compile_error!(
207 "state-machine definition transitions are declarations and must end with `;`"
208 );
209 };
210 (
211 @parse $standin:ty;
212 transition $first_from:ident $(| $from:ident)* => $first_to:ident $(| $to:ident)*
213 ($($arg:ident : $arg_ty:ty),* $(,)?) { $($body:tt)* }
214 $($rest:tt)*
215 ) => {
216 ::core::compile_error!(
217 "state-machine definition transitions cannot contain implementation bodies"
218 );
219 };
220 (
221 @parse $standin:ty;
222 transition $first_from:ident $(| $from:ident)* => $first_to:ident $(| $to:ident)*
223 ($($arg:ident : $arg_ty:ty),* $(,)?) { $($body:tt)* },
224 $($rest:tt)*
225 ) => {
226 ::core::compile_error!(
227 "state-machine definition transitions cannot contain implementation bodies"
228 );
229 };
230 (
231 @transition_impls $standin:ty; [$first_from:ident $(| $from:ident)*] => [$first_to:ident $(| $to:ident)*]
232 $args:tt
233 ) => {
234 $crate::__StateMachineDefinition!(
235 @transition_impls_for_from $standin; $first_from => [$first_to $(| $to)*] $args
236 );
237 $crate::__StateMachineDefinition!(
238 @transition_impls_for_froms $standin; [$($from)|*] => [$first_to $(| $to)*] $args
239 );
240 };
241 (@transition_impls_for_froms $standin:ty; [] => $targets:tt $args:tt) => {};
242 (
243 @transition_impls_for_froms $standin:ty; [$first_from:ident $(| $from:ident)*] => $targets:tt $args:tt
244 ) => {
245 $crate::__StateMachineDefinition!(
246 @transition_impls_for_from $standin; $first_from => $targets $args
247 );
248 $crate::__StateMachineDefinition!(
249 @transition_impls_for_froms $standin; [$($from)|*] => $targets $args
250 );
251 };
252 (
253 @transition_impls_for_from $standin:ty; $from:ident => [$first_to:ident $(| $to:ident)*]
254 $args:tt
255 ) => {
256 $crate::__StateMachineDefinition!(@transition_impl $standin; $from => $first_to $args);
257 $(
258 $crate::__StateMachineDefinition!(@transition_impl $standin; $from => $to $args);
259 )*
260 };
261 (@transition_impl $standin:ty; $from:ident => $to:ident ()) => {
262 impl $crate::Transition<$from, $to> for $standin {}
263 };
264 (
265 @transition_impl $standin:ty; $from:ident => $to:ident
266 ($first_arg:ident : $first_arg_ty:ty $(, $arg:ident : $arg_ty:ty)*)
267 ) => {
268 impl $crate::Transition<$from, $to> for $standin {
269 type F = fn($first_arg_ty $(, $arg_ty)*);
270 }
271 };
272}