Skip to main content

magicstatemachines/
union.rs

1use crate::{
2    MayTransition, SMapRuntime, SMove, SMut, SPinMut, SPinRef, SRef, State, StateInference,
3    StateMachineImpl, StateMarker, StateStorage, StateTrait, Transition, TransitionCallsite,
4    TransitionProof, UnionStateKind, 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 inferred_state<T, State>(inner: &Self::Inner<T, State>) -> state_trait::ErasedState
346    where
347        T: StateMachineImpl,
348        State: StateTrait,
349    {
350        inner.inference.state::<Storage, T, State>(&inner.inner)
351    }
352}
353
354impl<Storage> MayTransition for SDiscriminated<Storage>
355where
356    Storage: MayTransition,
357{
358    fn complete_transition<T, From, To, Args>(
359        state: State<Self, T, From>,
360        args: Args,
361        callsite: TransitionCallsite,
362    ) -> State<Self, T, To>
363    where
364        T: StateMachineImpl,
365        From: StateTrait,
366        To: crate::ConcreteStateTrait,
367        T::Standin: Transition<From, To>,
368        <T::Standin as Transition<From, To>>::F: crate::TransitionSignature<Args>,
369    {
370        let state = State::<Storage, T, From>::from_inner(state.inner.inner);
371        let state = Storage::complete_transition(state, args, callsite);
372        let inference =
373            <<Storage::Inference as crate::InferenceKind>::Inference as crate::StateInference>::new::<
374                Storage,
375                T,
376                To,
377            >(&state.inner);
378        State::from_inner(DiscriminatedInner {
379            inner: state.inner,
380            inference,
381        })
382    }
383
384    fn complete_transition_after_effect<T, From, To>(
385        state: State<Self, T, From>,
386        callsite: TransitionCallsite,
387    ) -> State<Self, T, To>
388    where
389        T: StateMachineImpl,
390        From: StateTrait,
391        To: crate::ConcreteStateTrait,
392    {
393        let state = State::<Storage, T, From>::from_inner(state.inner.inner);
394        let state = Storage::complete_transition_after_effect(state, callsite);
395        let inference =
396            <<Storage::Inference as crate::InferenceKind>::Inference as crate::StateInference>::new::<
397                Storage,
398                T,
399                To,
400            >(&state.inner);
401        State::from_inner(DiscriminatedInner {
402            inner: state.inner,
403            inference,
404        })
405    }
406}
407
408impl<Storage> SRef for SDiscriminated<Storage>
409where
410    Storage: SRef,
411{
412    fn s_ref<T, S>(inner: &Self::Inner<T, S>) -> &T
413    where
414        T: StateMachineImpl,
415    {
416        Storage::s_ref(&inner.inner)
417    }
418}
419
420impl<Storage> SMut for SDiscriminated<Storage>
421where
422    Storage: SMut,
423{
424    fn s_mut<T, S>(inner: &mut Self::Inner<T, S>) -> &mut T
425    where
426        T: StateMachineImpl,
427    {
428        Storage::s_mut(&mut inner.inner)
429    }
430}
431
432impl<Storage> SPinRef for SDiscriminated<Storage>
433where
434    Storage: SPinRef,
435{
436    fn s_pin_ref<T, S>(inner: &Self::Inner<T, S>) -> core::pin::Pin<&T>
437    where
438        T: StateMachineImpl,
439    {
440        Storage::s_pin_ref(&inner.inner)
441    }
442}
443
444impl<Storage> SPinMut for SDiscriminated<Storage>
445where
446    Storage: SPinMut,
447{
448    fn s_pin_mut<T, S>(inner: &mut Self::Inner<T, S>) -> core::pin::Pin<&mut T>
449    where
450        T: StateMachineImpl,
451    {
452        Storage::s_pin_mut(&mut inner.inner)
453    }
454}
455
456impl<Storage> SMove for SDiscriminated<Storage> where Storage: SMove {}
457
458impl<Storage, FromRuntime, ToRuntime> SMapRuntime<FromRuntime, ToRuntime>
459    for SDiscriminated<Storage>
460where
461    Storage: SMapRuntime<FromRuntime, ToRuntime>,
462    FromRuntime: StateMachineImpl,
463    ToRuntime: StateMachineImpl,
464{
465    fn map_runtime<S, F>(state: State<Self, FromRuntime, S>, f: F) -> State<Self, ToRuntime, S>
466    where
467        F: FnOnce(FromRuntime) -> ToRuntime,
468    {
469        let inference = state.inner.inference;
470        let state = State::<Storage, FromRuntime, S>::from_inner(state.inner.inner);
471        let state = Storage::map_runtime(state, f);
472        State::from_inner(DiscriminatedInner {
473            inner: state.inner,
474            inference,
475        })
476    }
477}
478
479/// Converts a concrete or already-erased member state into a union state.
480#[doc(hidden)]
481pub trait StateUnionErased<Marker>: StateTrait {
482    fn into_union_erased<Storage, T>(
483        state: State<Storage, T, Self>,
484    ) -> DiscriminatedState<Storage, T, Marker>
485    where
486        Self: Sized,
487        Storage: StateStorage,
488        T: StateMachineImpl,
489        Marker: StateUnionDiscriminant;
490}
491
492/// Runtime membership check for shared erased-state borrows.
493#[doc(hidden)]
494pub trait StateUnionRuntime {
495    fn contains(state: &dyn StateTrait) -> bool;
496    fn expected_type_name() -> &'static str;
497}
498
499/// Resolves transitions supported by every member of a generated state union.
500#[doc(hidden)]
501pub trait StateUnionTransition<Standin, To> {
502    type F;
503}
504
505/// Proof that a state marker is viewed through a specific generated union trait.
506#[doc(hidden)]
507pub trait StateUnionProofMembership<Marker>: StateUnionErased<Marker>
508where
509    Marker: StateUnionDiscriminant,
510{
511}
512
513/// Selects the union marker used to prove a transition to this target state.
514#[doc(hidden)]
515pub trait StateUnionProofTarget<T, From>: crate::ConcreteStateTrait + Sized
516where
517    T: StateMachineImpl,
518    From: StateTrait,
519{
520    type Marker: StateUnionDiscriminant + StateUnionSharedEffect<T, Self>;
521}
522
523/// Selects the implementation effect shared by every member of a generated state union.
524#[doc(hidden)]
525pub trait StateUnionSharedEffect<T, To>: StateUnionDiscriminant
526where
527    T: StateMachineImpl,
528    To: crate::ConcreteStateTrait,
529{
530    type Effect;
531}
532
533/// Applies the shared implementation effect for an erased union state.
534#[doc(hidden)]
535pub trait StateUnionSharedTransitionEffect<T, To, Args>: StateUnionSharedEffect<T, To>
536where
537    T: StateMachineImpl,
538    To: crate::ConcreteStateTrait,
539{
540    fn apply(value: &mut T, args: Args);
541}
542
543/// Selects the pinned implementation effect shared by every member of a generated state union.
544#[doc(hidden)]
545pub trait StateUnionSharedPinnedEffect<T, To>: StateUnionDiscriminant
546where
547    T: StateMachineImpl,
548    To: crate::ConcreteStateTrait,
549{
550    type Effect;
551}
552
553/// Applies the shared pinned implementation effect for an erased union state.
554#[doc(hidden)]
555pub trait StateUnionSharedPinnedTransitionEffect<T, To, Args>:
556    StateUnionSharedPinnedEffect<T, To>
557where
558    T: StateMachineImpl,
559    To: crate::ConcreteStateTrait,
560{
561    fn apply_pinned(value: Pin<&mut T>, args: Args);
562}
563
564/// Dispatches a discriminated union transition to the concrete state's effect.
565#[doc(hidden)]
566pub trait StateUnionDiscriminatedTransition<T, To, Args>: StateUnionDiscriminant
567where
568    T: StateMachineImpl,
569{
570    fn transition<Storage>(
571        state: DiscriminatedState<Storage, T, Self>,
572        args: Args,
573        callsite: TransitionCallsite,
574    ) -> State<Storage, T, To>
575    where
576        Storage: SMut,
577        To: crate::ConcreteStateTrait;
578}
579
580/// Dispatches a pinned discriminated union transition to the concrete state's effect.
581#[doc(hidden)]
582pub trait StateUnionDiscriminatedPinnedTransition<T, To, Args>: StateUnionDiscriminant
583where
584    T: StateMachineImpl,
585{
586    fn pinned_transition<Storage>(
587        state: DiscriminatedState<Storage, T, Self>,
588        args: Args,
589        callsite: TransitionCallsite,
590    ) -> State<Storage, T, To>
591    where
592        Storage: SPinMut,
593        To: crate::ConcreteStateTrait;
594}
595
596impl<Standin, Marker, To> Transition<StateUnionState<Marker>, To> for Standin
597where
598    Marker: StateUnionTransition<Standin, To>,
599{
600    type F = Marker::F;
601}