Skip to main content

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