Skip to main content

devela/data/value/of/
one.rs

1// devela::data::value::one
2//
3//! Defines the [`Oneof`] type.
4//
5
6_data_value_impl_oneof!();
7
8/// Defines [`Oneof`] and implements all the methods.
9#[macro_export]
10#[cfg_attr(cargo_primary_package, doc(hidden))]
11#[allow(clippy::crate_in_macro_def, reason = "impl_const_init arm")]
12macro_rules! _data_value_impl_oneof {
13    /* points of entry, using hardcoded argument list */
14
15    // main point of entry
16    // (defines Oneof & implements everything)
17    () => { $crate::_data_value_impl_oneof!(%canonical); };
18    //
19    // var_name : var_idx(0-based) + var_nth(1-based) : nth_suffix
20    ($($T:ident : $idx:literal + $nth:literal : $suf:literal),* $(,)?) => {
21        $crate::_data_value_impl_oneof!(define_enum: $($T:$nth:$suf),+);
22        $crate::_data_value_impl_oneof!(impl_default: $($T),+);
23        $crate::_data_value_impl_oneof!(impl_const_init: $($T),+);
24        $crate::_data_value_impl_oneof!(methods_general: $($T:$idx+$nth:$suf),+);
25        $crate::_data_value_impl_oneof!(methods_individ: $($T:$idx+$nth:$suf),+);
26    };
27
28    // point of entry for implementing ConstInit
29    (impl_const_init) => {
30        $crate::_data_value_impl_oneof!(%canonical %map_ident impl_const_init:);
31    };
32    // real ConstInit implementation
33    (impl_const_init: $_0:ident $(, $rest:ident)*) => {
34        impl<const LEN: usize, $_0: crate::ConstInit, $($rest),*> crate::ConstInit
35            for Oneof<LEN, $_0, $($rest),*> {
36            const INIT: Self = Oneof::$_0($_0::INIT);
37        }
38    };
39
40    /* helpers */
41
42    // hardcoded canonical list (12 variants)
43    (%canonical $($prefix:tt)*) => { $crate::_data_value_impl_oneof! { $($prefix)*
44        _0:0+1:"st", _1:1+2:"nd", _2:2+3:"rd", _3:3+4:"th", _4:4+5:"th", _5:5+6:"th",
45        _6:6+7:"th", _7:7+8:"th", _8:8+9:"th", _9:9+10:"th", _10:10+11:"th", _11:11+12:"th",
46    }};
47    // maps the canonical list to one of idents and forwards them to the prefixed arm.
48    (%map_ident $prefix:ident:
49     $($T:ident : $idx:literal + $nth:literal : $suf:literal),* $(,)?) => {
50        $crate::_data_value_impl_oneof!($prefix: $($T),*);
51    };
52    // maps the canonical list to one of idents:nth:suf and forwards them to the prefixed arm.
53    (%map_ident_nth_suf $prefix:ident:
54     $($T:ident : $idx:literal + $nth:literal : $suf:literal),* $(,)?) => {
55        $crate::_data_value_impl_oneof!($prefix: $($T:$nth:$suf),+);
56    };
57
58    /* main arms */
59
60    (define_enum: $($variant:ident : $nth:literal : $suf:literal),+) => { $crate::paste! {
61        #[doc = crate::_tags!(code)]
62        /// A generic, parameterized *enum* for expressing structured alternatives.
63        #[doc = crate::_doc_meta!{location("data/value")}]
64        ///
65        /// Variants are expected to be **contiguous**, meaning `()` (unit types)
66        /// should only appear at the **end**.
67        ///
68        /// The **first variant** (`A`) is considered the default,
69        /// implementing [`Default`] when `A: Default`.
70        #[non_exhaustive]
71        #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
72        pub enum Oneof<const LEN: usize, $($variant = ()),+> {
73            $(
74                #[doc = "The " $nth $suf " variant."]
75                $variant($variant)
76            ),+
77        }
78    }};
79    (impl_default: $_0:ident $(, $rest:ident)*) => {
80        impl<const LEN: usize, $_0: Default, $($rest),*> Default for Oneof<LEN, $_0, $($rest),*> {
81            fn default() -> Self { Oneof::$_0($_0::default()) }
82        }
83    };
84    (
85    // Implements:
86    // - LEN
87    // - MAX_ARITY
88    // - variant_index
89    // - is_variant_index
90    //
91    // - validate
92    //
93    // - into_tuple_options
94    // - into_tuple_defaults
95    // - as_tuple_ref_options
96    methods_general: $($T:ident : $idx:literal + $nth:literal : $suf:literal),+) => {
97        /// # Structural methods.
98        impl<const LEN:usize,  $($T),+ > Oneof<LEN, $($T),+> {
99            /// The number of active (non-`()` type) variants.
100            pub const LEN: usize = {
101                assert![LEN <= Self::MAX_ARITY, "LEN must be <= MAX_ARITY"];
102                LEN
103            };
104            /// The maximum number of generic type parameters in this enum.
105            pub const MAX_ARITY: usize = $crate::ident_total!($($T),+);
106            // pub const MAX_ARITY: usize = $crate::capture_last![literal $($nth),+]; // BENCH
107
108            /// Returns the current variant index (0-based).
109            pub const fn variant_index(&self) -> usize {
110                match self { $( Oneof::$T(_) => $idx ),+ }
111            }
112            /// Checks whether the current variant is at `index` (0-based).
113            pub const fn is_variant_index(&self, index: usize) -> bool {
114                self.variant_index() == index
115            }
116        }
117        impl<const LEN: usize, $($T: 'static),+ > Oneof<LEN, $($T),+> {
118            /// Validates that inactive `()` variants only appear at the end,
119            /// and that `LEN` equals the number of active variants.
120            #[allow(unused_assignments, reason = "wont be read in all cases")]
121            // WAIT const PartialEq for TypeId
122            pub fn validate() -> bool {
123                let mut non_unit_count = 0;
124                let mut unit_found = false;
125                $(
126                    if $crate::TypeId::of::<$T>() == $crate::TypeId::of::<()>() {
127                        unit_found = true;
128                    } else {
129                        if unit_found { return false; }
130                        non_unit_count += 1;
131                    }
132                )+
133                LEN == non_unit_count
134            }
135        }
136        /// # Conversion methods.
137        // IMPROVE: add a const fn for T: Copy (into_tuple_copy_options / into_tuple_const_init)
138        impl<const LEN: usize, $($T: Clone),+ > Oneof<LEN, $($T),+> {
139            /// Returns a tuple with `Some(value)` for the active variant and `None` elsewhere.
140            pub fn into_tuple_options(self) -> ($(Option<$T>),+) { $crate::paste! {
141                let index = self.variant_index();
142                ( $(
143                    if $idx == index {
144                        self.clone().[<into $T>]()
145                    } else {
146                        None::<$T>
147                    }
148                ),+ )
149            }}
150
151            /// Returns a tuple with the active variant's inner value in its corresponding position
152            /// and `Default::default()` for all others.
153            pub fn into_tuple_defaults(self) -> ($($T),+) where $($T: Default),+ { $crate::paste! {
154                let index = self.variant_index();
155                ( $(
156                    if $idx == index {
157                        self.clone().[<into $T>]().unwrap()
158                    } else {
159                        Default::default()
160                    }
161                ),+ )
162            }}
163        }
164        impl<const LEN: usize, $($T),+ > Oneof<LEN, $($T),+> {
165            /// Returns a tuple with `Some(&value)` for the active variant and `None` elsewhere.
166            // FUTURE: make the `()` types not wrapped in option.
167            pub const fn as_tuple_ref_options(&self) -> ($(Option<&$T>),+) { $crate::paste! {
168                ( $(
169                    if $idx == self.variant_index() {
170                        self.[<as_ref $T>]()
171                    } else {
172                        None::<&$T>
173                    }
174                ),+ )
175            }}
176        }
177    };
178    (
179    // Implements methods acting over individual fields.
180    methods_individ: $($T:ident : $idx:literal + $nth:literal : $suf:literal),+) => {
181        /// # Variant-specific methods.
182        impl<const LEN: usize, $($T),+> Oneof<LEN, $($T),+> {
183            // $crate::_data_value_impl_oneof!(methods_field_access: $($T),+);
184            $crate::_data_value_impl_oneof!(methods_field_access: $($T:$idx+$nth:$suf),+);
185            $crate::_data_value_impl_oneof!(methods_map: $($T),+);
186            // $crate::_data_value_impl_oneof!(methods_map: $($T:$idx+$nth),+);
187        }
188    };
189    (
190    // implements:
191    // - is_*
192    // - into_*
193    // - as_ref_*
194    // - as_mut_*
195    methods_field_access: $($T:ident : $idx:literal + $nth:literal : $suf:literal),+) => {
196        $( $crate::_data_value_impl_oneof! { @methods_field_access: $T : $idx + $nth : $suf} )+
197    };
198    (@methods_field_access: $T:ident : $idx:literal + $nth:literal : $suf:literal
199    ) => { $crate::paste! {
200        #[doc = "Returns `true` if there's a value in variant [`"
201            $T "`](#variant." $T ") (The " $nth $suf ")."]
202        pub const fn [<is $T>](&self) -> bool { matches!(self, Oneof::$T(_)) }
203
204        #[doc = "Returns a shared reference to the inner value in variant `" $T "`, if present."]
205        pub const fn [<as_ref $T>](&self) -> Option<&$T> {
206            $crate::is![let Self::$T($T) = self, Some($T), None]
207        }
208        #[doc = "Returns an exclusive reference to the inner value in variant`" $T "`, if present."]
209        pub const fn [<as_mut $T>](&mut self) -> Option<&mut $T> {
210            $crate::is![let Self::$T($T) = self, Some($T), None]
211        }
212        #[doc = "Returns a copy of the value in variant `" $T "`, if present."]
213        pub const fn [<copy $T >](self) -> Option<$T> where Self: Copy {
214            $crate::is![let Self::$T($T) = self, Some($T), None]
215        }
216        #[doc = "Returns the owned value in variant `" $T "`, if present."]
217        #[doc = "<hr/>"] // separator after the last method
218        pub fn [<into $T>](self) -> Option<$T> {
219            $crate::is![let Self::$T($T) = self, Some($T), None]
220        }
221    }};
222    (
223    // implements:
224    // - map_*
225    methods_map: $first:ident $(, $rest:ident)*) => {
226        // For the first variant, the `$before` list is empty.
227        $crate::_data_value_impl_oneof!(@methods_map: $first, (), ($($rest),*));
228        // Then, delegate to the helper macro with the first element as the accumulator.
229        $crate::_data_value_impl_oneof!(@methods_map_helper: ($first), ($($rest),*));
230
231        // NOTE: generates something like the following (e.g. for 6 variants):
232        //
233        // impl_map_method!(_0, (/*$before*/), (_1, _2, _3, _4, _5));
234        // impl_map_method!(_1, (_0), (_2, _3, _4, _5));
235        // impl_map_method!(_2, (_0, _1), (_3, _4, _5));
236        // impl_map_method!(_3, (_0, _1, _2), (_4, _5));
237        // impl_map_method!(_4, (_0, _1, _2, _3), (_5));
238        // impl_map_method!(_5, (_0, _1, _2, _3, _4), (/*$after*/));
239    };
240    (
241    @methods_map: $T:ident, ( $($before:ident),* ), ( $($after:ident),* )) => { $crate::paste! {
242        #[doc = "Transforms the inner value in variant`" $T
243        "` using `f`, leaving other variants unchanged."]
244        pub fn [<map $T>]<NEW>(self, f: impl FnOnce($T) -> NEW)
245            -> Oneof<LEN, $($before,)* NEW, $($after,)* > {
246            match self {
247                $( Self::$before(val) => Oneof::$before(val), )*
248                Self::$T(val) => Oneof::$T(f(val)),
249                $( Self::$after(val) => Oneof::$after(val), )*
250            }
251        }
252        // NOTE: Generates methods like the following (e.g. for variant _2, of 6 total):
253        //
254        // pub fn map_c<NEW>(self, f: impl FnOnce(_2) -> NEW) -> Oneof<_0, _1, NEW, _3, _4, _5> {
255        //     match self {
256        //         Self::_0(t) => Oneof::_0(t),    // $before
257        //         Self::_1(t) => Oneof::_1(t),    // …
258        //         Self::_2(t) => Oneof::_2(f(t)), // $T
259        //         Self::_3(t) => Oneof::_3(t),    // $after
260        //         Self::_4(t) => Oneof::_4(t),    // …
261        //         Self::_5(t) => Oneof::_5(t),    // …
262        //     }
263        // }
264    }};
265    // Stop when there are no types left in the `$after` list.
266    (@methods_map_helper: ($($before:ident),*), ()) => {};
267    // Recursively take the next type as the current one.
268    (@methods_map_helper: ($($before:ident),*), ($first:ident $(, $rest:ident)*)) => {
269        $crate::_data_value_impl_oneof!(@methods_map: $first, ($($before),*), ($($rest),*));
270        // Append the current type to the "before" list and continue.
271        $crate::_data_value_impl_oneof!(@methods_map_helper: ($($before,)* $first), ($($rest),*));
272    };
273}
274#[doc(hidden)]
275pub use _data_value_impl_oneof;
276
277#[cfg(test)]
278mod tests {
279    use super::Oneof;
280
281    type Bytes = Oneof<2, u8, i8>;
282    type Unums = Oneof<4, u8, u16, u32, u64>;
283
284    #[test]
285    fn validate() {
286        assert![Bytes::validate()];
287        assert![Unums::validate()];
288        assert![Oneof::<0, (), (), ()>::validate()];
289        assert![Oneof::<1, i8, (), ()>::validate()];
290        assert![!Oneof::<0, i8, (), ()>::validate()];
291        assert![!Oneof::<2, i8, (), ()>::validate()];
292        //
293        assert![!Oneof::<1, (), i8, ()>::validate()];
294        assert![!Oneof::<2, i32, (), i8>::validate()];
295        assert![!Oneof::<1, (), (), i8, ()>::validate()];
296    }
297    #[test]
298    fn map() {
299        let a: Oneof<2, i32, f64> = Oneof::_0(10);
300        assert_eq![Oneof::_0(20), a.map_0(|v| v * 2)];
301        assert_eq![Oneof::_0(10), a.map_1(|v| v * 2.0)];
302        let b: Oneof<2, i32, f64> = Oneof::_1(3.14);
303        assert_eq![Oneof::_1(3.14), b.map_0(|v| v * 2)];
304        assert_eq![Oneof::_1(6.28), b.map_1(|v| v * 2.0)];
305    }
306    #[test]
307    fn field_access() {
308        let mut u = Unums::_2(32);
309        assert_eq![u.is_2(), true];
310        assert_eq![u.into_2(), Some(32)];
311        assert_eq![u.as_ref_2(), Some(&32)];
312        assert_eq![u.as_mut_2(), Some(&mut 32)];
313        //
314        assert_eq![u.is_0(), false];
315        assert_eq![u.into_0(), None];
316        assert_eq![u.as_ref_0(), None];
317        assert_eq![u.as_mut_0(), None];
318    }
319    #[test]
320    fn positioning() {
321        let u = Unums::_2(32);
322        assert_eq![u.variant_index(), 2];
323        assert_eq![u.is_variant_index(2), true];
324        assert_eq![u.is_variant_index(3), false];
325        // assert_eq![u.variant_name(), "_2"];
326        // assert_eq![u.is_variant_name("_2"), true];
327        // assert_eq![u.is_variant_name("_1"), false];
328
329        let u = Unums::_0(32);
330        assert_eq![u.variant_index(), 0];
331        assert_eq![u.is_variant_index(0), true];
332        assert_eq![u.is_variant_index(1), false];
333        // assert_eq![u.variant_name(), "_0"];
334        // assert_eq![u.is_variant_name("_0"), true];
335        // assert_eq![u.is_variant_name("_1"), false];
336    }
337    #[test]
338    fn tuple() {
339        let u = Unums::_2(32);
340        assert_eq![
341            u.into_tuple_options(),
342            (None, None, Some(32), None, None, None, None, None, None, None, None, None)
343        ];
344        assert_eq![
345            u.as_tuple_ref_options(),
346            (None, None, Some(&32), None, None, None, None, None, None, None, None, None)
347        ];
348        assert_eq![u.into_tuple_defaults(), (0, 0, 32, 0, (), (), (), (), (), (), (), ())];
349    }
350}