derive_debug_extras/
lib.rs

1use proc_macro::TokenStream;
2use std::{error::Error, fmt::Display};
3
4use syn_helpers::{
5    derive_trait, path_to_string,
6    proc_macro2::{Ident, Span},
7    syn::{parse_macro_input, parse_quote, DeriveInput, Expr, LitStr, Stmt},
8    Constructable, FieldMut, Fields, NamedField, NamedOrUnnamedField, Trait, TraitItem,
9};
10
11const IGNORE_DEBUG: &str = "debug_ignore";
12const SINGLE_TUPLE_INLINE: &str = "debug_single_tuple_inline";
13const DEBUG_AS_DISPLAY: &str = "debug_as_display";
14
15#[proc_macro_derive(
16    DebugExtras,
17    attributes(debug_single_tuple_inline, debug_as_display, debug_ignore)
18)]
19pub fn debug_extras(input: TokenStream) -> TokenStream {
20    let input = parse_macro_input!(input as DeriveInput);
21
22    let fmt_item = TraitItem::new_method(
23        Ident::new("fmt", Span::call_site()),
24        None,
25        syn_helpers::TypeOfSelf::Reference,
26        vec![parse_quote!(f: &mut ::std::fmt::Formatter<'_>)],
27        Some(parse_quote!(::std::fmt::Result)),
28        |mut item| {
29            item.map_constructable(|mut constructable| {
30                let full_path_name_string = path_to_string(constructable.get_constructor_path());
31                let name = LitStr::new(&full_path_name_string, Span::call_site());
32
33                let inline_tuple_attribute = constructable
34                    .all_attributes()
35                    .any(|attr| attr.path().is_ident(SINGLE_TUPLE_INLINE));
36
37                let fields = &mut constructable.get_fields_mut();
38
39                let auto_debug_tuple_inline = cfg!(feature = "auto-debug-single-tuple-inline")
40                    && fields.fields_iterator().len() == 1
41                    && matches!(
42                        fields.fields_iterator().next().unwrap(),
43                        NamedOrUnnamedField::Unnamed(..)
44                    );
45
46                let debug_single_tuple_inline = inline_tuple_attribute || auto_debug_tuple_inline;
47
48                let expr: Expr = if debug_single_tuple_inline {
49                    if let Fields::Unnamed(fields, _) = fields
50                    {
51                        if let [field] = fields.as_mut_slice() {
52                            let read_expr = field.get_reference();
53                            let formatting_pattern = LitStr::new(
54                                &format!("{}({{:?}})", full_path_name_string),
55                                Span::call_site(),
56                            );
57                            parse_quote! { f.write_fmt(format_args!(#formatting_pattern, #read_expr)) }
58                        } else {
59                            return Err(Box::new(
60                                DebugExtrasErrors::DebugSingleTupleInlineInvalidStructure,
61                            ));
62                        }
63                    } else {
64                        return Err(Box::new(
65                            DebugExtrasErrors::DebugSingleTupleInlineInvalidStructure,
66                        ));
67                    }
68                } else {
69                    let builder: Expr = match fields {
70                        Fields::Named(fields, _) => {
71                            let mut top = parse_quote! {
72                                f.debug_struct(#name)
73                            };
74                            for field in fields.iter_mut() {
75                                let mut expr = field.get_reference();
76                                let NamedField { attrs, name, .. } = &field;
77                                if attrs.iter().any(|attr| attr.path().is_ident(IGNORE_DEBUG)) {
78                                    continue;
79                                }
80                                if attrs
81                                    .iter()
82                                    .any(|attr| attr.path().is_ident(DEBUG_AS_DISPLAY))
83                                {
84                                    expr = parse_quote! { format_args!("{}", &#expr) };
85                                }
86
87                                let field_name = LitStr::new(&name.to_string(), Span::call_site());
88                                top = parse_quote! { #top.field(#field_name, &#expr) }
89                            }
90                            top
91                        }
92                        Fields::Unnamed(fields, _) => {
93                            let mut top = parse_quote! {
94                                f.debug_tuple(#name)
95                            };
96                            for field in fields.iter_mut() {
97                                let mut expr = field.get_reference();
98                                if field
99                                    .attrs
100                                    .iter()
101                                    .any(|attr| attr.path().is_ident(IGNORE_DEBUG))
102                                {
103                                    continue;
104                                }
105                                if field
106                                    .attrs
107                                    .iter()
108                                    .any(|attr| attr.path().is_ident(DEBUG_AS_DISPLAY))
109                                {
110                                    expr = parse_quote! { format_args!("{}", &#expr) };
111                                }
112                                top = parse_quote! { #top.field(&#expr) }
113                            }
114                            top
115                        }
116                        Fields::Unit(..) => {
117                            parse_quote! {
118                                f.debug_struct(#name)
119                            }
120                        }
121                    };
122                    parse_quote! {
123                        #builder.finish()
124                    }
125                };
126                Ok(vec![Stmt::Expr(expr, None)])
127            })
128        },
129    );
130
131    // Debug trait
132    let debug_trait = Trait {
133        name: parse_quote!(::std::fmt::Debug),
134        generic_parameters: None,
135        items: vec![fmt_item],
136    };
137
138    derive_trait(input, debug_trait).into()
139}
140
141#[derive(Debug)]
142enum DebugExtrasErrors {
143    DebugSingleTupleInlineInvalidStructure,
144}
145
146impl Display for DebugExtrasErrors {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        match self {
149            DebugExtrasErrors::DebugSingleTupleInlineInvalidStructure => {
150                f.write_str("Must be tuple struct with one item")
151            }
152        }
153    }
154}
155
156impl Error for DebugExtrasErrors {}