int_to_c_enum_derive/
lib.rs

1// SPDX-License-Identifier: MPL-2.0
2
3use 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}