derive_debug_extras/
lib.rs1use 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 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 {}