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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Procedural macros for `rkyv_typename`.

#![deny(
    rustdoc::broken_intra_doc_links,
    missing_docs,
    rustdoc::missing_crate_level_docs
)]

extern crate proc_macro;

use proc_macro2::TokenStream;
use quote::quote;
use syn::{parse_macro_input, spanned::Spanned, AttrStyle, DeriveInput, Error, Lit, Meta};

#[derive(Default)]
struct Attributes {
    typename: Option<String>,
}

fn parse_attributes(input: &DeriveInput) -> Result<Attributes, TokenStream> {
    let mut result = Attributes::default();
    for a in input.attrs.iter() {
        if let AttrStyle::Outer = a.style {
            if let Ok(Meta::NameValue(meta)) = a.parse_meta() {
                if meta.path.is_ident("typename") {
                    if result.typename.is_none() {
                        if let Lit::Str(ref lit_str) = meta.lit {
                            result.typename = Some(lit_str.value());
                        } else {
                            return Err(Error::new(
                                meta.lit.span(),
                                "typename must be set to a string",
                            )
                            .to_compile_error());
                        }
                    } else {
                        return Err(Error::new(
                            meta.span(),
                            "typename attribute already specified",
                        )
                        .to_compile_error());
                    }
                }
            }
        }
    }
    Ok(result)
}

/// Derives `TypeName` for the labeled type.
///
/// A custom name can be set using the attribute `#[typename = "..."]`.
#[proc_macro_derive(TypeName, attributes(typename))]
pub fn type_name_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    let type_name_impl = derive_type_name_impl(&input);

    proc_macro::TokenStream::from(type_name_impl)
}

fn derive_type_name_impl(input: &DeriveInput) -> TokenStream {
    let attributes = match parse_attributes(input) {
        Ok(result) => result,
        Err(error) => return error,
    };

    let generic_params = input.generics.params.iter().map(|p| quote! { #p });
    let generic_args = input.generics.type_params().map(|p| {
        let name = &p.ident;
        quote! { #name }
    });
    let generic_predicates = match input.generics.where_clause {
        Some(ref clause) => {
            let predicates = clause.predicates.iter().map(|p| quote! { #p });
            quote! { #(#predicates,)* }
        }
        None => quote! {},
    };

    let type_wheres = input.generics.type_params().map(|p| {
        let name = &p.ident;
        quote! { #name: rkyv_typename::TypeName }
    });

    let name = &input.ident;
    let module_path = if attributes.typename.is_none() {
        quote! {
            f(core::concat!(core::module_path!(), "::"));
        }
    } else {
        quote! {}
    };
    let name_str = attributes
        .typename
        .unwrap_or_else(|| input.ident.to_string());

    let build_args = if !input.generics.params.is_empty() {
        let mut results = input.generics.type_params().map(|p| {
            let name = &p.ident;
            quote! { #name::build_type_name(&mut f) }
        });
        let first = results.next().unwrap();
        let name_str = format!("{}<", name_str);
        quote! {
            #module_path
            f(#name_str);
            #first;
            #(f(", "); #results;)*
            f(">");
        }
    } else {
        quote! {
            #module_path
            f(#name_str)
        }
    };

    quote! {
        const _: () = {
            use rkyv_typename::TypeName;

            impl<#(#generic_params,)*> TypeName for #name<#(#generic_args,)*>
            where
                #generic_predicates
                #(#type_wheres,)*
            {
                fn build_type_name<F: FnMut(&str)>(mut f: F) {
                    #build_args
                }
            }
        };
    }
}