shallow_debug/
lib.rs

1//! # Shallow debug
2//!
3//! A crate that allows any type to derive a very simple and "shallow" debug impl. The impl will
4//! only print the enum variant, but not the content of the variant. For structs, it will only
5//! print the struct's name, and none of it's field's values.
6//!
7//! This is mainly useful for enums when the variant is already useful information. Since none of
8//! the inner values are printed, they don't have to implement `Debug`, so this can also be useful
9//! in highly generic code where you just want a quick and simple way to get debug information.
10//!
11//! ## Example
12//!
13//! ```rust
14//! # use shallow_debug::ShallowDebug;
15//! #[derive(ShallowDebug)]
16//! enum MyEnum<A, B, C> {
17//!     A(A),
18//!     B(B),
19//!     C(C),
20//! }
21//!
22//! let value: MyEnum<i32, &str, usize> = MyEnum::A(123);
23//! assert_eq!(format!("{value:?}"), "MyEnum::A(..)");
24//! ```
25
26use syn::{Data, Fields, GenericParam};
27use quote::{quote, ToTokens};
28
29/// A derive macro that is able to implement `Debug` for any type, without requiring it's inner
30/// types to also implement the `Debug` trait. In order to do this, the `Debug` impl that is
31/// generated is "shallow", meaning it will only print the enum variant names, but not their
32/// internal values. You can also `#[derive(ShallowDebug)]` for structs and unions, but it will not
33/// print the field values. In general this is more useful for enums, since the variant can
34/// already tell you useful information.
35#[proc_macro_derive(ShallowDebug)]
36pub fn derive_shallow_debug(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
37    let input = syn::parse_macro_input!(stream as syn::DeriveInput);
38
39    let ident = &input.ident;
40    let fmt_body = match &input.data {
41        Data::Enum(data_enum) => {
42            let variants = data_enum.variants.iter()
43                .map(|variant| {
44                    let variant_ident = &variant.ident;
45                    match &variant.fields {
46                        Fields::Named(_) => {
47                            let fmt = format!("{ident}::{variant_ident}{{{{..}}}}");
48                            quote!(#ident::#variant_ident{..} => write!(f, #fmt))
49                        }
50                        Fields::Unnamed(_) => {
51                            let fmt = format!("{ident}::{variant_ident}(..)");
52                            quote!(#ident::#variant_ident(..) => write!(f, #fmt))
53                        }
54                        Fields::Unit => {
55                            let fmt = format!("{ident}::{variant_ident}");
56                            quote!(#ident::#variant_ident => write!(f, #fmt))
57                        }
58                    }
59                });
60
61            quote! {
62                match self {
63                    #(#variants,)*
64                }
65            }
66        }
67        Data::Struct(data_struct) => match &data_struct.fields {
68            Fields::Named(_) => {
69                let fmt = format!("{ident}{{{{..}}}}");
70                quote!(write!(f, #fmt))
71            }
72            Fields::Unnamed(_) => {
73                let fmt = format!("{ident}(..)");
74                quote!(write!(f, #fmt))
75            }
76            Fields::Unit => {
77                let fmt = format!("{ident}");
78                quote!(write!(f, #fmt))
79            }
80        }
81
82        Data::Union(_) => {
83            let fmt = format!("{ident}");
84            quote!(write!(f, #fmt))
85        }
86    };
87
88    let bounds = input.generics.params.iter()
89        .filter_map(|param| match param {
90            GenericParam::Lifetime(lifetime) if lifetime.bounds.is_empty() => None,
91            GenericParam::Lifetime(lifetime) => {
92                let bounds = &lifetime.bounds;
93                let ident = &lifetime.lifetime;
94                Some(quote!(#ident: #bounds))
95            }
96            GenericParam::Type(ty) if ty.bounds.is_empty() => None,
97            GenericParam::Type(ty) => {
98                let bounds = &ty.bounds;
99                let ident = &ty.ident;
100                Some(quote!(#ident: #bounds))
101            }
102            GenericParam::Const(_) => None,
103        })
104        .chain({
105            input.generics.where_clause.iter()
106                .flat_map(|clause| clause.predicates.iter().map(ToTokens::to_token_stream))
107        });
108
109    let ty_vars = input.generics.params.iter()
110        .map(|param| match param {
111            GenericParam::Lifetime(lifetime) => lifetime.lifetime.to_token_stream(),
112            GenericParam::Type(ty) => {
113                let ident = &ty.ident;
114                if let Some(default) = &ty.default {
115                    quote!(#ident = #default)
116                } else {
117                    ident.to_token_stream()
118                }
119            }
120            GenericParam::Const(cons) => cons.to_token_stream(),
121        })
122        .collect::<Vec<_>>();
123
124    // The `impl Debug for <type> where ...` part
125    let impl_debug = if ty_vars.is_empty() {
126        quote! {
127            impl std::fmt::Debug for #ident
128        }
129    } else {
130        quote! {
131            impl<#(#ty_vars),*> std::fmt::Debug for #ident<#(#ty_vars),*>
132            where
133                #(#bounds),*
134        }
135    };
136
137    quote! {
138        #impl_debug {
139            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140                #fmt_body
141            }
142        }
143    }.into()
144}
145