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