enumn/
lib.rs

1//! [![github]](https://github.com/dtolnay/enumn) [![crates-io]](https://crates.io/crates/enumn) [![docs-rs]](https://docs.rs/enumn)
2//!
3//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logo=docs.rs
6//!
7//! <br>
8//!
9//! Convert number to enum.
10//!
11//! This crate provides a derive macro to generate a function for converting a
12//! primitive integer into the corresponding variant of an enum.
13//!
14//! The generated function is named `n` and has the following signature:
15//!
16//! ```
17//! # const IGNORE: &str = stringify! {
18//! impl YourEnum {
19//!     pub fn n(value: Repr) -> Option<Self>;
20//! }
21//! # };
22//! ```
23//!
24//! where `Repr` is an integer type of the right size as described in more
25//! detail below.
26//!
27//! # Example
28//!
29//! ```
30//! use enumn::N;
31//!
32//! #[derive(PartialEq, Debug, N)]
33//! enum Status {
34//!     LegendaryTriumph,
35//!     QualifiedSuccess,
36//!     FortuitousRevival,
37//!     IndeterminateStalemate,
38//!     RecoverableSetback,
39//!     DireMisadventure,
40//!     AbjectFailure,
41//! }
42//!
43//! fn main() {
44//!     let s = Status::n(1);
45//!     assert_eq!(s, Some(Status::QualifiedSuccess));
46//!
47//!     let s = Status::n(9);
48//!     assert_eq!(s, None);
49//! }
50//! ```
51//!
52//! # Signature
53//!
54//! The generated signature depends on whether the enum has a `#[repr(..)]`
55//! attribute. If a `repr` is specified, the input to `n` will be required to be
56//! of that type.
57//!
58//! ```
59//! #[derive(enumn::N)]
60//! # enum E0 {
61//! #     IGNORE
62//! # }
63//! #
64//! #[repr(u8)]
65//! enum E {
66//!     /* ... */
67//!     # IGNORE
68//! }
69//!
70//! // expands to:
71//! impl E {
72//!     pub fn n(value: u8) -> Option<Self> {
73//!         /* ... */
74//!         # unimplemented!()
75//!     }
76//! }
77//! ```
78//!
79//! On the other hand if no `repr` is specified then we get a signature that is
80//! generic over a variety of possible types.
81//!
82//! ```
83//! # enum E {}
84//! #
85//! impl E {
86//!     pub fn n<REPR: Into<i64>>(value: REPR) -> Option<Self> {
87//!         /* ... */
88//!         # unimplemented!()
89//!     }
90//! }
91//! ```
92//!
93//! # Discriminants
94//!
95//! The conversion respects explicitly specified enum discriminants. Consider
96//! this enum:
97//!
98//! ```
99//! #[derive(enumn::N)]
100//! enum Letter {
101//!     A = 65,
102//!     B = 66,
103//! }
104//! ```
105//!
106//! Here `Letter::n(65)` would return `Some(Letter::A)`.
107
108#![doc(html_root_url = "https://docs.rs/enumn/0.1.14")]
109#![allow(
110    clippy::missing_panics_doc,
111    clippy::needless_doctest_main,
112    clippy::single_match_else
113)]
114
115extern crate proc_macro;
116
117use proc_macro::TokenStream;
118use quote::quote;
119use syn::{parse_macro_input, Data, DeriveInput, Error, Fields, Ident};
120
121#[proc_macro_derive(N)]
122pub fn derive(input: TokenStream) -> TokenStream {
123    let input = parse_macro_input!(input as DeriveInput);
124
125    let variants = match input.data {
126        Data::Enum(data) => data.variants,
127        Data::Struct(_) | Data::Union(_) => panic!("input must be an enum"),
128    };
129
130    for variant in &variants {
131        match variant.fields {
132            Fields::Unit => {}
133            Fields::Named(_) | Fields::Unnamed(_) => {
134                let span = variant.ident.span();
135                let err = Error::new(span, "enumn: variant with data is not supported");
136                return err.to_compile_error().into();
137            }
138        }
139    }
140
141    // Parse repr attribute like #[repr(u16)].
142    let mut repr = None;
143    for attr in input.attrs {
144        if attr.path().is_ident("repr") {
145            if let Ok(name) = attr.parse_args::<Ident>() {
146                match name.to_string().as_str() {
147                    "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8" | "i16" | "i32"
148                    | "i64" | "i128" | "isize" => {
149                        repr = Some(quote!(#name));
150                    }
151                    _ => {}
152                }
153            }
154        }
155    }
156
157    let signature;
158    let value;
159    match repr {
160        Some(ref repr) => {
161            signature = quote! {
162                fn n(value: #repr)
163            };
164            value = quote!(value);
165        }
166        None => {
167            repr = Some(quote!(i64));
168            signature = quote! {
169                fn n<REPR: Into<i64>>(value: REPR)
170            };
171            value = quote! {
172                <REPR as Into<i64>>::into(value)
173            };
174        }
175    }
176
177    let ident = input.ident;
178    let declare_discriminants = variants.iter().map(|variant| {
179        let variant = &variant.ident;
180        quote! {
181            const #variant: #repr = #ident::#variant as #repr;
182        }
183    });
184    let match_discriminants = variants.iter().map(|variant| {
185        let variant = &variant.ident;
186        quote! {
187            discriminant::#variant => Some(#ident::#variant),
188        }
189    });
190
191    TokenStream::from(quote! {
192        impl #ident {
193            pub #signature -> Option<Self> {
194                #[allow(non_camel_case_types)]
195                struct discriminant;
196                #[allow(non_upper_case_globals)]
197                impl discriminant {
198                    #(#declare_discriminants)*
199                }
200                match #value {
201                    #(#match_discriminants)*
202                    _ => None,
203                }
204            }
205        }
206    })
207}