1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#![recursion_limit = "128"]
extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;
use syn::{DeriveInput, Body, ConstExpr, Lit, VariantData};

#[proc_macro_derive(IntegerId)]
pub fn integer_id(input: TokenStream) -> TokenStream {
    let text = input.to_string();
    let ast = syn::parse_derive_input(&text).unwrap();
    impl_integer_id(&ast).parse().unwrap()
}

// The compiler doesn't seem to know when variables are used in the macro
#[allow(unused_variables)]
fn impl_integer_id(ast: &DeriveInput) -> quote::Tokens {
    let name = &ast.ident;
    match ast.body {
        Body::Struct(ref data) => {
            let fields = data.fields();
            match fields.len() {
                1 => {
                    let field = &fields[0];
                    /*
                     * NOTE: Delegating to the field's implementation allows efficient polymorphic overflow handling for all supported types.
                     * New types can be added to the library transparently, without changing the automatically derived implementation.
                     * Existing types can be improved by changing the implementation in one place, without touching the derived implementation.
                     * This should have zero overhead when inlining is enabled, since they're marked inline(always).
                     */
                    let field_type = &field.ty;
                    let (constructor, field_name) = match *data {
                        VariantData::Struct(_) => {
                            let field_name = create_tokens(&field.ident);
                            (quote!(#name { #field_name: value }), field_name)
                        },
                        VariantData::Tuple(_) => (quote! { #name( value ) }, quote!(0)),
                        VariantData::Unit => unreachable!()
                    };
                    quote! {
                        impl ::idmap::IntegerId for #name {
                            type Storage = <#field_type as ::idmap::IntegerId>::Storage;
                            #[inline(always)]
                            fn from_storage(storage: Self::Storage, id: u64) -> Self {
                                let value = <#field_type as ::idmap::IntegerId>::from_storage(storage, id);
                                #constructor
                            }
                            #[inline(always)]
                            fn into_storage(self) -> Self::Storage {
                                <#field_type as ::idmap::IntegerId>::into_storage(self.#field_name)
                            }
                            #[inline(always)]
                            fn id(&self) -> u64 {
                                <#field_type as ::idmap::IntegerId>::id(&self.#field_name)
                            }
                            #[inline(always)]                    
                            fn id32(&self) -> u32 {
                                <#field_type as ::idmap::IntegerId>::id32(&self.#field_name)
                            }
                        }
                    }
                },
                0 => panic!("`IntegerId` is currently unimplemented for empty structs"),
                _ => panic!("`IntegerId` can only be applied to structs with a single field, but {} has {}", name, fields.len())
            }
        },
        Body::Enum(ref variants) => {
            let mut idx = 0;
            let variants: Vec<_> = variants.iter().map(|variant| {
                let ident = &variant.ident;
                match variant.data {
                    VariantData::Unit => (),
                    _ => {
                        panic!("`IntegerId` can be applied only to unitary enums, {}::{} is either struct or tuple", name, ident)
                    },
                }
                match variant.discriminant {
                    Some(ConstExpr::Lit(Lit::Int(value, _))) => idx = value,
                    Some(_) => panic!("Can't handle discriminant for {}::{}", name, ident),
                    None => {}
                }
                let tt = quote!(#idx => #name::#ident);
                idx += 1;
                tt
            }).collect();
            quote! {
                impl ::idmap::IntegerId for #name {
                    type Storage = ();
                    #[inline]
                    fn from_storage(_: (), id: u64) -> Self {
                        match id {
                            #(variants,)*
                            _ => ::idmap::_invalid_id(id)
                        }
                    }
                    #[inline(always)]
                    fn into_storage(self) {}
                    #[inline(always)]
                    fn id(&self) -> u64 {
                        *self as u64
                    }
                    #[inline(always)]
                    fn id32(&self) -> u32 {
                        *self as u32
                    }
                }
            }
        }
    }
}
#[inline]
fn create_tokens<T: quote::ToTokens>(value: &T) -> quote::Tokens {
    let mut result = quote::Tokens::new();
    value.to_tokens(&mut result);
    result
}