Skip to main content

magicstatemachines/
union.rs

1use crate::{
2    SMove, SMut, SPinMut, SPinRef, SRef, State, StateInference, StateMachineImpl, StateMarker,
3    StateStorage, StateTrait, Transition, TransitionCallsite, TransitionProof, UnionStateKind,
4    state_trait,
5};
6use core::{any::TypeId, marker::PhantomData, pin::Pin};
7
8/// State marker shared by every member of a generated state union.
9#[doc(hidden)]
10pub struct StateUnionState<Marker>(PhantomData<fn() -> Marker>);
11
12impl<Marker> StateUnionState<Marker> {
13    #[doc(hidden)]
14    #[must_use]
15    pub const fn new() -> Self {
16        Self(PhantomData)
17    }
18}
19
20/// Implemented by concrete states that can still identify their enum variant.
21#[doc(hidden)]
22pub auto trait StateUnionConcreteState {}
23
24impl<Marker> !StateUnionConcreteState for StateUnionState<Marker> {}
25
26/// Records that `State` belongs to a generated state union.
27#[doc(hidden)]
28pub trait StateUnionMember<State> {}
29
30/// Selects the value-carrying enum type for a union marker.
31///
32/// `StateUnion!` implements this for the generated marker. For
33/// `StateUnion!(Online: Connected | Authenticated)`, the associated `Enum` is
34/// `OnlineEnum<Storage, T>` unless a custom enum name was supplied.
35///
36/// Users normally do not implement this trait manually. It is useful in
37/// generic APIs that need to name the enum associated with a marker:
38///
39/// ```ignore
40/// type OnlineValue<S, T> =
41///     <Online as magicstatemachines::StateUnionDiscriminant>::Enum<S, T>;
42/// ```
43pub trait StateUnionDiscriminant: Sized + StateMarker<Kind = UnionStateKind> {
44    /// Generated enum that can hold any concrete member state of this union.
45    ///
46    /// For `StateUnion!(Online: Connected | Authenticated)`, this is
47    /// `OnlineEnum<Storage, T>` unless a custom enum name was supplied.
48    type Enum<Storage, T>
49    where
50        Storage: StateStorage,
51        T: StateMachineImpl;
52
53    #[doc(hidden)]
54    fn discriminate<Storage, T>(
55        state: DiscriminatedState<Storage, T, Self>,
56    ) -> Self::Enum<Storage, T>
57    where
58        Storage: StateStorage,
59        T: StateMachineImpl;
60}
61
62/// Marks a state as being a concrete marker or a member of a generated state union.
63///
64/// Every concrete state implements `In<Self>`. Generated union membership
65/// traits such as `InOnline` extend `In<Online>` and add sealing/proof bounds.
66///
67/// Prefer the generated trait (`InOnline`) in normal function signatures. The
68/// generated trait carries the sealed union contract and any generated
69/// super-union relationships, so a value accepted as `impl InOnline` can also
70/// be used with APIs that accept wider generated traits such as `impl InAll`.
71/// A bare `impl In<Online>` proves only membership in that one marker and does
72/// not give Rust the same widened trait bounds.
73///
74/// ```ignore
75/// fn endpoint<S>(self: &State<S, Connection, impl InOnline>) -> &str
76/// where
77///     S: SRef,
78/// {
79///     &self.endpoint
80/// }
81/// ```
82///
83/// Use the generic form (`In<Online>`) when the marker is itself a type
84/// parameter, or when calling the associated conversion function explicitly.
85/// It is a lower-level building block, not the best ergonomic bound for public
86/// methods:
87///
88/// ```ignore
89/// fn to_online<S, Current>(
90///     state: State<S, Connection, Current>,
91/// ) -> DiscriminatedState<S, Connection, Online>
92/// where
93///     S: StateStorage,
94///     Current: In<Online>,
95/// {
96///     <Current as In<Online>>::into_discriminated(state)
97/// }
98/// ```
99pub trait In<Marker>: StateTrait + StateMarker
100where
101    Marker: StateMarker,
102{
103    /// Converts a concrete member state into the union's discriminated state.
104    ///
105    /// The returned [`DiscriminatedState`] keeps enough runtime information to
106    /// recover the concrete enum variant later with `discriminate()`.
107    #[must_use]
108    fn into_discriminated<Storage, T>(
109        state: State<Storage, T, Self>,
110    ) -> DiscriminatedState<Storage, T, Marker>
111    where
112        Self: Sized,
113        Storage: StateStorage,
114        T: StateMachineImpl,
115        Marker: StateUnionDiscriminant;
116
117    #[doc(hidden)]
118    #[must_use]
119    fn prove<Storage, T, To>()
120    -> TransitionProof<Storage, T, Self, Marker, To, <Marker as StateMarker>::Kind>
121    where
122        Self: Sized,
123        Storage: StateStorage,
124        T: StateMachineImpl,
125        To: StateTrait + StateMarker,
126    {
127        TransitionProof::new()
128    }
129}
130
131impl<StateMarkerType> In<StateMarkerType> for StateMarkerType
132where
133    StateMarkerType: StateTrait + StateMarker,
134{
135    fn into_discriminated<Storage, T>(
136        _state: State<Storage, T, Self>,
137    ) -> DiscriminatedState<Storage, T, StateMarkerType>
138    where
139        Self: Sized,
140        Storage: StateStorage,
141        T: StateMachineImpl,
142        StateMarkerType: StateUnionDiscriminant,
143    {
144        unreachable!("concrete identity states are not generated state unions")
145    }
146}
147
148/// Union-typed state that still remembers its concrete variant.
149///
150/// This is a type alias for `State<SDiscriminated<Storage>, T,
151/// StateUnionState<Marker>>`. The outer state marker is the union, so methods
152/// can accept it as "any online state". The storage wrapper remembers the
153/// exact concrete member, so `discriminate()` can later recover the generated
154/// enum:
155///
156/// ```ignore
157/// let online: DiscriminatedState<SOwned, Connection, Online> =
158///     <Connected as In<Online>>::into_discriminated(connected);
159///
160/// match online.discriminate() {
161///     OnlineEnum::Connected(connected) => {
162///         // `connected` is State<SOwned, Connection, Connected>.
163///     }
164///     OnlineEnum::Authenticated(authenticated) => {
165///         // `authenticated` is State<SOwned, Connection, Authenticated>.
166///     }
167/// }
168/// ```
169///
170/// Dynamic union transitions use the same discriminator internally. That is
171/// why `transition!(dyn Online self)` can run different bodies for different
172/// union members.
173pub type DiscriminatedState<Storage, T, Marker> =
174    State<SDiscriminated<Storage>, T, StateUnionState<Marker>>;
175
176impl<Storage, T, Marker> State<SDiscriminated<Storage>, T, StateUnionState<Marker>>
177where
178    Storage: StateStorage,
179    T: StateMachineImpl,
180    Marker: StateUnionDiscriminant,
181{
182    /// Recovers the generated enum for this discriminated union state.
183    ///
184    /// This is the runtime branch point: after matching on the enum, each
185    /// variant can be converted back into its concrete state.
186    ///
187    /// ```ignore
188    /// match online.discriminate() {
189    ///     OnlineEnum::Connected(connected) => {
190    ///         let authenticated = connected.authenticate("alice");
191    ///     }
192    ///     OnlineEnum::Authenticated(authenticated) => {
193    ///         let connected = authenticated.logout();
194    ///     }
195    /// }
196    /// ```
197    #[must_use]
198    pub fn discriminate(self) -> <Marker as StateUnionDiscriminant>::Enum<Storage, T> {
199        Marker::discriminate(self)
200    }
201}
202
203#[doc(hidden)]
204#[must_use]
205pub fn discriminate_state<Storage, T, From, Marker>(
206    state: State<Storage, T, From>,
207) -> DiscriminatedState<Storage, T, Marker>
208where
209    Storage: StateStorage,
210    T: StateMachineImpl,
211    From: crate::ConcreteStateTrait,
212    Marker: StateUnionDiscriminant,
213{
214    let inference =
215        <<Storage::Inference as crate::InferenceKind>::Inference as crate::StateInference>::new::<
216            Storage,
217            T,
218            From,
219        >(&state.inner);
220    State::from_inner(DiscriminatedInner {
221        inner: Storage::retag(state.inner),
222        inference,
223    })
224}
225
226#[doc(hidden)]
227#[must_use]
228pub fn rediscriminate_union_state<Storage, T, FromMarker, ToMarker>(
229    state: State<Storage, T, StateUnionState<FromMarker>>,
230) -> DiscriminatedState<Storage, T, ToMarker>
231where
232    Storage: StateStorage,
233    T: StateMachineImpl,
234    FromMarker: StateUnionDiscriminant,
235    StateUnionState<FromMarker>: StateTrait,
236    ToMarker: StateUnionDiscriminant,
237{
238    let inferred_state = Storage::inferred_state(&state.inner);
239    let inference =
240        <<Storage::Inference as crate::InferenceKind>::Inference as crate::StateInference>::from_erased(
241            inferred_state,
242        );
243    State::from_inner(DiscriminatedInner {
244        inner: Storage::retag(state.inner),
245        inference,
246    })
247}
248
249#[doc(hidden)]
250#[must_use]
251pub fn undiscriminate_state<Storage, T, S>(
252    state: State<SDiscriminated<Storage>, T, S>,
253) -> State<Storage, T, S>
254where
255    Storage: StateStorage,
256    T: StateMachineImpl,
257{
258    State::from_inner(state.inner.inner)
259}
260
261#[doc(hidden)]
262#[must_use]
263pub fn concretize_discriminated_state<Storage, T, Marker, Concrete>(
264    state: DiscriminatedState<Storage, T, Marker>,
265) -> State<Storage, T, Concrete>
266where
267    Storage: StateStorage,
268    T: StateMachineImpl,
269    Marker: StateUnionDiscriminant,
270{
271    State::from_inner(Storage::retag(state.inner.inner))
272}
273
274#[doc(hidden)]
275#[must_use]
276pub fn discriminated_state_marker<Storage, T, S>(
277    state: &State<SDiscriminated<Storage>, T, S>,
278) -> state_trait::ErasedState
279where
280    Storage: StateStorage,
281    T: StateMachineImpl,
282    S: StateTrait,
283{
284    state
285        .inner
286        .inference
287        .state::<Storage, T, S>(&state.inner.inner)
288}
289
290#[doc(hidden)]
291#[must_use]
292pub fn state_union_marker<Storage, T, S>(state: &State<Storage, T, S>) -> state_trait::ErasedState
293where
294    Storage: StateStorage,
295    T: StateMachineImpl,
296    S: StateTrait,
297{
298    Storage::inferred_state(&state.inner)
299}
300
301#[doc(hidden)]
302#[must_use]
303pub fn erased_state_type_id(state: &state_trait::ErasedState) -> TypeId {
304    state.type_id()
305}
306
307/// Storage backend that carries a discriminated union variant alongside another backend.
308#[doc(hidden)]
309pub struct SDiscriminated<Storage>(PhantomData<fn() -> Storage>);
310
311#[doc(hidden)]
312pub struct DiscriminatedInner<Inner, Inference> {
313    pub(crate) inner: Inner,
314    pub(crate) inference: Inference,
315}
316
317impl<Storage> StateStorage for SDiscriminated<Storage>
318where
319    Storage: StateStorage,
320{
321    type Inference = Storage::Inference;
322
323    type Inner<T, S>
324        = DiscriminatedInner<
325        Storage::Inner<T, S>,
326        <Storage::Inference as crate::InferenceKind>::Inference,
327    >
328    where
329        T: StateMachineImpl;
330    type Machine<T>
331        = Storage::Machine<T>
332    where
333        T: StateMachineImpl;
334
335    fn retag<T, From, To>(inner: Self::Inner<T, From>) -> Self::Inner<T, To>
336    where
337        T: StateMachineImpl,
338    {
339        DiscriminatedInner {
340            inner: Storage::retag(inner.inner),
341            inference: inner.inference,
342        }
343    }
344
345    fn complete_transition<T, From, To, Args>(
346        state: State<Self, T, From>,
347        args: Args,
348        callsite: TransitionCallsite,
349    ) -> State<Self, T, To>
350    where
351        T: StateMachineImpl,
352        From: StateTrait,
353        To: crate::ConcreteStateTrait,
354        T::Standin: Transition<From, To>,
355        <T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
356    {
357        let state = State::<Storage, T, From>::from_inner(state.inner.inner);
358        let state = Storage::complete_transition(state, args, callsite);
359        let inference =
360            <<Storage::Inference as crate::InferenceKind>::Inference as crate::StateInference>::new::<
361                Storage,
362                T,
363                To,
364            >(&state.inner);
365        State::from_inner(DiscriminatedInner {
366            inner: state.inner,
367            inference,
368        })
369    }
370
371    fn complete_transition_after_effect<T, From, To>(
372        state: State<Self, T, From>,
373        callsite: TransitionCallsite,
374    ) -> State<Self, T, To>
375    where
376        T: StateMachineImpl,
377        From: StateTrait,
378        To: crate::ConcreteStateTrait,
379    {
380        let state = State::<Storage, T, From>::from_inner(state.inner.inner);
381        let state = Storage::complete_transition_after_effect(state, callsite);
382        let inference =
383            <<Storage::Inference as crate::InferenceKind>::Inference as crate::StateInference>::new::<
384                Storage,
385                T,
386                To,
387            >(&state.inner);
388        State::from_inner(DiscriminatedInner {
389            inner: state.inner,
390            inference,
391        })
392    }
393
394    fn inferred_state<T, State>(inner: &Self::Inner<T, State>) -> state_trait::ErasedState
395    where
396        T: StateMachineImpl,
397        State: StateTrait,
398    {
399        inner.inference.state::<Storage, T, State>(&inner.inner)
400    }
401}
402
403impl<Storage> SRef for SDiscriminated<Storage>
404where
405    Storage: SRef,
406{
407    fn s_ref<T, S>(inner: &Self::Inner<T, S>) -> &T
408    where
409        T: StateMachineImpl,
410    {
411        Storage::s_ref(&inner.inner)
412    }
413}
414
415impl<Storage> SMut for SDiscriminated<Storage>
416where
417    Storage: SMut,
418{
419    fn s_mut<T, S>(inner: &mut Self::Inner<T, S>) -> &mut T
420    where
421        T: StateMachineImpl,
422    {
423        Storage::s_mut(&mut inner.inner)
424    }
425}
426
427impl<Storage> SPinRef for SDiscriminated<Storage>
428where
429    Storage: SPinRef,
430{
431    fn s_pin_ref<T, S>(inner: &Self::Inner<T, S>) -> core::pin::Pin<&T>
432    where
433        T: StateMachineImpl,
434    {
435        Storage::s_pin_ref(&inner.inner)
436    }
437}
438
439impl<Storage> SPinMut for SDiscriminated<Storage>
440where
441    Storage: SPinMut,
442{
443    fn s_pin_mut<T, S>(inner: &mut Self::Inner<T, S>) -> core::pin::Pin<&mut T>
444    where
445        T: StateMachineImpl,
446    {
447        Storage::s_pin_mut(&mut inner.inner)
448    }
449}
450
451impl<Storage> SMove for SDiscriminated<Storage> where Storage: SMove {}
452
453/// Converts a concrete or already-erased member state into a union state.
454#[doc(hidden)]
455pub trait StateUnionErased<Marker>: StateTrait {
456    fn into_union_erased<Storage, T>(
457        state: State<Storage, T, Self>,
458    ) -> DiscriminatedState<Storage, T, Marker>
459    where
460        Self: Sized,
461        Storage: StateStorage,
462        T: StateMachineImpl,
463        Marker: StateUnionDiscriminant;
464}
465
466/// Runtime membership check for shared erased-state borrows.
467#[doc(hidden)]
468pub trait StateUnionRuntime {
469    fn contains(state: &dyn StateTrait) -> bool;
470    fn expected_type_name() -> &'static str;
471}
472
473/// Resolves transitions supported by every member of a generated state union.
474#[doc(hidden)]
475pub trait StateUnionTransition<Standin, To> {
476    type F;
477}
478
479/// Proof that a state marker is viewed through a specific generated union trait.
480#[doc(hidden)]
481pub trait StateUnionProofMembership<Marker>: StateUnionErased<Marker>
482where
483    Marker: StateUnionDiscriminant,
484{
485}
486
487/// Selects the union marker used to prove a transition to this target state.
488#[doc(hidden)]
489pub trait StateUnionProofTarget<T, From>: crate::ConcreteStateTrait + Sized
490where
491    T: StateMachineImpl,
492    From: StateTrait,
493{
494    type Marker: StateUnionDiscriminant + StateUnionSharedEffect<T, Self>;
495}
496
497/// Selects the implementation effect shared by every member of a generated state union.
498#[doc(hidden)]
499pub trait StateUnionSharedEffect<T, To>: StateUnionDiscriminant
500where
501    T: StateMachineImpl,
502    To: crate::ConcreteStateTrait,
503{
504    type Effect;
505}
506
507/// Applies the shared implementation effect for an erased union state.
508#[doc(hidden)]
509pub trait StateUnionSharedTransitionEffect<T, To, Args>: StateUnionSharedEffect<T, To>
510where
511    T: StateMachineImpl,
512    To: crate::ConcreteStateTrait,
513{
514    fn apply(value: &mut T, args: Args);
515}
516
517/// Selects the pinned implementation effect shared by every member of a generated state union.
518#[doc(hidden)]
519pub trait StateUnionSharedPinnedEffect<T, To>: StateUnionDiscriminant
520where
521    T: StateMachineImpl,
522    To: crate::ConcreteStateTrait,
523{
524    type Effect;
525}
526
527/// Applies the shared pinned implementation effect for an erased union state.
528#[doc(hidden)]
529pub trait StateUnionSharedPinnedTransitionEffect<T, To, Args>:
530    StateUnionSharedPinnedEffect<T, To>
531where
532    T: StateMachineImpl,
533    To: crate::ConcreteStateTrait,
534{
535    fn apply_pinned(value: Pin<&mut T>, args: Args);
536}
537
538/// Dispatches a discriminated union transition to the concrete state's effect.
539#[doc(hidden)]
540pub trait StateUnionDiscriminatedTransition<T, To, Args>: StateUnionDiscriminant
541where
542    T: StateMachineImpl,
543{
544    fn transition<Storage>(
545        state: DiscriminatedState<Storage, T, Self>,
546        args: Args,
547        callsite: TransitionCallsite,
548    ) -> State<Storage, T, To>
549    where
550        Storage: SMut,
551        To: crate::ConcreteStateTrait;
552}
553
554/// Dispatches a pinned discriminated union transition to the concrete state's effect.
555#[doc(hidden)]
556pub trait StateUnionDiscriminatedPinnedTransition<T, To, Args>: StateUnionDiscriminant
557where
558    T: StateMachineImpl,
559{
560    fn pinned_transition<Storage>(
561        state: DiscriminatedState<Storage, T, Self>,
562        args: Args,
563        callsite: TransitionCallsite,
564    ) -> State<Storage, T, To>
565    where
566        Storage: SPinMut,
567        To: crate::ConcreteStateTrait;
568}
569
570impl<Standin, Marker, To> Transition<StateUnionState<Marker>, To> for Standin
571where
572    Marker: StateUnionTransition<Standin, To>,
573{
574    type F = Marker::F;
575}