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 
115 extern crate proc_macro;
116 
117 use proc_macro::TokenStream;
118 use quote::quote;
119 use syn::{parse_macro_input, Data, DeriveInput, Error, Fields, Ident};
120 
121 #[proc_macro_derive(N)]
derive(input: TokenStream) -> TokenStream122 pub 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 }
208