enum_primitive_derive/
lib.rs

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
107extern crate proc_macro;
108
109use proc_macro::TokenStream;
110
111/// Provides implementation of `num_traits::ToPrimitive` and
112/// `num_traits::FromPrimitive`
113#[proc_macro_derive(Primitive)]
114pub fn primitive(input: TokenStream) -> TokenStream {
115    let ast = syn::parse_macro_input!(input as syn::DeriveInput);
116    impl_primitive(&ast)
117}
118
119fn 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}