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}