Skip to main content

lark_debug_derive/
lib.rs

1extern crate proc_macro;
2
3#[macro_use]
4extern crate quote;
5
6use proc_macro2::TokenStream;
7
8#[proc_macro_derive(DebugWith)]
9pub fn derive_debug_with(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
10    // Parse the input tokens into a syntax tree.
11    let input = syn::parse_macro_input!(input as syn::DeriveInput);
12
13    // Used in the quasi-quotation below as `#name`.
14    let name = &input.ident;
15
16    // Generate an expression to sum up the heap size of each field.
17    let debug_with = debug_with(&input);
18
19    // Add a bound `T: HeapSize` to every type parameter T.
20    let generics = add_trait_bounds(input.generics);
21    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
22
23    let expanded = quote! {
24        // The generated impl.
25        impl #impl_generics ::lark_debug_with::DebugWith for #name #ty_generics #where_clause {
26            fn fmt_with<Cx: ?Sized>(&self, cx: &Cx, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27                #debug_with
28            }
29        }
30    };
31
32    if std::env::var("LARK_DEBUG_DERIVE").is_ok() {
33        eprintln!("expanded = {}", expanded);
34    }
35
36    // Hand the output tokens back to the compiler.
37    proc_macro::TokenStream::from(expanded)
38}
39
40// Add a bound `T: HeapSize` to every type parameter T.
41fn add_trait_bounds(mut generics: syn::Generics) -> syn::Generics {
42    // For each existing parameter, add `T: DebugWith`
43    for param in &mut generics.params {
44        if let syn::GenericParam::Type(ref mut type_param) = *param {
45            type_param
46                .bounds
47                .push(syn::parse_quote!(::lark_debug_with::DebugWith));
48        }
49    }
50
51    generics
52}
53
54// Generate an expression to sum up the heap size of each field.
55fn debug_with(input: &syn::DeriveInput) -> TokenStream {
56    match &input.data {
57        syn::Data::Struct(data) => debug_with_struct(&input.ident, data),
58        syn::Data::Enum(data) => debug_with_variants(&input.ident, data),
59        syn::Data::Union(_) => unimplemented!(),
60    }
61}
62
63fn debug_with_variants(type_name: &syn::Ident, data: &syn::DataEnum) -> TokenStream {
64    let variant_streams: Vec<_> = data
65        .variants
66        .iter()
67        .map(|variant| {
68            let variant_name = &variant.ident;
69            match &variant.fields {
70                syn::Fields::Named(fields) => {
71                    let fnames: &Vec<_> = &fields.named.iter().map(|f| &f.ident).collect();
72                    let fnames1: &Vec<_> = fnames;
73                    quote! {
74                        #type_name :: #variant_name { #(#fnames),* } => {
75                            fmt.debug_struct(stringify!(#variant_name))
76                                #(
77                                    .field(stringify!(#fnames), &#fnames1.debug_with(cx))
78                                )*
79                            .finish()
80                        }
81                    }
82                }
83
84                syn::Fields::Unnamed(fields) => {
85                    let all_names: Vec<syn::Ident> = vec![
86                        syn::parse_quote!(a),
87                        syn::parse_quote!(b),
88                        syn::parse_quote!(c),
89                        syn::parse_quote!(d),
90                    ];
91                    if fields.unnamed.len() > all_names.len() {
92                        unimplemented!("too many variants")
93                    }
94                    let names = &all_names[0..fields.unnamed.len()];
95                    quote! {
96                        #type_name :: #variant_name(#(#names),*) => {
97                            fmt.debug_tuple(stringify!(#variant_name))
98                                #(
99                                    .field(&#names.debug_with(cx))
100                                )*
101                            .finish()
102                        }
103                    }
104                }
105
106                syn::Fields::Unit => {
107                    quote! {
108                        #type_name :: #variant_name => {
109                            fmt.debug_struct(stringify!(#variant_name)).finish()
110                        }
111                    }
112                }
113            }
114        })
115        .collect();
116
117    quote! {
118        match self {
119            #(#variant_streams)*
120        }
121    }
122}
123
124fn debug_with_struct(type_name: &syn::Ident, data: &syn::DataStruct) -> TokenStream {
125    match &data.fields {
126        syn::Fields::Named(fields) => {
127            // Expands to an expression like
128            //
129            //     fmt.debug_struct("foo").field("a", self.a.debug_with(cx)).finish()
130            let fnames: &Vec<_> = &fields.named.iter().map(|f| &f.ident).collect();
131            let fnames1 = fnames;
132            quote! {
133                fmt.debug_struct(stringify!(#type_name))
134                #(
135                    .field(stringify!(#fnames), &self.#fnames1.debug_with(cx))
136                )*
137                .finish()
138            }
139        }
140        syn::Fields::Unnamed(fields) => {
141            // Expands to an expression like
142            //
143            //     fmt.debug_tuple("foo").field(self.0.debug_with(cx)).finish()
144            let indices = 0..fields.unnamed.len();
145            quote! {
146                fmt.debug_tuple(stringify!(#type_name))
147                #(
148                    .field(&self.#indices.debug_with(cx))
149                )*
150                .finish()
151            }
152        }
153        syn::Fields::Unit => {
154            quote! {
155                fmt.debug_struct(stringify!(#type_name))
156                .finish()
157            }
158        }
159    }
160}