devela/code/result/
enum.rs

1// devela::code::result::enum
2//
3//! Defines the [`Enum`] type.
4//
5
6use crate::{ConstDefault, ExtAny, is};
7
8// 12 variants by default
9impl_enum!(A:0+1, B:1+2, C:2+3, D:3+4, E:4+5, F:5+6, G:6+7, H:7+8, I:8+9, J:9+10, K:10+11, L:11+12);
10
11/// Defines [`Enum`] and implements all the methods.
12//
13//
14// Supports >= 3 variants.
15macro_rules! impl_enum {
16    (
17    // var_name : var_idx(0-based) + var_nth(1-based)
18    $A:ident: $_a:literal + $a:literal,
19    $B:ident: $_b:literal + $b:literal,
20    $C:ident: $_c:literal + $c:literal,
21    $($T:ident : $idx:literal + $nth:literal),*) => {
22        impl_enum!(define_enum: $A, $B, $C $(, $T:$nth)*);
23        impl_enum!(impl_default: $A, $B, $C $(, $T)*);
24        impl_enum!(methods_general: $A:$_a+$a, $B:$_a+$b, $C:$_c+$c $(, $T:$idx+$nth)*);
25        impl_enum!(methods_individual: $A, $B, $C $(, $T)*);
26    };
27    (define_enum: $A:ident, $B:ident, $C:ident, $($rest:ident:$nth:literal),*) => { $crate::paste! {
28        /// A generic, parameterized *enum* for expressing structured alternatives.
29        ///
30        /// Variants are expected to be **contiguous**, meaning `()` (unit types)
31        /// should only appear at the **end**.
32        ///
33        /// The **first variant** (`A`) is considered the default,
34        /// implementing [`Default`] when `A: Default`.
35        #[non_exhaustive]
36        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
37        pub enum Enum<const LEN: usize, $A, $B, $C=(), $($rest = ()),*> {
38            #[doc = "The 1st variant (default)."] $A($A),
39            #[doc = "The 2nd variant."] $B($B),
40            #[doc = "The 3rd variant."] $C($C),
41            $( #[doc = "The " $nth "th variant."] $rest($rest), )*
42        }
43    }};
44    (
45    impl_default: $A:ident $(, $rest:ident)*) => {
46        impl<const LEN: usize, $A: Default, $($rest),*> Default for Enum<LEN, $A, $($rest),*> {
47            fn default() -> Self { Enum::$A($A::default()) }
48        }
49        impl<const LEN: usize, $A: ConstDefault, $($rest),*> ConstDefault
50            for Enum<LEN, $A, $($rest),*> {
51            const DEFAULT: Self = Enum::$A($A::DEFAULT);
52        }
53    };
54    (
55    // Implements:
56    // - LEN
57    // - MAX_ARITY
58    // - variant_index
59    // - is_variant_index
60    // - variant_name
61    // - is_variant_name
62    //
63    // - first_non_unit
64    // - validate
65    //
66    // - into_tuple_options
67    // - into_tuple_defaults
68    methods_general: $($T:ident : $idx:literal + $nth:literal),+) => {
69        /// # Structural methods.
70        impl<const LEN:usize,  $($T),+ > Enum<LEN, $($T),+> {
71            /// The number of active (non-`()` type) variants.
72            pub const LEN: usize = {
73                assert![LEN <= Self::MAX_ARITY, "LEN must be <= MAX_ARITY"];
74                LEN
75            };
76            /// The maximum number of generic type parameters in this enum.
77            pub const MAX_ARITY: usize = $crate::ident_total!($($T),+);
78            // pub const MAX_ARITY: usize = $crate::capture_last![literal $($nth),+]; // BENCH
79
80            /// Returns the current variant index (0-based).
81            pub const fn variant_index(&self) -> usize {
82                match self { $( Enum::$T(_) => $idx ),+ }
83            }
84            /// Checks whether the current variant is at `index` (0-based).
85            pub const fn is_variant_index(&self, index: usize) -> bool {
86                self.variant_index() == index
87            }
88
89            /// Returns the current variant name.
90            pub const fn variant_name(&self) -> &'static str {
91                match self { $( Enum::$T(_) => stringify!($T) ),+ }
92            }
93            /// Checks whether the current variant has the given `name`.
94            pub const fn is_variant_name(&self, name: &str) -> bool {
95                $crate::Slice::<&str>::eq(self.variant_name(), name)
96            }
97        }
98        impl<const LEN: usize, $($T: 'static),+ > Enum<LEN, $($T),+> {
99            /// Returns the first non-unit variant name, if any.
100            // WAIT: [const_type_id](https://github.com/rust-lang/rust/issues/77125)
101            pub fn first_non_unit() -> Option<&'static str> {
102                $( if <$T>::type_id() != <()>::type_id() { return Some(stringify!($T)); } )+
103                None
104            }
105
106            /// Validates that inactive `()` variants only appear at the end,
107            /// and that `LEN` equals the number of active variants.
108            #[allow(unused_assignments, reason = "wont be read in all cases")]
109            pub fn validate() -> bool {
110                let mut non_unit_count = 0;
111                let mut unit_found = false;
112                $(
113                    if <$T>::type_id() == <()>::type_id() {
114                        unit_found = true;
115                    } else {
116                        if unit_found { return false; }
117                        non_unit_count += 1;
118                    }
119                )+
120                LEN == non_unit_count
121            }
122        }
123        /// # Conversion methods.
124        impl<const LEN: usize, $($T: Clone),+ > Enum<LEN, $($T),+> {
125            /// Returns a tuple with `Some(value)` for the active variant and `None` elsewhere.
126            pub fn into_tuple_options(self) -> ($(Option<$T>),+) { $crate::paste! {
127                let index = self.variant_index();
128                ( $(
129                    if $idx == index {
130                        self.clone().[<into_ $T:lower>]()
131                    } else {
132                        None::<$T>
133                    }
134                ),+ )
135            }}
136
137            /// Returns a tuple with the active variant’s inner value in its corresponding position
138            /// and `Default::default()` for all others.
139            pub fn into_tuple_defaults(self) -> ($($T),+) where $($T: Default),+ { $crate::paste! {
140                let index = self.variant_index();
141                ( $(
142                    if $idx == index {
143                        self.clone().[<into_ $T:lower>]().unwrap()
144                    } else {
145                        Default::default()
146                    }
147                ),+ )
148            }}
149        }
150        impl<const LEN: usize, $($T),+ > Enum<LEN, $($T),+> {
151            /// Returns a tuple with `Some(&value)` for the active variant and `None` elsewhere.
152            // WAIT: [const_type_id](https://github.com/rust-lang/rust/issues/77125)
153            // FUTURE: make the `()` types not wrapped in option.
154            pub fn as_tuple_ref_options(&self) -> ($(Option<&$T>),+) { $crate::paste! {
155                ( $(
156                    if $idx == self.variant_index() {
157                        self.[<as_ref_ $T:lower>]()
158                    } else {
159                        None::<&$T>
160                    }
161                ),+ )
162            }}
163        }
164    };
165    (
166    // Implements methods acting over individual fields.
167    methods_individual: $($T:ident),+) => {
168        /// # Variant-specific methods.
169        impl<const LEN: usize, $($T),+> Enum<LEN, $($T),+> {
170            impl_enum!(methods_field_access: $($T),+);
171            impl_enum!(methods_map: $($T),+);
172        }
173    };
174    (
175    // implements:
176    // - is_*
177    // - into_*
178    // - as_ref_*
179    // - as_mut_*
180    methods_field_access: $($T:ident),+) => {
181        $( impl_enum! { @methods_field_access: $T } )+
182    };
183    (@methods_field_access: $T:ident) => { $crate::paste! {
184        #[doc = "Returns `true` if the value is of type [`" $T "`][Self::" $T "]"]
185        pub const fn [<is_ $T:lower>](&self) -> bool { matches!(self, Enum::$T(_)) }
186
187        #[doc = "Returns the inner `" $T "` value, if present."]
188        pub fn [<into_ $T:lower>](self) -> Option<$T> {
189            is![let Self::$T([<$T:lower>]) = self; Some([<$T:lower>]); None]
190        }
191        #[doc = "Returns a reference to the inner `" $T "` value, if present."]
192        pub fn [<as_ref_ $T:lower>](&self) -> Option<&$T> {
193            is![let Self::$T([<$T:lower>]) = self; Some([<$T:lower>]); None]
194        }
195        #[doc = "Returns a reference to the inner `" $T "` value, if present."]
196        pub fn [<as_mut_ $T:lower>](&mut self) -> Option<&mut $T> {
197            is![let Self::$T([<$T:lower>]) = self; Some([<$T:lower>]); None]
198        }
199    }};
200    (
201    // implements:
202    // - map_*
203    methods_map: $first:ident $(, $rest:ident)*) => {
204        // For the first variant, the `$before` list is empty.
205        impl_enum!(@methods_map: $first, (), ($($rest),*));
206        // Then, delegate to the helper macro with the first element as the accumulator.
207        impl_enum!(@methods_map_helper: ($first), ($($rest),*));
208
209        // NOTE: generates something like the following (e.g. for 6 variants):
210        //
211        // impl_map_method!(A, (/*$before*/), (B, C, D, E, F));
212        // impl_map_method!(B, (A), (C, D, E, F));
213        // impl_map_method!(C, (A, B), (D, E, F));
214        // impl_map_method!(D, (A, B, C), (E, F));
215        // impl_map_method!(E, (A, B, C, D), (F));
216        // impl_map_method!(F, (A, B, C, D, E), (/*$after*/));
217    };
218    (
219    @methods_map: $T:ident, ( $($before:ident),* ), ( $($after:ident),* )) => { $crate::paste! {
220        #[doc = "Transforms the inner `" $T "` value using `f`, leaving other variants unchanged."]
221        pub fn [<map_ $T:lower>]<NEW>(self, f: impl FnOnce($T) -> NEW)
222            -> Enum<LEN, $($before,)* NEW, $($after,)* > {
223            match self {
224                $( Self::$before(val) => Enum::$before(val), )*
225                Self::$T(val) => Enum::$T(f(val)),
226                $( Self::$after(val) => Enum::$after(val), )*
227            }
228        }
229        // NOTE: Generates methods like the following (e.g. for variant C, of 6):
230        //
231        // pub fn map_c<NEW>(self, f: impl FnOnce(C) -> NEW) -> Enum<A, B, NEW, D, E, F> {
232        //     match self {
233        //         Self::A(a) => Enum::A(a),    // $before
234        //         Self::B(b) => Enum::B(b),    // …
235        //         Self::C(c) => Enum::C(f(c)), // $T
236        //         Self::D(d) => Enum::D(d),    // $after
237        //         Self::E(e) => Enum::E(e),    // …
238        //         Self::F(f) => Enum::F(f),    // …
239        //     }
240        // }
241    }};
242    // Stop when there are no types left in the `$after` list.
243    (@methods_map_helper: ($($before:ident),*), ()) => {};
244    // Recursively take the next type as the current one.
245    (@methods_map_helper: ($($before:ident),*), ($first:ident $(, $rest:ident)*)) => {
246        impl_enum!(@methods_map: $first, ($($before),*), ($($rest),*));
247        // Append the current type to the "before" list and continue.
248        impl_enum!(@methods_map_helper: ($($before,)* $first), ($($rest),*));
249    };
250}
251use impl_enum;
252
253#[cfg(test)]
254mod tests {
255    use super::Enum;
256
257    type Bytes = Enum<2, u8, i8>;
258    type Unums = Enum<4, u8, u16, u32, u64>;
259
260    #[test]
261    fn validate() {
262        assert![Bytes::validate()];
263        assert![Unums::validate()];
264        assert![Enum::<0, (), (), ()>::validate()];
265        assert![Enum::<1, i8, (), ()>::validate()];
266        assert![!Enum::<0, i8, (), ()>::validate()];
267        assert![!Enum::<2, i8, (), ()>::validate()];
268        //
269        assert![!Enum::<1, (), i8, ()>::validate()];
270        assert![!Enum::<2, i32, (), i8>::validate()];
271        assert![!Enum::<1, (), (), i8, ()>::validate()];
272    }
273    #[test]
274    fn map() {
275        let a: Enum<2, i32, f64> = Enum::A(10);
276        assert_eq![Enum::A(20), a.map_a(|v| v * 2)];
277        assert_eq![Enum::A(10), a.map_b(|v| v * 2.0)];
278        let b: Enum<2, i32, f64> = Enum::B(3.14);
279        assert_eq![Enum::B(3.14), b.map_a(|v| v * 2)];
280        assert_eq![Enum::B(6.28), b.map_b(|v| v * 2.0)];
281    }
282    #[test]
283    fn field_access() {
284        let mut u = Unums::C(32);
285        assert_eq![u.is_c(), true];
286        assert_eq![u.into_c(), Some(32)];
287        assert_eq![u.as_ref_c(), Some(&32)];
288        assert_eq![u.as_mut_c(), Some(&mut 32)];
289        //
290        assert_eq![u.is_a(), false];
291        assert_eq![u.into_a(), None];
292        assert_eq![u.as_ref_a(), None];
293        assert_eq![u.as_mut_a(), None];
294    }
295    #[test]
296    fn positioning() {
297        let u = Unums::C(32);
298        assert_eq![u.variant_index(), 2];
299        assert_eq![u.is_variant_index(2), true];
300        assert_eq![u.is_variant_index(3), false];
301        assert_eq![u.variant_name(), "C"];
302        assert_eq![u.is_variant_name("C"), true];
303        assert_eq![u.is_variant_name("B"), false];
304
305        let u = Unums::A(32);
306        assert_eq![u.variant_index(), 0];
307        assert_eq![u.is_variant_index(0), true];
308        assert_eq![u.is_variant_index(1), false];
309        assert_eq![u.variant_name(), "A"];
310        assert_eq![u.is_variant_name("A"), true];
311        assert_eq![u.is_variant_name("B"), false];
312    }
313    #[test]
314    fn tuple() {
315        let u = Unums::C(32);
316        assert_eq![
317            u.into_tuple_options(),
318            (None, None, Some(32), None, None, None, None, None, None, None, None, None)
319        ];
320        assert_eq![
321            u.as_tuple_ref_options(),
322            (None, None, Some(&32), None, None, None, None, None, None, None, None, None)
323        ];
324        assert_eq![u.into_tuple_defaults(), (0, 0, 32, 0, (), (), (), (), (), (), (), ())];
325    }
326}