utils/
enum.rs

1//! Enum helpers.
2
3/// Macro to generate helpers for fieldless unit-only enums.
4///
5/// Helpers for accessing variants:
6/// - `COUNT`: The number of variants.
7/// - `ALL`: All variants (reference to a static array).
8/// - `iter()`: [`Iterator`] over all variants.
9///
10/// Helpers for converting to and from the discriminant (requires the enum to have an explicit
11/// `#[repr(...)]` attribute before all other attributes):
12/// - `checked_from_discriminant()` & `from_discriminant()`: Safe and panicking conversions from
13///   the discriminant.
14/// - [`From`] implementation from the enum to the discriminant.
15/// - [`TryFrom`] implementation from the discriminant to the enum.
16///
17/// Helpers for using variants as array indices (requires all variants to use implicit
18/// discriminants):
19/// - [`Index`](std::ops::Index) and [`IndexMut`](std::ops::IndexMut) implementations for
20///   `[T; COUNT]` arrays.
21///
22/// See also [`parser::parsable_enum!`](crate::parser::parsable_enum), which combined this macro
23/// with building a parser.
24///
25/// # Examples
26///
27/// Variant helpers:
28/// ```
29/// utils::enumerable_enum! {
30///     enum Direction {
31///         North,
32///         East,
33///         South,
34///         West,
35///     }
36/// }
37///
38/// assert_eq!(Direction::COUNT, 4);
39/// # // Use matches! as PartialEq is not derived or implemented in this example
40/// assert!(matches!(Direction::ALL, &[
41///     Direction::North,
42///     Direction::East,
43///     Direction::South,
44///     Direction::West,
45/// ]));
46/// assert!(matches!(Direction::iter().collect::<Vec<_>>().as_slice(), &[
47///     Direction::North,
48///     Direction::East,
49///     Direction::South,
50///     Direction::West,
51/// ]));
52/// ```
53///
54/// Discriminant helpers:
55/// ```
56/// utils::enumerable_enum! {
57///     #[repr(u8)]
58///     #[derive(Copy, Clone, Debug, Default, PartialEq)]
59///     enum Operation {
60///         Add,
61///         Sub,
62///         Mul,
63///         Div,
64///         #[default]
65///         Noop = 255,
66///     }
67/// }
68///
69/// assert_eq!(Operation::COUNT, 5);
70/// assert_eq!(Operation::ALL, &[
71///     Operation::Add,
72///     Operation::Sub,
73///     Operation::Mul,
74///     Operation::Div,
75///     Operation::Noop
76/// ]);
77/// assert_eq!(Operation::iter().collect::<Vec<_>>(), Operation::ALL);
78/// assert_eq!(Operation::from_discriminant(0), Operation::Add);
79/// assert_eq!(Operation::checked_from_discriminant(255), Some(Operation::Noop));
80/// assert_eq!(Operation::checked_from_discriminant(100), None);
81/// assert_eq!(u8::from(Operation::Add), 0u8);
82/// assert_eq!(Operation::try_from(2u8), Ok(Operation::Mul));
83/// assert_eq!(Operation::try_from(4u8), Err(()));
84/// ```
85///
86/// `from_discriminant` panics on invalid values:
87/// ```should_panic
88/// utils::enumerable_enum! {
89///     #[repr(u8)]
90///     enum Operation {
91///         Add,
92///         Sub,
93///         Mul,
94///         Div,
95///     }
96/// }
97///
98/// Operation::from_discriminant(64);
99/// ```
100///
101/// Index helpers:
102/// ```
103/// utils::enumerable_enum! {
104///     enum Register {
105///         A,
106///         B,
107///         C,
108///         D,
109///     }
110/// }
111///
112/// let mut registers = [0, 1, 2, 3];
113/// assert_eq!(registers[Register::A], 0);
114/// assert_eq!(registers[Register::B], 1);
115/// assert_eq!(registers[Register::C], 2);
116/// assert_eq!(registers[Register::D], 3);
117/// registers[Register::C] = 123;
118/// assert_eq!(registers[Register::C], 123);
119/// ```
120#[macro_export]
121macro_rules! enumerable_enum {
122    (
123        #[repr($t:ty)]
124        $(#[$meta:meta])*
125        $vis:vis enum $name:ident {
126            $($(#[$variant_meta:meta])* $variant:ident $(= $discriminant:expr)?),+ $(,)?
127        }
128    ) => {
129        // #[cfg(true) is used to force the second arm to be matched, even when no other attributes
130        // are provided.
131        $crate::enumerable_enum! {
132            $(#[$meta])*
133            #[cfg(true)]
134            #[repr($t)]
135            $vis enum $name {
136                $($(#[$variant_meta])* $variant $(= $discriminant)?,)+
137            }
138        }
139
140        impl $name {
141            /// Returns the variant with the provided discriminant, or [`None`] on invalid values.
142            #[inline]
143            #[must_use]
144            #[allow(non_snake_case, non_upper_case_globals)]
145            pub const fn checked_from_discriminant(v: $t) -> Option<Self> {
146                $(const $variant: $t = $name::$variant as $t;)+
147
148                match v {
149                    $($variant => Some(Self::$variant),)+
150                    _ => None,
151                }
152            }
153
154            /// Returns the variant with the provided discriminant, panicking on invalid values.
155            #[inline]
156            #[must_use]
157            pub const fn from_discriminant(v: $t) -> Self {
158                Self::checked_from_discriminant(v).expect("invalid discriminant")
159            }
160        }
161
162        impl From<$name> for $t {
163            #[inline]
164            fn from(v: $name) -> Self {
165                v as Self
166            }
167        }
168
169        impl TryFrom<$t> for $name {
170            type Error = ();
171
172            #[inline]
173            fn try_from(value: $t) -> Result<Self, ()> {
174                Self::checked_from_discriminant(value).ok_or(())
175            }
176        }
177    };
178    (
179        $(#[$meta:meta])*
180        $vis:vis enum $name:ident {
181            $($(#[$variant_meta:meta])* $variant:ident $(= $discriminant:expr)?),+ $(,)?
182        }
183    ) => {
184        $crate::enumerable_enum! {@enum
185            $(#[$meta])*
186            $vis enum $name {
187                $($(#[$variant_meta])* $variant $(= $discriminant)?,)+
188            }
189        }
190
191        impl $name {
192            /// The number of variants.
193            pub const COUNT: usize = [$(Self::$variant),+].len();
194            /// All variants.
195            pub const ALL: &'static [Self; Self::COUNT] = &[$(Self::$variant),+];
196
197            /// Iterator over all variants.
198            #[inline]
199            pub fn iter() -> ::std::array::IntoIter<Self, { $name::COUNT }> {
200                // Returns items by value without references even for non-copy types
201                [$(Self::$variant),+].into_iter()
202            }
203        }
204    };
205    (@enum
206        $(#[$meta:meta])*
207        $vis:vis enum $name:ident {
208            $($(#[$variant_meta:meta])* $variant:ident),+ $(,)?
209        }
210    ) => {
211        $(#[$meta])*
212        $vis enum $name {
213            $($(#[$variant_meta])* $variant,)+
214        }
215
216        impl<T> ::std::ops::Index<$name> for [T; $name::COUNT] {
217            type Output = T;
218
219            #[inline]
220            fn index(&self, index: $name) -> &Self::Output {
221                &self[index as usize]
222            }
223        }
224        impl<T> ::std::ops::IndexMut<$name> for [T; $name::COUNT] {
225            #[inline]
226            fn index_mut(&mut self, index: $name) -> &mut Self::Output {
227                &mut self[index as usize]
228            }
229        }
230    };
231    (@enum
232        $(#[$meta:meta])*
233        $vis:vis enum $name:ident {
234            $($(#[$variant_meta:meta])* $variant:ident $(= $discriminant:expr)?),+ $(,)?
235        }
236    ) => {
237        $(#[$meta])*
238        $vis enum $name {
239            $($(#[$variant_meta])* $variant $(= $discriminant)?,)+
240        }
241    };
242}