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