idmap_derive/
lib.rs

1#![recursion_limit = "128"]
2extern crate proc_macro;
3
4use quote::quote;
5
6use proc_macro2::TokenStream;
7use quote::ToTokens;
8use syn::{Data, DeriveInput, Expr, ExprLit, Fields, Lit};
9
10#[proc_macro_derive(IntegerId)]
11pub fn integer_id(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
12    let ast = syn::parse(input).unwrap();
13    impl_integer_id(&ast).into()
14}
15
16// The compiler doesn't seem to know when variables are used in the macro
17#[allow(unused_variables)]
18fn impl_integer_id(ast: &DeriveInput) -> TokenStream {
19    let name = &ast.ident;
20    match ast.data {
21        Data::Struct(ref data) => {
22            let fields = &data.fields;
23            match fields.len() {
24                1 => {
25                    let field = fields.iter().next().unwrap();
26                    /*
27                     * NOTE: Delegating to the field's implementation allows efficient polymorphic overflow handling for all supported types.
28                     * New types can be added to the library transparently, without changing the automatically derived implementation.
29                     * Existing types can be improved by changing the implementation in one place, without touching the derived implementation.
30                     * This should have zero overhead when inlining is enabled, since they're marked inline(always).
31                     */
32                    let field_type = &field.ty;
33                    let (constructor, field_name) = match data.fields {
34                        Fields::Named(_) => {
35                            let field_name = field.ident.to_token_stream();
36                            (quote!(#name { #field_name: value }), field_name)
37                        }
38                        Fields::Unnamed(_) => (quote! { #name( value ) }, quote!(0)),
39                        Fields::Unit => unreachable!(),
40                    };
41                    quote! {
42                        impl ::idmap::IntegerId for #name {
43                            #[inline(always)]
44                            fn from_id(id: u64) -> Self {
45                                let value = <#field_type as ::idmap::IntegerId>::from_id(id);
46                                #constructor
47                            }
48                            #[inline(always)]
49                            fn id(&self) -> u64 {
50                                <#field_type as ::idmap::IntegerId>::id(&self.#field_name)
51                            }
52                            #[inline(always)]
53                            fn id32(&self) -> u32 {
54                                <#field_type as ::idmap::IntegerId>::id32(&self.#field_name)
55                            }
56                        }
57                    }
58                }
59                0 => panic!("`IntegerId` is currently unimplemented for empty structs"),
60                _ => panic!(
61                    "`IntegerId` can only be applied to structs with a single field, but {} has {}",
62                    name,
63                    fields.len()
64                ),
65            }
66        }
67        Data::Enum(ref data) => {
68            let mut idx = 0;
69            let variants: Vec<_> = data.variants.iter().map(|variant| {
70                let ident = &variant.ident;
71                match variant.fields {
72                    Fields::Unit => (),
73                    _ => {
74                        panic!("`IntegerId` can be applied only to unitary enums, {}::{} is either struct or tuple", name, ident)
75                    },
76                }
77                match &variant.discriminant {
78                    Some((_, Expr::Lit(ExprLit { lit: Lit::Int(value), .. }))) => {
79                        idx = value.base10_parse::<u64>().expect("Unable to parse discriminant");
80                    },
81                    Some(_) => panic!("Can't handle discriminant for {}::{}", name, ident),
82                    None => {}
83                }
84                let tt = quote!(#idx => #name::#ident);
85                idx += 1;
86                tt
87            }).collect();
88            quote! {
89                impl ::idmap::IntegerId for #name {
90                    #[inline]
91                    fn from_id(id: u64) -> Self {
92                        match id {
93                            #(#variants,)*
94                            _ => ::idmap::_invalid_id(id)
95                        }
96                    }
97                    #[inline(always)]
98                    fn id(&self) -> u64 {
99                        *self as u64
100                    }
101                    #[inline(always)]
102                    fn id32(&self) -> u32 {
103                        *self as u32
104                    }
105                }
106            }
107        }
108        Data::Union(_) => panic!("Unions are unsupported!"),
109    }
110}