• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017 Doug Goldstein <cardoe@cardoe.com>
2 
3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // “Software”), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
10 
11 // The above copyright notice and this permission notice shall be
12 // included in all copies or substantial portions of the Software.
13 
14 // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 
22 //! This crate provides a custom derive `Primitive` that helps people
23 //! providing native Rust bindings to C code by allowing a C-like `enum`
24 //! declaration to convert to its primitve values and back from them. You
25 //! can selectively include `num_traits::ToPrimitive` and
26 //! `num_traits::FromPrimitive` to get these features.
27 //!
28 //! # Example
29 //!
30 //! ```rust
31 //! use enum_primitive_derive::Primitive;
32 //! use num_traits::{FromPrimitive, ToPrimitive};
33 //!
34 //! #[derive(Debug, Eq, PartialEq, Primitive)]
35 //! enum Foo {
36 //!     Bar = 32,
37 //!     Dead = 42,
38 //!     Beef = 50,
39 //! }
40 //!
41 //! fn main() {
42 //!     assert_eq!(Foo::from_i32(32), Some(Foo::Bar));
43 //!     assert_eq!(Foo::from_i32(42), Some(Foo::Dead));
44 //!     assert_eq!(Foo::from_i64(50), Some(Foo::Beef));
45 //!     assert_eq!(Foo::from_isize(17), None);
46 //!
47 //!     let bar = Foo::Bar;
48 //!     assert_eq!(bar.to_i32(), Some(32));
49 //!
50 //!     let dead = Foo::Dead;
51 //!     assert_eq!(dead.to_isize(), Some(42));
52 //! }
53 //! ```
54 //!
55 //! # Complex Example
56 //!
57 //! ```rust
58 //! use enum_primitive_derive::Primitive;
59 //! use num_traits::{FromPrimitive, ToPrimitive};
60 //!
61 //! pub const ABC: ::std::os::raw::c_uint = 1;
62 //! pub const DEF: ::std::os::raw::c_uint = 2;
63 //! pub const GHI: ::std::os::raw::c_uint = 4;
64 //!
65 //! #[derive(Clone, Copy, Debug, Eq, PartialEq, Primitive)]
66 //! enum BindGenLike {
67 //!     ABC = ABC as isize,
68 //!     DEF = DEF as isize,
69 //!     GHI = GHI as isize,
70 //! }
71 //!
72 //! fn main() {
73 //!     assert_eq!(BindGenLike::from_isize(4), Some(BindGenLike::GHI));
74 //!     assert_eq!(BindGenLike::from_u32(2), Some(BindGenLike::DEF));
75 //!     assert_eq!(BindGenLike::from_u32(8), None);
76 //!
77 //!     let abc = BindGenLike::ABC;
78 //!     assert_eq!(abc.to_u32(), Some(1));
79 //! }
80 //! ```
81 //!
82 //! # TryFrom Example
83 //!
84 //! ```rust
85 //! use enum_primitive_derive::Primitive;
86 //! use core::convert::TryFrom;
87 //!
88 //! #[derive(Debug, Eq, PartialEq, Primitive)]
89 //! enum Foo {
90 //!     Bar = 32,
91 //!     Dead = 42,
92 //!     Beef = 50,
93 //! }
94 //!
95 //! fn main() {
96 //!     let bar = Foo::try_from(32);
97 //!     assert_eq!(bar, Ok(Foo::Bar));
98 //!
99 //!     let dead = Foo::try_from(42);
100 //!     assert_eq!(dead, Ok(Foo::Dead));
101 //!
102 //!     let unknown = Foo::try_from(12);
103 //!     assert!(unknown.is_err());
104 //! }
105 //! ```
106 
107 extern crate proc_macro;
108 
109 use proc_macro::TokenStream;
110 
111 /// Provides implementation of `num_traits::ToPrimitive` and
112 /// `num_traits::FromPrimitive`
113 #[proc_macro_derive(Primitive)]
primitive(input: TokenStream) -> TokenStream114 pub fn primitive(input: TokenStream) -> TokenStream {
115     let ast = syn::parse_macro_input!(input as syn::DeriveInput);
116     impl_primitive(&ast)
117 }
118 
impl_primitive(ast: &syn::DeriveInput) -> TokenStream119 fn impl_primitive(ast: &syn::DeriveInput) -> TokenStream {
120     let name = &ast.ident;
121 
122     // Check if derive(Primitive) was specified for a struct
123     if let syn::Data::Enum(ref variant) = ast.data {
124         let (var_u64, dis_u64): (Vec<_>, Vec<_>) = variant
125             .variants
126             .iter()
127             .map(|v| {
128                 match v.fields {
129                     syn::Fields::Unit => (),
130                     _ => panic!("#[derive(Primitive) can only operate on C-like enums"),
131                 }
132                 if v.discriminant.is_none() {
133                     panic!(
134                         "#[derive(Primitive) requires C-like enums with \
135                        discriminants for all enum variants"
136                     );
137                 }
138 
139                 let discrim = match v.discriminant.clone().map(|(_eq, expr)| expr).unwrap() {
140                     syn::Expr::Cast(real) => *real.expr,
141                     orig => orig,
142                 };
143                 (v.ident.clone(), discrim)
144             })
145             .unzip();
146 
147         // quote!{} needs this to be a vec since its in #( )*
148         let enum_u64 = vec![name.clone(); variant.variants.len()];
149 
150         // can't reuse variables in quote!{} body
151         let var_i64 = var_u64.clone();
152         let dis_i64 = dis_u64.clone();
153         let enum_i64 = enum_u64.clone();
154 
155         let to_name = name.clone();
156         let to_enum_u64 = enum_u64.clone();
157         let to_var_u64 = var_u64.clone();
158         let to_dis_u64 = dis_u64.clone();
159 
160         let to_enum_i64 = enum_u64.clone();
161         let to_var_i64 = var_u64.clone();
162         let to_dis_i64 = dis_u64.clone();
163 
164         TokenStream::from(quote::quote! {
165             impl ::num_traits::FromPrimitive for #name {
166                 fn from_u64(val: u64) -> Option<Self> {
167                     match val as _ {
168                         #( #dis_u64 => Some(#enum_u64::#var_u64), )*
169                         _ => None,
170                     }
171                 }
172 
173                 fn from_i64(val: i64) -> Option<Self> {
174                     match val as _ {
175                         #( #dis_i64 => Some(#enum_i64::#var_i64), )*
176                         _ => None,
177                     }
178                 }
179             }
180 
181             impl ::num_traits::ToPrimitive for #to_name {
182                 fn to_u64(&self) -> Option<u64> {
183                     match *self {
184                         #( #to_enum_u64::#to_var_u64 => Some(#to_dis_u64 as u64), )*
185                     }
186                 }
187 
188                 fn to_i64(&self) -> Option<i64> {
189                     match *self {
190                         #( #to_enum_i64::#to_var_i64 => Some(#to_dis_i64 as i64), )*
191                     }
192                 }
193             }
194 
195             impl ::core::convert::TryFrom<u64> for #to_name {
196                 type Error = &'static str;
197 
198                 fn try_from(value: u64) -> ::core::result::Result<Self, <Self as ::core::convert::TryFrom<u64>>::Error> {
199                     use ::num_traits::FromPrimitive;
200 
201                     #to_name::from_u64(value).ok_or_else(|| "Unknown variant")
202                 }
203             }
204 
205             impl ::core::convert::TryFrom<u32> for #to_name {
206                 type Error = &'static str;
207 
208                 fn try_from(value: u32) -> ::core::result::Result<Self, <Self as ::core::convert::TryFrom<u32>>::Error> {
209                     use ::num_traits::FromPrimitive;
210 
211                     #to_name::from_u32(value).ok_or_else(|| "Unknown variant")
212                 }
213             }
214 
215             impl ::core::convert::TryFrom<u16> for #to_name {
216                 type Error = &'static str;
217 
218                 fn try_from(value: u16) -> ::core::result::Result<Self, <Self as ::core::convert::TryFrom<u16>>::Error> {
219                     use ::num_traits::FromPrimitive;
220 
221                     #to_name::from_u16(value).ok_or_else(|| "Unknown variant")
222                 }
223             }
224 
225             impl ::core::convert::TryFrom<u8> for #to_name {
226                 type Error = &'static str;
227 
228                 fn try_from(value: u8) -> ::core::result::Result<Self, <Self as ::core::convert::TryFrom<u8>>::Error> {
229                     use ::num_traits::FromPrimitive;
230 
231                     #to_name::from_u8(value).ok_or_else(|| "Unknown variant")
232                 }
233             }
234 
235             impl ::core::convert::TryFrom<i64> for #name {
236                 type Error = &'static str;
237 
238                 fn try_from(value: i64) -> ::core::result::Result<Self, <Self as ::core::convert::TryFrom<i64>>::Error> {
239                     use ::num_traits::FromPrimitive;
240 
241                     #to_name::from_i64(value).ok_or_else(|| "Unknown variant")
242                 }
243             }
244 
245             impl ::core::convert::TryFrom<i32> for #name {
246                 type Error = &'static str;
247 
248                 fn try_from(value: i32) -> ::core::result::Result<Self, <Self as ::core::convert::TryFrom<i32>>::Error> {
249                     use ::num_traits::FromPrimitive;
250 
251                     #to_name::from_i32(value).ok_or_else(|| "Unknown variant")
252                 }
253             }
254 
255             impl ::core::convert::TryFrom<i16> for #name {
256                 type Error = &'static str;
257 
258                 fn try_from(value: i16) -> ::core::result::Result<Self, <Self as ::core::convert::TryFrom<i16>>::Error> {
259                     use ::num_traits::FromPrimitive;
260 
261                     #to_name::from_i16(value).ok_or_else(|| "Unknown variant")
262                 }
263             }
264 
265             impl ::core::convert::TryFrom<i8> for #name {
266                 type Error = &'static str;
267 
268                 fn try_from(value: i8) -> ::core::result::Result<Self, <Self as ::core::convert::TryFrom<i8>>::Error> {
269                     use ::num_traits::FromPrimitive;
270 
271                     #to_name::from_i8(value).ok_or_else(|| "Unknown variant")
272                 }
273             }
274         })
275     } else {
276         panic!("#[derive(Primitive)] is only valid for C-like enums");
277     }
278 }
279