derive_debug_plus/
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, punctuated::Punctuated, spanned::Spanned, token::Comma, Attribute, DataEnum,
7    DataStruct, DeriveInput, Error, Expr, Fields, FieldsNamed, FieldsUnnamed, Ident, LitInt,
8    LitStr, Meta, Path, Token, Variant,
9};
10
11/// Derive macro generating an implementation of [`Debug`](std::fmt::Debug)
12/// with more customization options that the normal [`Debug`] derive macro.
13///
14/// For detailed documentation see the [`mod-level docs`](self)
15#[proc_macro_derive(Dbg, attributes(dbg))]
16pub fn derive_debug(target: proc_macro::TokenStream) -> proc_macro::TokenStream {
17    let item = parse_macro_input!(target as DeriveInput);
18    derive_debug_impl(item).into()
19}
20
21fn derive_debug_impl(item: DeriveInput) -> TokenStream {
22    let name = &item.ident;
23    let (impl_generics, type_generics, where_clause) = &item.generics.split_for_impl();
24
25    let options = match parse_options(&item.attrs, OptionsTarget::DeriveItem) {
26        Ok(options) => options,
27        Err(e) => return e.to_compile_error(),
28    };
29
30    let display_name = if let Some(alias) = options.alias {
31        alias
32    } else {
33        let alias = name.to_string();
34        syn::parse_quote_spanned! { name.span() => #alias }
35    };
36
37    let res = match &item.data {
38        syn::Data::Struct(data) => derive_struct(&display_name, data),
39        syn::Data::Enum(data) => derive_enum(data),
40        syn::Data::Union(data) => Err(syn::Error::new_spanned(
41            data.union_token,
42            "#[derive(Dbg)] not supported on unions",
43        )),
44    };
45
46    match res {
47        Ok(res) => quote! {
48            impl #impl_generics ::std::fmt::Debug for #name #type_generics #where_clause {
49                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
50                    #res
51                }
52            }
53        },
54        Err(e) => e.to_compile_error(),
55    }
56}
57
58fn derive_struct(display_name: &Expr, data: &DataStruct) -> Result<TokenStream, syn::Error> {
59    match &data.fields {
60        Fields::Named(fields) => {
61            let fields = derive_named_fields(fields, true)?;
62            Ok(quote! {
63                let mut fd = f.debug_struct(#display_name);
64                #fields
65                fd.finish()
66            })
67        }
68        Fields::Unnamed(fields) => {
69            let fields = derive_unnamed_fields(fields, true)?;
70            Ok(quote! {
71                let mut fd = f.debug_tuple(#display_name);
72                #fields
73                fd.finish()
74            })
75        }
76        Fields::Unit => Ok(quote! {
77            f.debug_struct(#display_name).finish()
78        }),
79    }
80}
81
82fn derive_enum(data: &DataEnum) -> Result<TokenStream, syn::Error> {
83    if data.variants.is_empty() {
84        return Ok(quote! {
85            unsafe { ::core::hint::unreachable_unchecked() }
86        });
87    }
88
89    let variants = derive_enum_variants(data.variants.iter())?;
90
91    Ok(quote! {
92        match self {
93            #variants
94        }
95    })
96}
97
98fn derive_enum_variants<'a>(
99    variants: impl Iterator<Item = &'a Variant>,
100) -> Result<TokenStream, syn::Error> {
101    let mut res = TokenStream::new();
102
103    for variant in variants {
104        let name = &variant.ident;
105
106        let options = parse_options(&variant.attrs, OptionsTarget::EnumVariant)?;
107
108        let display_name = if let Some(alias) = options.alias {
109            alias
110        } else {
111            let alias = name.to_string();
112            syn::parse_quote_spanned! { name.span() => #alias }
113        };
114
115        let derive_variant = match options.print_type {
116            FieldPrintType::Normal => derive_variant(name, &display_name, &variant.fields)?,
117            FieldPrintType::Skip => skip_variant(name, &display_name, &variant.fields)?,
118            _ => return Err(syn::Error::new_spanned(variant, "Internal error")),
119        };
120
121        res.extend(derive_variant);
122    }
123
124    Ok(res)
125}
126
127fn derive_variant(
128    name: &Ident,
129    display_name: &Expr,
130    fields: &Fields,
131) -> Result<TokenStream, syn::Error> {
132    let match_list = derive_match_list(fields)?;
133
134    match fields {
135        Fields::Named(fields) => {
136            let fields = derive_named_fields(fields, false)?;
137            Ok(quote! {
138                Self::#name #match_list => {
139                    let mut fd = f.debug_struct(#display_name);
140                    #fields
141                    fd.finish()
142                },
143            })
144        }
145        Fields::Unnamed(fields) => {
146            let fields = derive_unnamed_fields(fields, false)?;
147            Ok(quote! {
148                Self::#name #match_list => {
149                    let mut fd = f.debug_tuple(#display_name);
150                    #fields
151                    fd.finish()
152                },
153            })
154        }
155        Fields::Unit => Ok(quote! { Self::#name => write!(f, #display_name), }),
156    }
157}
158
159fn skip_variant(
160    name: &Ident,
161    display_name: &Expr,
162    fields: &Fields,
163) -> Result<TokenStream, syn::Error> {
164    match fields {
165        Fields::Named(_) => {
166            Ok(quote! { Self::#name{..} => f.debug_struct(#display_name).finish(), })
167        }
168        Fields::Unnamed(_) => {
169            Ok(quote! { Self::#name(..) => f.debug_tuple(#display_name).finish(), })
170        }
171        Fields::Unit => Ok(quote! { Self::#name => write!(f, #display_name), }),
172    }
173}
174
175fn derive_match_list(fields: &Fields) -> Result<TokenStream, syn::Error> {
176    match fields {
177        Fields::Named(fields) => {
178            let mut res = TokenStream::new();
179            for field in &fields.named {
180                let name = field.ident.as_ref().unwrap();
181                let options = parse_options(&field.attrs, OptionsTarget::NamedField)?;
182
183                match options.print_type {
184                    FieldPrintType::Skip => res.extend(quote! { #name: _, }),
185                    _ => res.extend(quote! { #name, }),
186                }
187            }
188            Ok(quote! { { #res } })
189        }
190        Fields::Unnamed(fields) => {
191            let mut res = TokenStream::new();
192            for (i, field) in fields.unnamed.iter().enumerate() {
193                let name = format_ident!("field_{}", i);
194                let options = parse_options(&field.attrs, OptionsTarget::UnnamedField)?;
195
196                match options.print_type {
197                    FieldPrintType::Skip => res.extend(quote! { _, }),
198                    _ => res.extend(quote! { #name, }),
199                }
200            }
201            Ok(quote! { (#res) })
202        }
203        Fields::Unit => Ok(quote! {}),
204    }
205}
206
207fn derive_named_fields(fields: &FieldsNamed, use_self: bool) -> Result<TokenStream, syn::Error> {
208    let mut res = TokenStream::new();
209
210    let mut _fields: Vec<_> = vec![];
211
212    for (i, field) in fields.named.iter().enumerate() {
213        let options = parse_options(&field.attrs, OptionsTarget::NamedField)?;
214
215        _fields.push((i, field, options));
216    }
217
218    _fields.sort_by_key(|(i, _, ref options)| (options.sort, *i));
219
220    for (_, field, options) in _fields.into_iter() {
221        if let FieldPrintType::Skip = options.print_type {
222            continue;
223        }
224
225        let name = field.ident.as_ref().unwrap();
226
227        let name_str = if let Some(alias) = options.alias {
228            alias
229        } else {
230            let alias = name.to_string();
231            syn::parse_quote_spanned! { name.span() => #alias }
232        };
233
234        let field_ref = if use_self {
235            quote! { &self.#name }
236        } else {
237            quote! { #name }
238        };
239
240        let alias_ident = format_ident!("_it");
241
242        if !matches!(
243            options.print_type,
244            FieldPrintType::Placeholder(_)
245        ) {
246            res.extend(quote! {
247                let #alias_ident = #field_ref;
248            });
249        }
250
251        let q = match options.print_type {
252            FieldPrintType::Normal => {
253                quote! { fd.field(#name_str, #alias_ident); }
254            }
255            FieldPrintType::Placeholder(placeholder) => {
256                quote! { fd.field(#name_str, &format_args!(#placeholder)); }
257            }
258            FieldPrintType::Format(fmt) => {
259                quote! { fd.field(#name_str, &format_args!(#fmt, #alias_ident)); }
260            }
261            FieldPrintType::Custom(formatter) => {
262                quote! { fd.field(#name_str, &format_args!("{}", #formatter(#alias_ident))); }
263            }
264            FieldPrintType::Expr(expr) => {
265                quote! { fd.field(#name_str, #expr); }
266            }
267            FieldPrintType::Skip => {
268                quote! {}
269            }
270        };
271
272        if options.flat_option {
273            res.extend(quote! { if let Some(#alias_ident) = #alias_ident { #q } });
274        } else {
275            res.extend(q)
276        }
277    }
278
279    Ok(res)
280}
281
282fn derive_unnamed_fields(
283    fields: &FieldsUnnamed,
284    use_self: bool,
285) -> Result<TokenStream, syn::Error> {
286    let mut res = TokenStream::new();
287
288    let mut _fields: Vec<_> = vec![];
289
290    for (i, field) in fields.unnamed.iter().enumerate() {
291        let options = parse_options(&field.attrs, OptionsTarget::UnnamedField)?;
292
293        _fields.push((i, field, options));
294    }
295
296    _fields.sort_by_key(|(i, _, ref options)| (options.sort, *i));
297
298    for (i, _field, options) in _fields.into_iter() {
299        if let FieldPrintType::Skip = options.print_type {
300            continue;
301        }
302
303        let field_ref = if use_self {
304            let index = syn::Index::from(i);
305            quote! { &self.#index }
306        } else {
307            format_ident!("field_{}", i).to_token_stream()
308        };
309
310        let alias_ident = format_ident!("_it");
311
312        if !matches!(
313            options.print_type,
314            FieldPrintType::Placeholder(_)
315        ) {
316            res.extend(quote! {
317                let #alias_ident = #field_ref;
318            });
319        }
320
321        let q = match options.print_type {
322            FieldPrintType::Normal => {
323                quote! { fd.field(#alias_ident); }
324            }
325            FieldPrintType::Placeholder(placeholder) => {
326                quote! { fd.field(&format_args!(#placeholder)); }
327            }
328            FieldPrintType::Format(fmt) => {
329                quote! { fd.field(&format_args!(#fmt, #alias_ident)); }
330            }
331            FieldPrintType::Custom(formatter) => {
332                quote! { fd.field(&format_args!("{}", #formatter(#alias_ident))); }
333            }
334            FieldPrintType::Expr(expr) => {
335                quote! { fd.field(#expr); }
336            }
337            FieldPrintType::Skip => {
338                quote! {}
339            }
340        };
341
342        if options.flat_option {
343            res.extend(quote! { if let Some(#alias_ident) = #alias_ident { #q } });
344        } else {
345            res.extend(q)
346        }
347    }
348
349    Ok(res)
350}
351
352enum FieldPrintType {
353    Normal,
354    Placeholder(String),
355    Skip,
356    Format(LitStr),
357    Custom(Path),
358    Expr(Expr),
359}
360
361struct FieldOutputOptions {
362    print_type: FieldPrintType,
363    alias: Option<Expr>,
364    flat_option: bool,
365    sort: isize,
366}
367
368#[derive(PartialEq, Eq)]
369enum OptionsTarget {
370    DeriveItem,
371    EnumVariant,
372    NamedField,
373    UnnamedField,
374}
375
376fn parse_options(
377    attributes: &[Attribute],
378    target: OptionsTarget,
379) -> Result<FieldOutputOptions, syn::Error> {
380    let mut res = FieldOutputOptions {
381        print_type: FieldPrintType::Normal,
382        alias: None,
383        flat_option: false,
384        sort: 0,
385    };
386
387    for attrib in attributes {
388        let meta = &attrib.meta;
389
390        if !meta.path().is_ident("dbg") {
391            continue;
392        }
393
394        let meta = if let Meta::List(m) = meta {
395            m
396        } else {
397            return Err(syn::Error::new_spanned(
398                meta,
399                "invalid #[dbg(...)] attribute",
400            ));
401        };
402
403        let options: OptionItems = syn::parse2(meta.tokens.clone())?;
404
405        for option in options.options {
406            match option.option {
407                TheOption::Sort(sort) if target != OptionsTarget::DeriveItem => {
408                    res.sort = sort.base10_parse()?
409                }
410                TheOption::Skip if target != OptionsTarget::DeriveItem => {
411                    res.print_type = FieldPrintType::Skip
412                }
413                TheOption::FlatOption if target != OptionsTarget::DeriveItem => {
414                    res.flat_option = true
415                }
416                TheOption::Alias(alias) if target != OptionsTarget::UnnamedField => {
417                    res.alias = Some(alias)
418                }
419                TheOption::Placeholder(placeholder)
420                    if target == OptionsTarget::NamedField
421                        || target == OptionsTarget::UnnamedField =>
422                {
423                    res.print_type = FieldPrintType::Placeholder(placeholder.value())
424                }
425                TheOption::Fmt(fmt)
426                    if target == OptionsTarget::NamedField
427                        || target == OptionsTarget::UnnamedField =>
428                {
429                    res.print_type = FieldPrintType::Format(fmt)
430                }
431                TheOption::Expr(expr) => res.print_type = FieldPrintType::Expr(expr),
432                TheOption::Formatter(path)
433                    if target == OptionsTarget::NamedField
434                        || target == OptionsTarget::UnnamedField =>
435                {
436                    res.print_type = FieldPrintType::Custom(path)
437                }
438                _ => return Err(syn::Error::new_spanned(option.path, "invalid option")),
439            }
440        }
441    }
442
443    Ok(res)
444}
445
446struct OptionItems {
447    pub options: Punctuated<OptionItem, Comma>,
448}
449
450impl syn::parse::Parse for OptionItems {
451    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
452        let options = Punctuated::<OptionItem, Comma>::parse_separated_nonempty(input)?;
453        Ok(Self { options })
454    }
455}
456
457struct OptionItem {
458    pub path: Path,
459    pub option: TheOption,
460}
461
462enum TheOption {
463    Skip,
464    FlatOption,
465    Alias(Expr),
466    Placeholder(LitStr),
467    Fmt(LitStr),
468    Expr(Expr),
469    Formatter(Path),
470    Sort(LitInt),
471}
472
473impl syn::parse::Parse for OptionItem {
474    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
475        let path: Path = input.parse()?;
476
477        if path.is_ident("skip") {
478            return Ok(Self {
479                path,
480                option: TheOption::Skip,
481            });
482        }
483        if path.is_ident("flat_option") {
484            return Ok(Self {
485                path,
486                option: TheOption::FlatOption,
487            });
488        }
489        if path.is_ident("alias") {
490            let _ = input.parse::<Token![=]>()?;
491            let a = input.parse()?;
492            return Ok(Self {
493                path,
494                option: TheOption::Alias(a),
495            });
496        }
497        if path.is_ident("placeholder") {
498            let _ = input.parse::<Token![=]>()?;
499            let a = input.parse()?;
500            return Ok(Self {
501                path,
502                option: TheOption::Placeholder(a),
503            });
504        }
505        if path.is_ident("fmt") {
506            let _ = input.parse::<Token![=]>()?;
507            let a = input.parse()?;
508            return Ok(Self {
509                path,
510                option: TheOption::Fmt(a),
511            });
512        }
513        if path.is_ident("expr") {
514            let _ = input.parse::<Token![=]>()?;
515            let a = input.parse()?;
516            return Ok(Self {
517                path,
518                option: TheOption::Expr(a),
519            });
520        }
521        if path.is_ident("formatter") {
522            let _ = input.parse::<Token![=]>()?;
523            let a = input.parse()?;
524            return Ok(Self {
525                path,
526                option: TheOption::Formatter(a),
527            });
528        }
529        if path.is_ident("sort") {
530            let _ = input.parse::<Token![=]>()?;
531            let a = input.parse()?;
532            return Ok(Self {
533                path,
534                option: TheOption::Sort(a),
535            });
536        }
537
538        Err(Error::new(path.span(), "invalid option"))
539    }
540}