custom_debug_derive/
lib.rs1use darling::FromMeta;
2use proc_macro2::TokenStream;
3use quote::quote;
4use syn::{Fields, Result};
5use synstructure::{decl_derive, AddBounds, BindingInfo, Structure, VariantInfo};
6
7use crate::field_attributes::{DebugFormat, FieldAttributes, SkipMode};
8use crate::result_into_stream_ext::ResultIntoStreamExt;
9use crate::retain_ext::RetainExt;
10
11mod field_attributes;
12mod result_into_stream_ext;
13mod retain_ext;
14#[cfg(test)]
15mod tests;
16
17decl_derive!([Debug, attributes(debug)] => custom_debug_derive);
18
19fn custom_debug_derive(mut structure: Structure) -> Result<TokenStream> {
20 filter_out_skipped_fields(&mut structure)?;
21
22 structure.add_bounds(AddBounds::Fields);
23
24 let match_arms =
25 structure.each_variant(|variant| generate_match_arm_body(variant).into_stream());
26
27 Ok(structure.gen_impl(quote! {
28 gen impl ::core::fmt::Debug for @Self {
29 fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
30 match self {
31 #match_arms
32 }
33 }
34 }
35 }))
36}
37
38fn filter_out_skipped_fields(structure: &mut Structure) -> Result<()> {
39 structure.try_retain(|binding| {
40 let field_attributes = parse_field_attributes(binding)?;
41
42 Ok(field_attributes.skip_mode != SkipMode::Always)
43 })?;
44
45 Ok(())
46}
47
48fn generate_match_arm_body(variant: &VariantInfo) -> Result<TokenStream> {
49 let name = variant.ast().ident.to_string();
50 let debug_builder = match variant.ast().fields {
51 Fields::Named(_) | Fields::Unit => quote! { debug_struct },
52 Fields::Unnamed(_) => quote! { debug_tuple },
53 };
54 let mut debug_builder_calls = Vec::new();
55
56 for binding in variant.bindings() {
57 let field_attributes = parse_field_attributes(binding)?;
58
59 let debug_builder_call = match &field_attributes.skip_mode {
60 SkipMode::Default => generate_debug_builder_call(binding, &field_attributes)?,
61 SkipMode::Condition(condition) => {
62 let debug_builder_call = generate_debug_builder_call(binding, &field_attributes)?;
63
64 quote! {
65 if (!#condition(#binding)) {
66 #debug_builder_call
67 }
68 }
69 }
70 SkipMode::Always => quote! {},
71 };
72
73 debug_builder_calls.push(debug_builder_call);
74 }
75
76 Ok(quote! {
77 let mut debug_builder = fmt.#debug_builder(#name);
78
79 #(#debug_builder_calls)*
80
81 debug_builder.finish()
82 })
83}
84
85fn generate_debug_builder_call(
86 binding: &BindingInfo,
87 field_attributes: &FieldAttributes,
88) -> Result<TokenStream> {
89 let format = generate_debug_impl(binding, &field_attributes.debug_format);
90
91 let debug_builder_call =
92 if let Some(ref name) = binding.ast().ident.as_ref().map(<_>::to_string) {
93 quote! {
94 debug_builder.field(#name, #format);
95 }
96 } else {
97 quote! {
98 debug_builder.field(#format);
99 }
100 };
101
102 Ok(debug_builder_call)
103}
104
105fn generate_debug_impl(binding: &BindingInfo, debug_format: &DebugFormat) -> TokenStream {
106 match debug_format {
107 DebugFormat::Default => quote! { #binding },
108 DebugFormat::Format(format) => quote! { &format_args!(#format, #binding) },
109 DebugFormat::With(with) => quote! {
110 {
111 struct DebugWith<'a, T: 'a> {
112 data: &'a T,
113 fmt: fn(&T, &mut ::core::fmt::Formatter) -> ::core::fmt::Result,
114 }
115
116 impl<'a, T: 'a> ::core::fmt::Debug for DebugWith<'a, T> {
117 fn fmt(&self, fmt: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
118 (self.fmt)(self.data, fmt)
119 }
120 }
121
122 &DebugWith {
123 data: #binding,
124 fmt: #with,
125 }
126 }
127 },
128 }
129}
130
131fn parse_field_attributes(binding: &BindingInfo<'_>) -> Result<FieldAttributes> {
132 let mut combined_field_attributes = FieldAttributes::default();
133
134 for attr in &binding.ast().attrs {
135 if !attr.path().is_ident("debug") {
136 continue;
137 }
138
139 let field_attributes = FieldAttributes::from_meta(&attr.meta)?;
140
141 combined_field_attributes = combined_field_attributes.try_combine(field_attributes)?;
142 }
143
144 Ok(combined_field_attributes)
145}