idmap_derive/
lib.rs

1use quote::quote;
2
3use proc_macro2::TokenStream;
4use quote::ToTokens;
5use syn::{Data, DeriveInput, Expr, ExprLit, Fields, Lit};
6
7#[proc_macro_derive(IntegerId)]
8pub fn integer_id(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
9    let ast = syn::parse(input).unwrap();
10    impl_integer_id(&ast)
11        .unwrap_or_else(syn::Error::into_compile_error)
12        .into()
13}
14
15// The compiler doesn't seem to know when variables are used in the macro
16fn impl_integer_id(ast: &DeriveInput) -> syn::Result<TokenStream> {
17    let name = &ast.ident;
18    match ast.data {
19        Data::Struct(ref data) => {
20            let fields = &data.fields;
21            match fields.len() {
22                1 => {
23                    let field = fields.iter().next().unwrap();
24                    /*
25                     * NOTE: Delegating to the field's implementation allows efficient polymorphic overflow handling for all supported types.
26                     * New types can be added to the library transparently, without changing the automatically derived implementation.
27                     * Existing types can be improved by changing the implementation in one place, without touching the derived implementation.
28                     * This should have zero overhead when inlining is enabled, since they're marked inline(always).
29                     */
30                    let field_type = &field.ty;
31                    let (constructor, field_name) = match data.fields {
32                        Fields::Named(_) => {
33                            let field_name = field.ident.to_token_stream();
34                            (quote!(#name { #field_name: value }), field_name)
35                        }
36                        Fields::Unnamed(_) => (quote! { #name( value ) }, quote!(0)),
37                        Fields::Unit => unreachable!(),
38                    };
39                    Ok(quote! {
40                        impl ::idmap::IntegerId for #name {
41                            #[inline(always)]
42                            fn from_id(id: u64) -> Self {
43                                let value = <#field_type as ::idmap::IntegerId>::from_id(id);
44                                #constructor
45                            }
46                            #[inline(always)]
47                            fn id(&self) -> u64 {
48                                <#field_type as ::idmap::IntegerId>::id(&self.#field_name)
49                            }
50                            #[inline(always)]
51                            fn id32(&self) -> u32 {
52                                <#field_type as ::idmap::IntegerId>::id32(&self.#field_name)
53                            }
54                        }
55                    })
56                }
57                0 => Err(syn::Error::new_spanned(
58                    &ast.ident,
59                    "IntegerId does not currently support empty structs",
60                )),
61                _ => Err(syn::Error::new_spanned(
62                    fields.iter().nth(1).unwrap(),
63                    "IntegerId can only be applied to structs with a single field",
64                )),
65            }
66        }
67        Data::Enum(ref data) => {
68            let mut idx = 0;
69            let mut variant_matches = Vec::new();
70            let mut errors = Vec::new();
71            for variant in &data.variants {
72                let ident = &variant.ident;
73                match variant.fields {
74                    Fields::Unit => (),
75                    _ => errors.push(syn::Error::new_spanned(
76                        &variant.fields,
77                        "IntegerId can only be applied to C-like enums",
78                    )),
79                }
80                match &variant.discriminant {
81                    Some((
82                        _,
83                        Expr::Lit(ExprLit {
84                            lit: Lit::Int(value),
85                            ..
86                        }),
87                    )) => match value.base10_parse::<u64>() {
88                        Ok(discriminant) => {
89                            idx = discriminant;
90                        }
91                        Err(x) => errors.push(x),
92                    },
93                    Some((_, discriminant_expr)) => errors.push(syn::Error::new_spanned(
94                        discriminant_expr,
95                        "Discriminant too complex to understand",
96                    )),
97                    None => {}
98                }
99                variant_matches.push(quote!(#idx => #name::#ident));
100                idx += 1;
101            }
102            let mut errors = errors.into_iter();
103            if let Some(mut error) = errors.next() {
104                for other in errors {
105                    error.combine(other);
106                }
107                Err(error)
108            } else {
109                Ok(quote! {
110                    impl ::idmap::IntegerId for #name {
111                        #[inline]
112                        #[track_caller]
113                        fn from_id(id: u64) -> Self {
114                            match id {
115                                #(#variant_matches,)*
116                                _ => ::idmap::_invalid_id(id)
117                            }
118                        }
119                        #[inline]
120                        fn id(&self) -> u64 {
121                            *self as u64
122                        }
123                        #[inline]
124                        fn id32(&self) -> u32 {
125                            *self as u32
126                        }
127                    }
128                })
129            }
130        }
131        Data::Union(ref data) => Err(syn::Error::new_spanned(
132            data.union_token,
133            "Unions are unsupported",
134        )),
135    }
136}