wrapcenum_derive/
lib.rs

1/*!
2Internal macro used in [nvml-wrapper](https://github.com/Cldfire/nvml-wrapper).
3
4This macro is tied to the crate and is not meant for use by the general public.
5
6Its purpose is to auto-generate both a `TryFrom` implementation converting an `i32`
7into a Rust enum (specifically for converting a C enum represented as an integer that
8has come over FFI) and an `as_c` method for converting the Rust enum back into an `i32`.
9
10It wouldn't take much effort to turn this into something usable by others; if you're
11interested feel free to contribute or file an issue asking me to put some work into it.
12*/
13
14use proc_macro2::{Ident, Span, TokenStream};
15use quote::{quote, ToTokens};
16use syn;
17
18use darling::{ast, FromVariant, FromDeriveInput};
19
20/// Handles parsing attributes on the enum itself
21#[derive(Debug, FromDeriveInput)]
22#[darling(attributes(wrap), supports(enum_any))]
23struct EnumReceiver {
24    ident: Ident,
25    data: ast::Data<VariantReceiver, ()>,
26    /// The ident of the C enum to be wrapped
27    c_enum: String,
28}
29
30impl ToTokens for EnumReceiver {
31    fn to_tokens(&self, tokens: &mut TokenStream) {
32        let EnumReceiver {
33            ref ident,
34            ref data,
35            ref c_enum,
36        } = *self;
37
38        let join_c_name_and_variant = |name: &str, variant: &str| {
39            Ident::new(&format!("{}_{}", &name, variant), Span::call_site())
40        };
41
42        let c_name = Ident::new(c_enum, Span::call_site());
43        let rust_name = ident;
44
45        let variants = data
46            .as_ref()
47            .take_enum()
48            .expect("should never be a struct");
49
50        let as_arms = variants
51            .iter()
52            .map(|v| {
53                let c_joined = join_c_name_and_variant(&c_name.to_string(), &v.c_variant);
54                let v_ident = &v.ident;
55
56                quote! {
57                    #rust_name::#v_ident => #c_joined,
58                }
59            })
60            .collect::<Vec<_>>();
61
62        let try_from_arms = variants
63            .into_iter()
64            .map(|v| {
65                let c_joined = join_c_name_and_variant(&c_name.to_string(), &v.c_variant);
66                let v_ident = &v.ident;
67
68                quote! {
69                    #c_joined => Ok(#rust_name::#v_ident),
70                }
71            })
72            .collect::<Vec<_>>();
73
74        tokens.extend(quote! {
75            impl #rust_name {
76                /// Returns the C enum variant equivalent for the given Rust enum variant
77                pub fn as_c(&self) -> #c_name {
78                    match *self {
79                        #(#as_arms)*
80                    }
81                }
82            }
83
84            impl ::std::convert::TryFrom<#c_name> for #rust_name {
85                type Error = NvmlError;
86
87                fn try_from(data: #c_name) -> Result<Self, Self::Error> {
88                    match data {
89                        #(#try_from_arms)*
90                        _ => Err(NvmlError::UnexpectedVariant(data)),
91                    }
92                }
93            }
94        });
95    }
96}
97
98/// Handles parsing attributes on enum variants
99#[derive(Debug, FromVariant)]
100#[darling(attributes(wrap))]
101struct VariantReceiver {
102    ident: Ident,
103    /// The ident of the C enum variant this Rust variant maps to
104    c_variant: String
105}
106
107#[proc_macro_derive(EnumWrapper, attributes(wrap))]
108pub fn wrapcenum_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
109    let ast = syn::parse(input).unwrap();
110    let receiver = EnumReceiver::from_derive_input(&ast).unwrap();
111
112    quote!(#receiver).into()
113}