Skip to main content

type_info_derive/
lib.rs

1//! A procedural macro for deriving `TypeInfo` for any type.
2//!
3//! See the `type-info` crate for more information as to what this means.
4#![feature(const_type_id)]
5#![recursion_limit = "128"]
6
7extern crate proc_macro;
8extern crate proc_macro2;
9#[macro_use]
10extern crate quote;
11#[macro_use]
12extern crate syn;
13#[cfg(test)]
14extern crate tempfile;
15#[cfg(test)]
16extern crate type_info;
17
18#[cfg(test)]
19mod test;
20
21struct MetaType<'a> {
22    tokens: quote::Tokens,
23    ident: syn::Ident,
24    data: MetaData<'a>,
25}
26
27struct MetaData<'a> {
28    tokens: quote::Tokens,
29    fields: Option<MetaFields<'a>>,
30    variants: Option<Vec<MetaVariant<'a>>>,
31}
32
33struct MetaFields<'a> {
34    tokens: quote::Tokens,
35    kind: MetaFieldsKind,
36    fields: Vec<MetaField<'a>>,
37}
38
39enum MetaFieldsKind {
40    Unit,
41    Unnamed(usize),
42    Named(usize),
43}
44
45struct MetaField<'a> {
46    tokens: quote::Tokens,
47    id: MetaFieldId<'a>,
48}
49
50struct MetaVariant<'a> {
51    tokens: quote::Tokens,
52    id: MetaVariantId,
53    fields: MetaFields<'a>,
54}
55
56enum MetaFieldId<'a> {
57    Unnamed(syn::Index),
58    Named(&'a syn::Ident),
59}
60
61struct MetaVariantId(syn::Ident);
62
63enum MetaBorrow {
64    Ref,
65    Mut,
66}
67
68/// Derive the `TypeInfo` and `DynamicTypeInfo` traits for a given type.
69#[proc_macro_derive(TypeInfo, attributes(type_info))]
70pub fn type_info(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
71    let ast = syn::parse(input).unwrap();
72    let gen = impl_type_info(ast);
73    gen.into()
74}
75
76#[cfg(test)]
77pub fn type_info_test(input: proc_macro2::TokenStream) -> quote::Tokens {
78    let ast = syn::parse2(input).unwrap();
79    impl_type_info(ast)
80}
81
82fn impl_type_info(mut ast: syn::DeriveInput) -> quote::Tokens {
83    let ident = &ast.ident;
84
85    add_static(&mut ast.generics);
86
87    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
88
89    let type_info = build_type_info(&ast);
90    let tokens = &type_info.tokens;
91
92    let field_fn = build_field_fn(&type_info);
93    let field_mut_fn = build_field_mut_fn(&type_info);
94    let variant_fn = build_variant_fn(&type_info);
95    let field_any_fn = build_field_any_fn(&type_info);
96    let field_any_mut_fn = build_field_any_mut_fn(&type_info);
97
98    quote! {
99        impl #impl_generics ::type_info::TypeInfo for #ident #ty_generics #where_clause {
100            const TYPE: ::type_info::Type = #tokens;
101            #field_fn
102            #field_mut_fn
103        }
104
105        impl #impl_generics ::type_info::DynamicTypeInfo for #ident #ty_generics #where_clause {
106            fn type_ref(&self) -> &'static ::type_info::Type {
107                &<Self as ::type_info::TypeInfo>::TYPE
108            }
109
110            #variant_fn
111            #field_any_fn
112            #field_any_mut_fn
113        }
114    }
115}
116
117fn build_field_fn(type_info: &MetaType) -> quote::Tokens {
118    build_field_fn_body(
119        type_info,
120        |a| quote!({::std::any::Any::downcast_ref::<TypeInfoA>(#a)}),
121        MetaBorrow::Ref,
122    ).map(|body| {
123        quote! {
124            fn field<TypeInfoA>(&self, id: ::type_info::FieldId) -> ::std::option::Option<&TypeInfoA>
125            where
126                TypeInfoA: ::std::any::Any,
127            {
128                #body
129            }
130        }
131    })
132        .unwrap_or(quote!())
133}
134
135fn build_field_mut_fn(type_info: &MetaType) -> quote::Tokens {
136    build_field_fn_body(
137        type_info,
138        |a| quote!({::std::any::Any::downcast_mut::<TypeInfoA>(#a)}),
139        MetaBorrow::Mut,
140    ).map(|body| {
141        quote! {
142            fn field_mut<TypeInfoA>(&mut self, id: ::type_info::FieldId) -> ::std::option::Option<&mut TypeInfoA>
143            where
144                TypeInfoA: ::std::any::Any,
145            {
146                #body
147            }
148        }
149    })
150        .unwrap_or(quote!())
151}
152
153fn build_field_any_fn(type_info: &MetaType) -> quote::Tokens {
154    build_field_fn_body(
155        type_info,
156        |a| quote!(::std::option::Option::Some(#a),),
157        MetaBorrow::Ref,
158    ).map(|body| {
159        quote! {
160            fn field_any(&self, id: ::type_info::FieldId) -> ::std::option::Option<&::std::any::Any> {
161                #body
162            }
163        }
164    })
165        .unwrap_or(quote!())
166}
167
168fn build_field_any_mut_fn(type_info: &MetaType) -> quote::Tokens {
169    build_field_fn_body(
170        type_info,
171        |a| quote!(::std::option::Option::Some(#a),),
172        MetaBorrow::Mut,
173    ).map(|body| {
174        quote! {
175            fn field_any_mut(&mut self, id: ::type_info::FieldId) -> ::std::option::Option<&mut ::std::any::Any> {
176                #body
177            }
178        }
179    })
180        .unwrap_or(quote!())
181}
182
183fn build_field_fn_body<A>(
184    type_info: &MetaType,
185    accessor_builder: A,
186    meta_borrow: MetaBorrow,
187) -> Option<quote::Tokens>
188where
189    A: FnMut(quote::Tokens) -> quote::Tokens,
190{
191    if let Some(ref variants) = type_info.data.variants {
192        if variants.iter().all(|v| v.fields.fields.is_empty()) {
193            None
194        } else {
195            Some(build_field_fn_body_from_variants(
196                &type_info.ident,
197                variants,
198                accessor_builder,
199                meta_borrow,
200            ))
201        }
202    } else if let Some(MetaFields { ref fields, .. }) = type_info.data.fields {
203        if fields.is_empty() {
204            None
205        } else {
206            Some(build_field_fn_body_from_fields(
207                fields,
208                accessor_builder,
209                meta_borrow,
210            ))
211        }
212    } else {
213        None
214    }
215}
216
217fn build_field_fn_body_from_fields<A>(
218    meta_fields: &[MetaField],
219    mut accessor_builder: A,
220    meta_borrow: MetaBorrow,
221) -> quote::Tokens
222where
223    A: FnMut(quote::Tokens) -> quote::Tokens,
224{
225    let fields = meta_fields.iter().map(|f| match f.id {
226        MetaFieldId::Unnamed(ref i) => {
227            let i_usize = i.index as usize;
228            let accessor = accessor_builder(match meta_borrow {
229                MetaBorrow::Ref => quote!(&self.#i),
230                MetaBorrow::Mut => quote!(&mut self.#i),
231            });
232
233            quote! {
234                ::type_info::FieldId::Unnamed(#i_usize) => #accessor
235            }
236        }
237        MetaFieldId::Named(i) => {
238            let i_str = i.as_ref();
239            let accessor = accessor_builder(match meta_borrow {
240                MetaBorrow::Ref => quote!(&self.#i),
241                MetaBorrow::Mut => quote!(&mut self.#i),
242            });
243
244            quote! {
245                ::type_info::FieldId::Named(#i_str) => #accessor
246            }
247        }
248    });
249
250    quote! {
251        match id {
252            #(#fields)*
253            _ => ::std::option::Option::None,
254        }
255    }
256}
257
258fn build_field_fn_body_from_variants<A>(
259    type_ident: &syn::Ident,
260    meta_variants: &[MetaVariant],
261    mut accessor_builder: A,
262    meta_borrow: MetaBorrow,
263) -> quote::Tokens
264where
265    A: FnMut(quote::Tokens) -> quote::Tokens,
266{
267    let variants = meta_variants.iter().map(|v| {
268        let ident = v.id.0;
269        let meta_fields = &v.fields.fields;
270
271        let syn_idents = (0..meta_fields.len())
272            .map(|idx| syn::Ident::from(format!("_{}", idx).as_str()))
273            .collect::<Vec<_>>();
274        let pat_syn_idents = syn_idents.iter().map(|ident| match meta_borrow {
275            MetaBorrow::Ref => quote!(ref #ident),
276            MetaBorrow::Mut => quote!(ref mut #ident),
277        });
278
279        match v.fields.kind {
280            MetaFieldsKind::Unit => quote! { #type_ident::#ident => ::std::option::Option::None, },
281            MetaFieldsKind::Unnamed(_) => {
282                let body = build_field_fn_variant_field_match(
283                    meta_fields,
284                    &syn_idents,
285                    &mut accessor_builder,
286                );
287
288                quote! { #type_ident::#ident(#(#pat_syn_idents,)*) => #body }
289            }
290            MetaFieldsKind::Named(_) => {
291                let pat_idents = meta_fields.iter().map(|f| match f.id {
292                    MetaFieldId::Named(ident) => ident,
293                    _ => unreachable!(),
294                });
295                let body = build_field_fn_variant_field_match(
296                    meta_fields,
297                    &syn_idents,
298                    &mut accessor_builder,
299                );
300
301                quote! { #type_ident::#ident { #(#pat_idents: #pat_syn_idents,)* } => #body }
302            }
303        }
304    });
305
306    quote! {
307        match *self {
308            #(#variants)*
309        }
310    }
311}
312
313fn build_field_fn_variant_field_match<A>(
314    meta_fields: &[MetaField],
315    syn_idents: &[syn::Ident],
316    mut accessor_builder: A,
317) -> quote::Tokens
318where
319    A: FnMut(quote::Tokens) -> quote::Tokens,
320{
321    if meta_fields.is_empty() {
322        quote!(::std::option::Option::None,)
323    } else {
324        let fields = meta_fields
325            .iter()
326            .zip(syn_idents)
327            .map(|(f, syn_ident)| match f.id {
328                MetaFieldId::Unnamed(ref i) => {
329                    let i_usize = i.index as usize;
330                    let accessor = accessor_builder(quote!(#syn_ident));
331
332                    quote! {
333                        ::type_info::FieldId::Unnamed(#i_usize) => #accessor
334                    }
335                }
336                MetaFieldId::Named(i) => {
337                    let i_str = i.as_ref();
338                    let accessor = accessor_builder(quote!(#syn_ident));
339
340                    quote! {
341                        ::type_info::FieldId::Named(#i_str) => #accessor
342                    }
343                }
344            });
345
346        quote! {
347            match id {
348                #(#fields)*
349                _ => ::std::option::Option::None,
350            },
351        }
352    }
353}
354
355fn build_variant_fn(type_info: &MetaType) -> quote::Tokens {
356    match type_info.data.variants {
357        Some(ref meta_variants) if !meta_variants.is_empty() => {
358            let variants = meta_variants.iter().map(|v| {
359                let type_ident = type_info.ident;
360                let ident = v.id.0;
361                let ident_str = ident.as_ref();
362                match v.fields.kind {
363                    MetaFieldsKind::Unit => {
364                        quote! { #type_ident::#ident => ::std::option::Option::Some(#ident_str), }
365                    }
366                    MetaFieldsKind::Unnamed(_) => {
367                        quote! { #type_ident::#ident( .. ) => ::std::option::Option::Some(#ident_str), }
368                    }
369                    MetaFieldsKind::Named(_) => {
370                        quote! { #type_ident::#ident { .. } => ::std::option::Option::Some(#ident_str), }
371                    }
372                }
373            });
374
375            quote! {
376                fn variant(&self) -> ::std::option::Option<&str> {
377                    match *self {
378                        #(#variants)*
379                    }
380                }
381            }
382        }
383        _ => quote!(),
384    }
385}
386
387fn add_static(generics: &mut syn::Generics) {
388    for type_param in generics.type_params_mut() {
389        if !type_param.bounds.empty_or_trailing() {
390            type_param
391                .bounds
392                .push_punct(syn::token::Add::new(proc_macro2::Span::call_site()));
393        }
394        type_param
395            .bounds
396            .push_value(syn::TypeParamBound::Trait(parse_quote!(::std::any::Any)));
397    }
398}
399
400fn build_type_info(ast: &syn::DeriveInput) -> MetaType {
401    let ident = &ast.ident;
402    let (_, generics, _) = &ast.generics.split_for_impl();
403    let ident_str = ident.as_ref();
404    let data = build_data(&ast.data);
405    let tokens = {
406        let data = &data.tokens;
407        quote! {
408            ::type_info::Type {
409                id: ::type_info::TypeId::of::<#ident #generics>(),
410                module: module_path!(),
411                ident: #ident_str,
412                data: #data,
413            }
414        }
415    };
416    let ident = ast.ident.clone();
417
418    MetaType {
419        tokens,
420        ident,
421        data,
422    }
423}
424
425fn build_data(data: &syn::Data) -> MetaData {
426    match *data {
427        syn::Data::Struct(ref data_struct) => build_data_struct(data_struct).map_tokens(|tokens| {
428            quote! {
429                ::type_info::Data::Struct(#tokens)
430            }
431        }),
432        syn::Data::Enum(ref data_enum) => build_data_enum(data_enum).map_tokens(|tokens| {
433            quote! {
434                ::type_info::Data::Enum(#tokens)
435            }
436        }),
437        syn::Data::Union(ref data_union) => build_data_union(data_union).map_tokens(|tokens| {
438            quote! {
439                ::type_info::Data::Union(#tokens)
440            }
441        }),
442    }
443}
444
445fn build_data_struct(data_struct: &syn::DataStruct) -> MetaData {
446    let data_struct_fields = build_fields(&data_struct.fields);
447    let tokens = {
448        let fields = &data_struct_fields.tokens;
449        quote! {
450            ::type_info::DataStruct {
451                fields: #fields,
452            }
453        }
454    };
455
456    MetaData {
457        tokens,
458        fields: Some(data_struct_fields),
459        variants: None,
460    }
461}
462
463fn build_data_enum(data_enum: &syn::DataEnum) -> MetaData {
464    let variants = data_enum
465        .variants
466        .iter()
467        .map(build_variant)
468        .collect::<Vec<_>>();
469
470    let tokens = {
471        let variant_tokens = variants.iter().map(|v| &v.tokens);
472        quote! {
473            ::type_info::DataEnum {
474                variants: &[
475                    #(#variant_tokens,)*
476                ],
477            }
478        }
479    };
480
481    MetaData {
482        tokens,
483        fields: None,
484        variants: Some(variants),
485    }
486}
487
488fn build_data_union(data_union: &syn::DataUnion) -> MetaData {
489    let fields_named = build_fields_named(&data_union.fields);
490    let tokens = {
491        let fields = &fields_named.tokens;
492        quote! {
493            ::type_info::DataUnion {
494                fields: #fields,
495            }
496        }
497    };
498
499    MetaData {
500        tokens,
501        fields: Some(fields_named),
502        variants: None,
503    }
504}
505
506fn build_variant(variant: &syn::Variant) -> MetaVariant {
507    let ident = variant.ident.as_ref();
508    let fields = build_fields(&variant.fields);
509    let tokens = {
510        let field_tokens = &fields.tokens;
511        quote! {
512            ::type_info::Variant {
513                ident: #ident,
514                fields: #field_tokens,
515            }
516        }
517    };
518
519    MetaVariant {
520        tokens,
521        id: MetaVariantId(variant.ident),
522        fields,
523    }
524}
525
526fn build_fields(fields: &syn::Fields) -> MetaFields {
527    match *fields {
528        syn::Fields::Named(ref fields_named) => {
529            build_fields_named(fields_named).map_tokens(|tokens| {
530                quote! {
531                    ::type_info::Fields::Named(#tokens)
532                }
533            })
534        }
535        syn::Fields::Unnamed(ref fields_unnamed) => build_fields_unnamed(fields_unnamed)
536            .map_tokens(|tokens| {
537                quote! {
538                    ::type_info::Fields::Unnamed(#tokens)
539                }
540            }),
541        syn::Fields::Unit => MetaFields {
542            tokens: quote! {
543                ::type_info::Fields::Unit
544            },
545            kind: MetaFieldsKind::Unit,
546            fields: vec![],
547        },
548    }
549}
550
551fn build_fields_named(fields_named: &syn::FieldsNamed) -> MetaFields {
552    let fields = fields_named
553        .named
554        .iter()
555        .enumerate()
556        .map(|(i, f)| build_field(i, f))
557        .collect::<Vec<_>>();
558    let tokens = {
559        let named = fields.iter().map(|f| &f.tokens);
560        quote! {
561            ::type_info::FieldsNamed {
562                named: &[
563                    #(#named,)*
564                ],
565            }
566        }
567    };
568    let kind = MetaFieldsKind::Named(fields.len());
569
570    MetaFields {
571        tokens,
572        fields,
573        kind,
574    }
575}
576
577fn build_fields_unnamed(fields_unnamed: &syn::FieldsUnnamed) -> MetaFields {
578    let fields = fields_unnamed
579        .unnamed
580        .iter()
581        .enumerate()
582        .map(|(i, f)| build_field(i, f))
583        .collect::<Vec<_>>();
584    let tokens = {
585        let unnamed = fields.iter().map(|f| &f.tokens);
586        quote! {
587            ::type_info::FieldsUnnamed {
588                unnamed: &[
589                    #(#unnamed,)*
590                ],
591            }
592        }
593    };
594    let kind = MetaFieldsKind::Unnamed(fields.len());
595
596    MetaFields {
597        tokens,
598        fields,
599        kind,
600    }
601}
602
603fn build_field(idx: usize, field: &syn::Field) -> MetaField {
604    match field.ident {
605        Some(ref ident) => {
606            let ident_str = ident.as_ref();
607            let ty = &field.ty;
608            let tokens = quote! {
609                ::type_info::Field {
610                    id: ::type_info::FieldId::Named(#ident_str),
611                    ident: ::std::option::Option::Some(#ident_str),
612                    ty: <#ty as ::type_info::TryTypeInfo>::TRY_TYPE,
613                }
614            };
615            let id = MetaFieldId::Named(ident);
616
617            MetaField { tokens, id }
618        }
619        None => {
620            let ty = &field.ty;
621            let tokens = quote! {
622                ::type_info::Field {
623                    id: ::type_info::FieldId::Unnamed( #idx),
624                    ident: ::std::option::Option::None,
625                    ty: <#ty as::type_info::TryTypeInfo >::TRY_TYPE,
626                }
627            };
628            let id = MetaFieldId::Unnamed(syn::Index {
629                index: idx as u32,
630                span: proc_macro2::Span::call_site(),
631            });
632
633            MetaField { tokens, id }
634        }
635    }
636}
637
638impl<'a> MetaData<'a> {
639    fn map_tokens<F>(self, mapper: F) -> Self
640    where
641        F: FnOnce(quote::Tokens) -> quote::Tokens,
642    {
643        MetaData {
644            tokens: mapper(self.tokens),
645            fields: self.fields,
646            variants: self.variants,
647        }
648    }
649}
650
651impl<'a> MetaFields<'a> {
652    fn map_tokens<F>(self, mapper: F) -> Self
653    where
654        F: FnOnce(quote::Tokens) -> quote::Tokens,
655    {
656        MetaFields {
657            tokens: mapper(self.tokens),
658            kind: self.kind,
659            fields: self.fields,
660        }
661    }
662}