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
use proc_macro2::TokenStream;
use syn::{parse_str, Fields, Ident, Lit, Meta, NestedMeta, Path};
use synstructure::{decl_derive, quote, AddBounds, BindingInfo, Structure};

#[cfg(test)]
mod tests;

decl_derive!([CustomDebug, attributes(debug)] => custom_debug_derive);

fn custom_debug_derive(mut s: Structure) -> TokenStream {
    fn get_metas<'a>(b: &BindingInfo<'a>) -> impl Iterator<Item = NestedMeta> + 'a {
        let debug_attr = parse_str::<Path>("debug").unwrap();

        b.ast()
            .attrs
            .iter()
            .filter(move |attr| attr.path == debug_attr)
            .flat_map(|attr| attr.parse_meta())
            .flat_map(|meta| match meta {
                Meta::List(list) => list.nested,
                _ => panic!("Invalid debug attribute"),
            })
    };

    s.add_bounds(AddBounds::Fields);

    let skip_ident: Ident = parse_str("skip").unwrap();
    s.filter(|b| {
        for meta in get_metas(b) {
            if let NestedMeta::Meta(Meta::Path(ref path)) = meta {
                if path.get_ident().map(|i| i == &skip_ident).unwrap_or(false) {
                    return false;
                }
            }
        }
        true
    });

    let variants = s.each_variant(|variant| {
        let name = variant.ast().ident.to_string();
        let debug_helper = match variant.ast().fields {
            | Fields::Named(_)
            | Fields::Unit => quote! { debug_struct },
            | Fields::Unnamed(_) => quote! { debug_tuple },
        };

        let variant_body = variant.bindings().iter().map(|b| {
            let mut format = None;

            for meta in get_metas(b) {
                match meta {
                    NestedMeta::Meta(Meta::NameValue(nv)) => {
                        let value = nv.lit;
                        let ident = nv.path.get_ident().map(|i| i.to_string());
                        let ident_ref = ident.as_ref().map(|s| -> &str { &s });
                        format = Some(match ident_ref {
                            Some("format") => quote! { &format_args!(#value, #b) },
                            Some("with") => match value {
                                Lit::Str(fun) => {
                                    let fun = fun.parse::<Path>().unwrap();
                                    quote! {
                                        &{
                                            struct DebugWith<'a, T: 'a> {
                                                data: &'a T,
                                                fmt: fn(&T, &mut ::core::fmt::Formatter) -> ::core::fmt::Result,
                                            }

                                            impl<'a, T: 'a> ::core::fmt::Debug for DebugWith<'a, T> {
                                                fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
                                                    (self.fmt)(self.data, f)
                                                }
                                            }

                                            DebugWith {
                                                data: #b,
                                                fmt: #fun,
                                            }
                                        }
                                    }
                                },
                                _ => panic!("Invalid 'with' value"),
                            },
                            _ => panic!("Unknown key '{}'", quote!(nv.path).to_string()),
                        })
                    },
                    _ => panic!("Invalid debug attribute"),
                }
            }

            let format = format.unwrap_or_else(|| quote! { #b });

            if let Some(ref name) = b.ast().ident.as_ref().map(<_>::to_string) {
                quote! {
                    s.field(#name, #format);
                }
            } else {
                quote! {
                    s.field(#format);
                }
            }
        });

        quote! {
            let mut s = f.#debug_helper(#name);
            #(#variant_body)*
            s.finish()
        }
    });

    s.gen_impl(quote! {
        gen impl ::core::fmt::Debug for @Self {
            fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
                match self {
                    #variants
                }
            }
        }
    })
}