derive_debug/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote, ToTokens};
5use syn::{
6    parse_macro_input, Attribute, DataEnum, DataStruct, DeriveInput, Fields,
7    FieldsNamed, FieldsUnnamed, Ident, Lit, LitStr, Meta, MetaNameValue, NestedMeta, Path, Variant,
8};
9
10/// Derive macro generating an implementation of [`Debug`](std::fmt::Debug)
11/// with more customization options that the normal [`Debug`] derive macro.
12///
13/// For detailed documentation see the [`mod-level docs`](self)
14#[proc_macro_derive(Dbg, attributes(dbg))]
15pub fn derive_debug(target: proc_macro::TokenStream) -> proc_macro::TokenStream {
16    let item = parse_macro_input!(target as DeriveInput);
17    derive_debug_impl(item).into()
18}
19
20fn derive_debug_impl(item: DeriveInput) -> TokenStream {
21    let name = &item.ident;
22    let (impl_generics, type_generics, where_clause) = &item.generics.split_for_impl();
23
24    let options = match parse_options(&item.attrs, OptionsTarget::DeriveItem) {
25        Ok(options) => options,
26        Err(e) => return e.to_compile_error(),
27    };
28
29    let display_name = if let Some(alias) = options.alias {
30        alias
31    } else {
32        name.to_string()
33    };
34
35    let res = match &item.data {
36        syn::Data::Struct(data) => derive_struct(&display_name, data),
37        syn::Data::Enum(data) => derive_enum(data),
38        syn::Data::Union(data) => Err(syn::Error::new_spanned(
39            data.union_token,
40            "#[derive(Dbg)] not supported on unions",
41        )),
42    };
43
44    match res {
45        Ok(res) => quote! {
46            impl #impl_generics ::std::fmt::Debug for #name #type_generics #where_clause {
47                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
48                    #res
49                }
50            }
51        },
52        Err(e) => e.to_compile_error(),
53    }
54}
55
56fn derive_struct(display_name: &str, data: &DataStruct) -> Result<TokenStream, syn::Error> {
57    match &data.fields {
58        Fields::Named(fields) => {
59            let fields = derive_named_fields(fields, true)?;
60            Ok(quote! {
61                f.debug_struct(#display_name)
62                    #fields
63                    .finish()
64            })
65        }
66        Fields::Unnamed(fields) => {
67            let fields = derive_unnamed_fields(fields, true)?;
68            Ok(quote! {
69                f.debug_tuple(#display_name)
70                    #fields
71                    .finish()
72            })
73        }
74        Fields::Unit => Ok(quote! {
75            f.debug_struct(#display_name).finish()
76        }),
77    }
78}
79
80fn derive_enum(data: &DataEnum) -> Result<TokenStream, syn::Error> {
81    if data.variants.is_empty() {
82        return Ok(quote! {
83            unsafe { ::core::hint::unreachable_unchecked() }
84        });
85    }
86
87    let variants = derive_enum_variants(data.variants.iter())?;
88
89    Ok(quote! {
90        match self {
91            #variants
92        }
93    })
94}
95
96fn derive_enum_variants<'a>(
97    variants: impl Iterator<Item = &'a Variant>,
98) -> Result<TokenStream, syn::Error> {
99    let mut res = TokenStream::new();
100
101    for variant in variants {
102        let name = &variant.ident;
103
104        let options = parse_options(&variant.attrs, OptionsTarget::EnumVariant)?;
105
106        let display_name = if let Some(alias) = options.alias {
107            alias
108        } else {
109            name.to_string()
110        };
111
112        let derive_variant = match options.print_type {
113            FieldPrintType::Normal => derive_variant(name, &display_name, &variant.fields)?,
114            FieldPrintType::Skip => skip_variant(name, &display_name, &variant.fields)?,
115            _ => return Err(syn::Error::new_spanned(variant, "Internal error")),
116        };
117
118        res.extend(derive_variant);
119    }
120
121    Ok(res)
122}
123
124fn derive_variant(
125    name: &Ident,
126    display_name: &str,
127    fields: &Fields,
128) -> Result<TokenStream, syn::Error> {
129    let match_list = derive_match_list(fields)?;
130
131    match fields {
132        Fields::Named(fields) => {
133            let fields = derive_named_fields(fields, false)?;
134            Ok(quote! {
135                Self::#name #match_list => f.debug_struct(#display_name) #fields .finish(),
136            })
137        }
138        Fields::Unnamed(fields) => {
139            let fields = derive_unnamed_fields(fields, false)?;
140            Ok(quote! {
141                Self::#name #match_list => f.debug_tuple(#display_name) #fields .finish(),
142            })
143        }
144        Fields::Unit => Ok(quote! { Self::#name => write!(f, #display_name), }),
145    }
146}
147
148fn skip_variant(
149    name: &Ident,
150    display_name: &str,
151    fields: &Fields,
152) -> Result<TokenStream, syn::Error> {
153    match fields {
154        Fields::Named(_) => {
155            Ok(quote! { Self::#name{..} => f.debug_struct(#display_name).finish(), })
156        }
157        Fields::Unnamed(_) => {
158            Ok(quote! { Self::#name(..) => f.debug_tuple(#display_name).finish(), })
159        }
160        Fields::Unit => Ok(quote! { Self::#name => write!(f, #display_name), }),
161    }
162}
163
164fn derive_match_list(fields: &Fields) -> Result<TokenStream, syn::Error> {
165    match fields {
166        Fields::Named(fields) => {
167            let mut res = TokenStream::new();
168            for field in &fields.named {
169                let name = field.ident.as_ref().unwrap();
170                let options = parse_options(&field.attrs, OptionsTarget::NamedField)?;
171
172                match options.print_type {
173                    FieldPrintType::Skip => res.extend(quote! { #name: _, }),
174                    _ => res.extend(quote! { #name, }),
175                }
176            }
177            Ok(quote! { { #res } })
178        }
179        Fields::Unnamed(fields) => {
180            let mut res = TokenStream::new();
181            for (i, field) in fields.unnamed.iter().enumerate() {
182                let name = format_ident!("field_{}", i);
183                let options = parse_options(&field.attrs, OptionsTarget::UnnamedField)?;
184
185                match options.print_type {
186                    FieldPrintType::Skip => res.extend(quote! { _, }),
187                    _ => res.extend(quote! { #name, }),
188                }
189            }
190            Ok(quote! { (#res) })
191        }
192        Fields::Unit => Ok(quote! {}),
193    }
194}
195
196fn derive_named_fields(fields: &FieldsNamed, use_self: bool) -> Result<TokenStream, syn::Error> {
197    let mut res = TokenStream::new();
198
199    for field in &fields.named {
200        let name = field.ident.as_ref().unwrap();
201
202        let options = parse_options(&field.attrs, OptionsTarget::NamedField)?;
203
204        let name_str = if let Some(alias) = options.alias {
205            alias
206        } else {
207            name.to_string()
208        };
209
210        match options.print_type {
211            FieldPrintType::Normal => {
212                let field_ref = if use_self {
213                    quote! { &self.#name }
214                } else {
215                    quote! { #name }
216                };
217                res.extend(quote! { .field(#name_str, #field_ref) });
218            }
219            FieldPrintType::Placeholder(placeholder) => {
220                res.extend(quote! { .field(#name_str, &format_args!(#placeholder)) })
221            }
222            FieldPrintType::Format(fmt) => {
223                let field_ref = if use_self {
224                    quote! { self.#name }
225                } else {
226                    quote! { #name }
227                };
228                res.extend(quote! { .field(#name_str, &format_args!(#fmt, #field_ref)) })
229            }
230            FieldPrintType::Custom(formatter) => {
231                let field_ref = if use_self {
232                    quote! { &self.#name }
233                } else {
234                    quote! { #name }
235                };
236                res.extend(quote! { .field(#name_str, &format_args!("{}", #formatter(#field_ref))) })
237            }
238            FieldPrintType::Skip => {}
239        }
240    }
241
242    Ok(res)
243}
244
245fn derive_unnamed_fields(
246    fields: &FieldsUnnamed,
247    use_self: bool,
248) -> Result<TokenStream, syn::Error> {
249    let mut res = TokenStream::new();
250
251    for (i, field) in fields.unnamed.iter().enumerate() {
252        let options = parse_options(&field.attrs, OptionsTarget::UnnamedField)?;
253
254        match options.print_type {
255            FieldPrintType::Normal => {
256                let field_ref = if use_self {
257                    let index = syn::Index::from(i);
258                    quote! { &self.#index }
259                } else {
260                    format_ident!("field_{}", i).to_token_stream()
261                };
262                res.extend(quote! { .field(#field_ref) });
263            }
264            FieldPrintType::Placeholder(placeholder) => {
265                res.extend(quote! { .field(&format_args!(#placeholder)) })
266            }
267            FieldPrintType::Format(fmt) => {
268                let field_ref = if use_self {
269                    let index = syn::Index::from(i);
270                    quote! { self.#index }
271                } else {
272                    format_ident!("field_{}", i).to_token_stream()
273                };
274                res.extend(quote! { .field(&format_args!(#fmt, #field_ref)) })
275            }
276            FieldPrintType::Custom(formatter) => {
277                let field_ref = if use_self {
278                    let index = syn::Index::from(i);
279                    quote! { &self.#index }
280                } else {
281                    format_ident!("field_{}", i).to_token_stream()
282                };
283                res.extend(quote! { .field(&format_args!("{}", #formatter(#field_ref))) });
284            }
285            FieldPrintType::Skip => {}
286        }
287    }
288
289    Ok(res)
290}
291
292enum FieldPrintType {
293    Normal,
294    Placeholder(String),
295    Skip,
296    Format(LitStr),
297    Custom(Path),
298}
299
300struct FieldOutputOptions {
301    print_type: FieldPrintType,
302    alias: Option<String>,
303}
304
305#[derive(PartialEq, Eq)]
306enum OptionsTarget {
307    DeriveItem,
308    EnumVariant,
309    NamedField,
310    UnnamedField,
311}
312
313fn parse_options(
314    attributes: &[Attribute],
315    target: OptionsTarget,
316) -> Result<FieldOutputOptions, syn::Error> {
317    let mut res = FieldOutputOptions {
318        print_type: FieldPrintType::Normal,
319        alias: None,
320    };
321
322    for attrib in attributes {
323        if !attrib.path.is_ident("dbg") {
324            continue;
325        }
326
327        let meta = attrib.parse_meta()?;
328        let meta = if let Meta::List(m) = meta {
329            m
330        } else {
331            return Err(syn::Error::new_spanned(
332                meta,
333                "invalid #[dbg(...)] attribute",
334            ));
335        };
336
337        for option in meta.nested {
338            match option {
339                NestedMeta::Meta(Meta::Path(option))
340                    if option.is_ident("skip") && target != OptionsTarget::DeriveItem =>
341                {
342                    res.print_type = FieldPrintType::Skip
343                }
344                NestedMeta::Meta(Meta::NameValue(MetaNameValue {
345                    path,
346                    lit: Lit::Str(placeholder),
347                    ..
348                })) if path.is_ident("placeholder")
349                    && (target == OptionsTarget::NamedField
350                        || target == OptionsTarget::UnnamedField) =>
351                {
352                    res.print_type = FieldPrintType::Placeholder(placeholder.value())
353                }
354                NestedMeta::Meta(Meta::NameValue(MetaNameValue {
355                    path,
356                    lit: Lit::Str(alias),
357                    ..
358                })) if path.is_ident("alias") && target != OptionsTarget::UnnamedField => {
359                    res.alias = Some(alias.value())
360                }
361                NestedMeta::Meta(Meta::NameValue(MetaNameValue {
362                    path,
363                    lit: Lit::Str(fmt),
364                    ..
365                })) if path.is_ident("fmt")
366                    && (target == OptionsTarget::NamedField
367                        || target == OptionsTarget::UnnamedField) =>
368                {
369                    res.print_type = FieldPrintType::Format(fmt)
370                }
371                NestedMeta::Meta(Meta::NameValue(MetaNameValue {
372                    path,
373                    lit: Lit::Str(custom),
374                    ..
375                })) if path.is_ident("formatter")
376                    && (target == OptionsTarget::NamedField
377                        || target == OptionsTarget::UnnamedField) =>
378                {
379                    let path = syn::parse_str::<Path>(&custom.value()).map_err(|e| syn::Error::new(custom.span(), e.to_string()))?;
380                    res.print_type = FieldPrintType::Custom(path);
381                }
382                _ => return Err(syn::Error::new_spanned(option, "invalid option")),
383            }
384        }
385    }
386
387    Ok(res)
388}