int_to_c_enum_derive/
lib.rs1use proc_macro2::{Ident, TokenStream};
4use quote::{format_ident, quote, TokenStreamExt};
5use syn::{parse_macro_input, Attribute, Data, DataEnum, DeriveInput, Generics};
6
7const ALLOWED_REPRS: &[&str] = &[
8 "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "usize", "isize",
9];
10const VALUE_NAME: &str = "value";
11const REPR_PATH: &str = "repr";
12
13#[proc_macro_derive(TryFromInt)]
14pub fn derive_try_from_num(input_token: proc_macro::TokenStream) -> proc_macro::TokenStream {
15 let input = parse_macro_input!(input_token as DeriveInput);
16 expand_derive_try_from_num(input).into()
17}
18
19fn expand_derive_try_from_num(input: DeriveInput) -> TokenStream {
20 let attrs = input.attrs;
21 let ident = input.ident;
22 let generics = input.generics;
23 if let Data::Enum(data_enum) = input.data {
24 impl_try_from(data_enum, attrs, generics, ident)
25 } else {
26 panic!("cannot derive TryFromInt for structs or union.")
27 }
28}
29
30fn impl_try_from(
31 data_enum: DataEnum,
32 attrs: Vec<Attribute>,
33 generics: Generics,
34 ident: Ident,
35) -> TokenStream {
36 let valid_repr = if let Some(valid_repr) = has_valid_repr(attrs) {
37 format_ident!("{}", valid_repr)
38 } else {
39 panic!(
40 "{} does not have invalid repr to implement TryFromInt.",
41 ident
42 );
43 };
44
45 for variant in &data_enum.variants {
46 if variant.discriminant.is_none() {
47 panic!("Enum can only have fields like Variant=1");
48 }
49 }
50
51 let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
52 let fn_body = fn_body_tokens(VALUE_NAME, &data_enum, ident.clone());
53 let param = format_ident!("{}", VALUE_NAME);
54 quote!(
55 #[automatically_derived]
56 impl #impl_generics ::core::convert::TryFrom<#valid_repr> #type_generics for #ident #where_clause {
57 type Error = ::int_to_c_enum::TryFromIntError;
58 fn try_from(#param: #valid_repr) -> ::core::result::Result<Self, Self::Error> {
59 #fn_body
60 }
61 }
62 )
63}
64
65fn fn_body_tokens(value_name: &str, data_enum: &DataEnum, ident: Ident) -> TokenStream {
66 let mut match_bodys = quote!();
67 for variant in &data_enum.variants {
68 let (_, value) = variant
69 .discriminant
70 .as_ref()
71 .expect("Each field must be assigned a discriminant value explicitly");
72 let variant_ident = &variant.ident;
73 let statement = quote!(#value => ::core::result::Result::Ok(#ident::#variant_ident),);
74 match_bodys.append_all(statement);
75 }
76 match_bodys.append_all(
77 quote!(_ => core::result::Result::Err(::int_to_c_enum::TryFromIntError::InvalidValue),),
78 );
79 let param = format_ident!("{}", value_name);
80 quote!(match #param {
81 #match_bodys
82 })
83}
84
85fn has_valid_repr(attrs: Vec<Attribute>) -> Option<&'static str> {
86 for attr in attrs {
87 if let Some(repr) = parse_repr(attr) {
88 return Some(repr);
89 }
90 }
91 None
92}
93
94fn parse_repr(attr: Attribute) -> Option<&'static str> {
95 if !attr.path().is_ident(REPR_PATH) {
96 return None;
97 }
98 let mut repr = None;
99 attr.parse_nested_meta(|meta| {
100 for allowed_repr in ALLOWED_REPRS {
101 if meta.path.is_ident(*allowed_repr) {
102 repr = Some(*allowed_repr);
103 break;
104 }
105 }
106 Ok(())
107 })
108 .ok()?;
109 repr
110}