neli_proc_macros/
neli_enum.rs

1use proc_macro::TokenStream;
2use proc_macro2::{Span, TokenStream as TokenStream2};
3use quote::quote;
4use syn::{
5    parse, parse_str, Arm, Attribute, Expr, Ident, ItemEnum, Lit, Meta, Path, Token, Type, Variant,
6};
7
8use crate::shared::remove_bad_attrs;
9
10fn parse_type_attr(attr: Meta) -> Type {
11    if let Meta::NameValue(nv) = attr {
12        if nv.path == parse_str::<Path>("serialized_type").unwrap() {
13            if let Lit::Str(ls) = nv.lit {
14                return parse_str::<Type>(&ls.value())
15                    .unwrap_or_else(|_| panic!("Invalid type supplied: {}", ls.value()));
16            }
17        }
18    }
19
20    panic!("Attribute in the form #[neli(serialized_type = \"TYPE_LITERAL_STR\")] required")
21}
22
23fn parse_enum(enm: &mut ItemEnum, ty: &Type) -> Vec<(Vec<Attribute>, Ident, Expr)> {
24    let exprs = enm
25        .variants
26        .iter_mut()
27        .map(|var| {
28            if let Some((_, expr)) = var.discriminant.take() {
29                (var.attrs.clone(), var.ident.clone(), expr)
30            } else {
31                panic!("All variants in the provided enum require an expression assignment")
32            }
33        })
34        .collect();
35    if !enm.variants.trailing_punct() {
36        enm.variants.push_punct(Token![,](Span::call_site()));
37    }
38    enm.variants.push_value(
39        parse::<Variant>(TokenStream::from(quote! {
40            UnrecognizedConst(#ty)
41        }))
42        .expect("Could not parse tokens as a variant"),
43    );
44    exprs
45}
46
47fn parse_from_info(
48    enum_name: Ident,
49    var_info: Vec<(Vec<Attribute>, Ident, Expr)>,
50) -> (Vec<Arm>, Vec<Arm>) {
51    let mut from_const_info = Vec::new();
52    let mut from_type_info = Vec::new();
53    for (mut attributes, ident, expr) in var_info {
54        attributes = remove_bad_attrs(attributes);
55        let mut from_const_arm = parse::<Arm>(TokenStream::from(quote! {
56            #(
57                #attributes
58            )*
59            i if i == #expr => #enum_name::#ident,
60        }))
61        .expect("Failed to parse tokens as a match arm");
62        from_const_arm.attrs = attributes.clone();
63        from_const_info.push(from_const_arm);
64
65        let mut from_type_arm = parse::<Arm>(TokenStream::from(quote! {
66            #(
67                #attributes
68            )*
69            #enum_name::#ident => #expr,
70        }))
71        .expect("Failed to parse tokens as a match arm");
72        from_type_arm.attrs = attributes.clone();
73        from_type_info.push(from_type_arm);
74    }
75    (from_const_info, from_type_info)
76}
77
78pub fn generate_neli_enum(mut enm: ItemEnum, meta: Meta) -> TokenStream2 {
79    let enum_name = enm.ident.clone();
80    let ty = parse_type_attr(meta);
81
82    let variant_info = parse_enum(&mut enm, &ty);
83    let (from_const_info, from_type_info) = parse_from_info(enum_name.clone(), variant_info);
84
85    quote! {
86        #[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
87        #[allow(missing_docs)]
88        #enm
89
90        impl #enum_name {
91            /// Check whether a given method is an unrecognized
92            /// constant for the set of possible constants
93            /// associated with the current type.
94            pub fn is_unrecognized(&self) -> bool {
95                match *self {
96                    #enum_name::UnrecognizedConst(_) => true,
97                    _ => false,
98                }
99            }
100        }
101
102        impl neli::Size for #enum_name {
103            fn unpadded_size(&self) -> usize {
104                std::mem::size_of::<#ty>()
105            }
106        }
107
108        impl neli::TypeSize for #enum_name {
109            fn type_size() -> usize {
110                std::mem::size_of::<#ty>()
111            }
112        }
113
114        impl neli::ToBytes for #enum_name {
115            fn to_bytes(&self, buffer: &mut std::io::Cursor<Vec<u8>>) -> Result<(), neli::err::SerError> {
116                let bin_rep: #ty = self.into();
117                bin_rep.to_bytes(buffer)
118            }
119        }
120
121        impl<'lt> neli::FromBytes<'lt> for #enum_name {
122            fn from_bytes(buffer: &mut std::io::Cursor<&'lt [u8]>) -> Result<Self, neli::err::DeError> {
123                Ok(#enum_name::from(<#ty as neli::FromBytes>::from_bytes(
124                    buffer
125                )?))
126            }
127        }
128
129        impl From<#ty> for #enum_name {
130            fn from(cnst: #ty) -> Self {
131                match cnst {
132                    #(
133                        #from_const_info
134                    )*
135                    i => #enum_name::UnrecognizedConst(i),
136                }
137            }
138        }
139
140        impl From<#enum_name> for #ty {
141            fn from(enm: #enum_name) -> Self {
142                match enm {
143                    #(
144                        #from_type_info
145                    )*
146                    #enum_name::UnrecognizedConst(i) => i,
147                }
148            }
149        }
150
151        impl From<&#enum_name> for #ty {
152            fn from(enm: &#enum_name) -> Self {
153                match *enm {
154                    #(
155                        #from_type_info
156                    )*
157                    #enum_name::UnrecognizedConst(i) => i,
158                }
159            }
160        }
161    }
162}