enum_utils/
lib.rs

1//! A set of procedural macros for deriving useful functionality on enums.
2
3#![doc(html_root_url = "https://docs.rs/enum-utils/0.1.2")]
4
5extern crate proc_macro;
6
7#[macro_use]
8mod attr;
9mod iter;
10mod from_str;
11mod conv;
12
13use proc_macro::TokenStream;
14use syn::{DeriveInput, parse_macro_input};
15
16fn unwrap_errors<T>(res: Result<T, attr::ErrorList>) -> T {
17    match res {
18        Ok(x) => x,
19        Err(list) => {
20            // TODO: print error spans with proc_macro_diagnostic
21            let desc: String = list.iter()
22                .map(|e| format!("\n{}", e))
23                .collect();
24
25            panic!("enum_utils encountered one or more errors:{}", desc);
26        }
27    }
28}
29
30/// Derives [`FromStr`] for C-like enums.
31///
32/// The generated code will be more efficient than a simple `match` statement for most enums. It is
33/// guaranteed to run in `O(m)` time (where `m` is the length of the input string) rather than the
34/// `O(mn)` time (where `n` is the number of variants) which would be required by the naive
35/// approach.
36///
37/// # Examples
38///
39/// [`FromStr`] can be derived for C-like enums by deriving `enum_utils::FromStr`.
40///
41/// ```
42/// #[derive(Debug, PartialEq, enum_utils::FromStr)]
43/// enum Test {
44///     Alpha,
45///     Beta,
46/// }
47///
48/// assert_eq!("Alpha".parse(), Ok(Test::Alpha));
49/// assert_eq!("Beta".parse(), Ok(Test::Beta));
50/// ```
51///
52/// # Attributes
53///
54/// The implementation can be customized by attributes of the form `#[enumeration(...)]`. These
55/// are based on the ones in [`serde`].
56///
57/// ## `#[enumeration(skip)]`
58///
59/// This attribute causes a single variant of the enum to be ignored when deserializing.
60/// Variants which are skipped may have data fields.
61///
62/// ```
63/// #[derive(Debug, PartialEq, enum_utils::FromStr)]
64/// enum Skip {
65///     #[enumeration(skip)]
66///     Alpha(usize),
67///     Beta,
68/// }
69///
70/// assert_eq!("Alpha".parse::<Skip>(), Err(()));
71/// assert_eq!("Beta".parse(), Ok(Skip::Beta));
72/// ```
73///
74/// ## `#[enumeration(rename = "...")]`
75///
76/// This attribute renames a single variant of an enum. This replaces the name of the variant and
77/// overrides [`rename_all`].
78///
79/// Only one [`rename`] attribute can appear for each enum variant.
80///
81/// ```
82/// #[derive(Debug, PartialEq, enum_utils::FromStr)]
83/// enum Rename {
84///     #[enumeration(rename = "α")]
85///     Alpha,
86///     Beta,
87/// }
88///
89/// assert_eq!("Alpha".parse::<Rename>(), Err(()));
90/// assert_eq!("α".parse(), Ok(Rename::Alpha));
91/// assert_eq!("Beta".parse(), Ok(Rename::Beta));
92/// ```
93///
94/// ## `#[enumeration(alias = "...")]`
95///
96/// This attribute is similar to [`rename`], but it does not replace the name of the variant.
97///
98/// Unlike [`rename`], there is no limit to the number of `alias` attributes which can be applied.
99/// This allows multiple strings to serialize to the same variant.
100///
101/// ```
102/// #[derive(Debug, PartialEq, enum_utils::FromStr)]
103/// enum Alias {
104///     #[enumeration(alias = "A", alias = "α")]
105///     Alpha,
106///     Beta,
107/// }
108///
109/// assert_eq!("Alpha".parse(), Ok(Alias::Alpha));
110/// assert_eq!("A".parse(), Ok(Alias::Alpha));
111/// assert_eq!("α".parse(), Ok(Alias::Alpha));
112/// assert_eq!("Beta".parse(), Ok(Alias::Beta));
113/// ```
114///
115/// ## `#[enumeration(rename_all = "...")]`
116///
117/// This attribute can be applied to an entire enum, and causes all fields to be renamed according
118/// to the given [rename rule]. All rename rules defined in [`serde`] are supported.
119///
120/// ```
121/// #[enumeration(rename_all = "snake_case")]
122/// #[derive(Debug, PartialEq, enum_utils::FromStr)]
123/// enum RenameAll {
124///     FooBar,
125///     BarFoo,
126/// }
127///
128/// assert_eq!("foo_bar".parse(), Ok(RenameAll::FooBar));
129/// assert_eq!("bar_foo".parse(), Ok(RenameAll::BarFoo));
130/// ```
131///
132/// ## `#[enumeration(case_insensitive)]`
133///
134/// This attribute can be applied to an entire enum, it causes all variants to be parsed
135/// case-insensitively.
136///
137/// ```
138/// #[enumeration(case_insensitive)]
139/// #[derive(Debug, PartialEq, enum_utils::FromStr)]
140/// enum NoCase {
141///     Alpha,
142///     Beta,
143/// }
144///
145/// assert_eq!("ALPHA".parse(), Ok(NoCase::Alpha));
146/// assert_eq!("beta".parse(), Ok(NoCase::Beta));
147/// ```
148///
149/// [`FromStr`]: https://doc.rust-lang.org/std/str/trait.FromStr.html
150/// [`serde`]: https://serde.rs/attributes.html
151/// [`rename`]: #enumerationrename--
152/// [`rename_all`]: #enumerationrename_all--
153/// [rename rule]: https://serde.rs/container-attrs.html#rename_all
154#[proc_macro_derive(FromStr, attributes(enumeration))]
155pub fn from_str_derive(input: TokenStream) -> TokenStream {
156    let ast = parse_macro_input!(input as DeriveInput);
157    unwrap_errors(from_str::derive(&ast)).into()
158}
159
160/// Derives a static method, `iter()`, which iterates over the variants of an enum.
161///
162/// # Examples
163///
164/// Variants are yielded in the order they are defined. If the discriminants of the enum form a
165/// single, increasing run and `#[repr(...)]` is specified as in the following example, a fast
166/// implementation of `iter` can be generated which does not require the enum to implement `Clone`.
167///
168/// ```
169/// /// The discriminants of this enum are `[1, 2, 3, 4]`.
170/// #[derive(Debug, PartialEq, Eq, enum_utils::IterVariants)]
171/// #[repr(u8)]
172/// pub enum Direction {
173///     North = 1,
174///     East,
175///     South,
176///     West,
177/// }
178///
179/// use Direction::*;
180/// assert_eq!(Direction::iter().collect::<Vec<_>>(), vec![North, East, South, West]);
181/// ```
182///
183/// If the preceding conditions are not met, `Clone` must be implemented to successfully derive
184/// `IterVariants`. `enum_utils` will create a `const` array containing each variant and iterate
185/// over that.
186///
187/// ```
188/// #[derive(Debug, Clone, Copy, PartialEq, Eq, enum_utils::IterVariants)]
189/// #[repr(u16)]
190/// pub enum Bitmask {
191///     Empty = 0x0000,
192///     Full = 0xffff,
193/// }
194///
195/// use Bitmask::*;
196/// assert_eq!(Bitmask::iter().collect::<Vec<_>>(), vec![Empty, Full]);
197/// ```
198///
199/// Named constants or complex expressions (beyond an integer literal) are not evaluated when used
200/// as a discriminant and will cause `IterVariants` to default to the `Clone`-based implementation.
201///
202/// ```compile_fail
203/// #[derive(Debug, PartialEq, Eq, enum_utils::IterVariants)] // Missing `Clone` impl
204/// #[repr(u8)]
205/// pub enum Bitmask {
206///     Bit1 = 1 << 0,
207///     Bit2 = 1 << 1,
208/// }
209/// ```
210///
211/// # Attributes
212///
213/// ## `#[enumeration(skip)]`
214///
215/// Use `#[enumeration(skip)]` to avoid iterating over a variant. This can be useful when an enum
216/// contains a "catch-all" variant.
217///
218/// ```
219/// #[derive(Debug, Clone, Copy, PartialEq, Eq, enum_utils::IterVariants)]
220/// pub enum Http2FrameType {
221///     Data,
222///     Headers,
223///
224///     /* ... */
225///
226///     Continuation,
227///
228///     #[enumeration(skip)]
229///     Unknown(u8),
230/// }
231///
232/// use Http2FrameType::*;
233/// assert_eq!(Http2FrameType::iter().collect::<Vec<_>>(),
234///            vec![Data, Headers, /* ... */ Continuation]);
235/// ```
236#[proc_macro_derive(IterVariants, attributes(enumeration))]
237pub fn iter_variants_derive(input: TokenStream) -> TokenStream {
238    let ast = parse_macro_input!(input as DeriveInput);
239    unwrap_errors(iter::derive(&ast)).into()
240}
241
242/// Derives [`TryFrom<Repr>`] for a C-like enum, where `Repr` is a [primitive representation]
243/// specified in `#[repr(...)]`.
244///
245/// [`TryFrom<Repr>`]: https://doc.rust-lang.org/std/convert/trait.TryFrom.html
246/// [primitive representation]: https://doc.rust-lang.org/reference/type-layout.html#primitive-representations
247///
248/// # Examples
249///
250/// ```
251/// use std::convert::{TryFrom, TryInto};
252///
253/// #[derive(Debug, Clone, Copy, PartialEq, Eq, enum_utils::TryFromRepr)]
254/// #[repr(u8)]
255/// pub enum Direction {
256///     North = 1,
257///     East,
258///     South,
259///     West
260/// }
261///
262/// use Direction::*;
263/// assert_eq!(North, 1u8.try_into().unwrap());
264/// assert_eq!(West,  4u8.try_into().unwrap());
265/// assert_eq!(Err(()), Direction::try_from(0u8));
266/// assert_eq!(Err(()), Direction::try_from(5u8));
267/// ```
268///
269/// This macro only works on C-like enums.
270///
271/// ```compile_fail
272/// #[derive(Debug, Clone,  enum_utils::TryFromRepr)]
273/// #[repr(u8)]
274/// pub enum Letter {
275///     A,
276///     B,
277///     Other(u8),
278/// }
279/// ```
280#[proc_macro_derive(TryFromRepr, attributes(enumeration))]
281pub fn try_from_repr_derive(input: TokenStream) -> TokenStream {
282    let ast = parse_macro_input!(input as DeriveInput);
283    unwrap_errors(conv::derive_try_from_repr(&ast)).into()
284}
285
286/// Derives [`From<CLikeEnum>`] for the [primitive representation] specified in `#[repr(...)]`.
287///
288/// [`From<Enum>`]: https://doc.rust-lang.org/std/convert/trait.From.html
289/// [primitive representation]: https://doc.rust-lang.org/reference/type-layout.html#primitive-representations
290///
291/// # Examples
292///
293/// ```
294/// #[derive(Debug, Clone, Copy, PartialEq, Eq, enum_utils::ReprFrom)]
295/// #[repr(u8)]
296/// pub enum Direction {
297///     North = 1,
298///     East,
299///     South,
300///     West
301/// }
302///
303/// use Direction::*;
304/// assert_eq!(1u8, North.into());
305/// assert_eq!(4u8, West.into());
306/// ```
307///
308/// This macro only works on C-like enums.
309///
310/// ```compile_fail
311/// #[derive(Debug, Clone,  enum_utils::TryFromRepr)]
312/// #[repr(u8)]
313/// pub enum Letter {
314///     A,
315///     B,
316///     Other(u8),
317/// }
318/// ```
319#[proc_macro_derive(ReprFrom, attributes(enumeration))]
320pub fn repr_from_derive(input: TokenStream) -> TokenStream {
321    let ast = parse_macro_input!(input as DeriveInput);
322    unwrap_errors(conv::derive_repr_from(&ast)).into()
323}