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
15fn 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 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}