educe/trait_handlers/debug/
debug_struct.rs1use std::str::FromStr;
2
3use proc_macro2::TokenStream;
4use quote::{quote, ToTokens};
5use syn::{Data, DeriveInput, Fields, Generics, Meta};
6
7use super::{
8 super::TraitHandler,
9 models::{FieldAttributeBuilder, FieldAttributeName, TypeAttributeBuilder, TypeAttributeName},
10};
11use crate::{panic, Trait};
12
13pub struct DebugStructHandler;
14
15impl TraitHandler for DebugStructHandler {
16 fn trait_meta_handler(
17 ast: &DeriveInput,
18 tokens: &mut TokenStream,
19 traits: &[Trait],
20 meta: &Meta,
21 ) {
22 let is_tuple = {
23 if let Data::Struct(data) = &ast.data {
24 matches!(data.fields, Fields::Unnamed(_))
25 } else {
26 true
27 }
28 };
29
30 let type_attribute = TypeAttributeBuilder {
31 enable_flag: true,
32 name: TypeAttributeName::Default,
33 enable_name: true,
34 named_field: !is_tuple,
35 enable_named_field: true,
36 enable_bound: true,
37 }
38 .from_debug_meta(meta);
39
40 let name = type_attribute.name.into_string_by_ident(&ast.ident);
41
42 let named_field = type_attribute.named_field;
43
44 let bound = type_attribute
45 .bound
46 .into_punctuated_where_predicates_by_generic_parameters(&ast.generics.params);
47
48 let mut builder_tokens = TokenStream::new();
49 let mut has_fields = false;
50
51 if named_field {
52 if name.is_empty() {
53 builder_tokens.extend(quote!(
54 struct RawString(&'static str);
55
56 impl core::fmt::Debug for RawString {
57 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
58 f.write_str(self.0)
59 }
60 }
61 ));
62 builder_tokens.extend(quote!(let mut builder = formatter.debug_map();));
63 } else {
64 builder_tokens.extend(quote!(let mut builder = formatter.debug_struct(#name);));
65 }
66
67 if let Data::Struct(data) = &ast.data {
68 for (index, field) in data.fields.iter().enumerate() {
69 let field_attribute = FieldAttributeBuilder {
70 name: FieldAttributeName::Default,
71 enable_name: true,
72 enable_ignore: true,
73 enable_impl: true,
74 }
75 .from_attributes(&field.attrs, traits);
76
77 if field_attribute.ignore {
78 continue;
79 }
80
81 let rename = field_attribute.name.into_option_string();
82
83 let format_trait = field_attribute.format_trait;
84 let format_method = field_attribute.format_method;
85
86 let (key, field_name) = match rename {
87 Some(rename) => {
88 if let Some(ident) = field.ident.as_ref() {
89 (rename, ident.to_string())
90 } else {
91 (rename, format!("{}", index))
92 }
93 },
94 None => {
95 if let Some(ident) = field.ident.as_ref() {
96 (ident.to_string(), ident.to_string())
97 } else {
98 (format!("_{}", index), format!("{}", index))
99 }
100 },
101 };
102
103 match format_trait {
104 Some(format_trait) => {
105 let format_method = format_method.unwrap();
106
107 builder_tokens.extend(
108 TokenStream::from_str(&format!(
109 "
110 let arg = {{
111 struct MyDebug<'a, T: {format_trait}>(&'a T);
112
113 impl<'a, T: {format_trait}> core::fmt::Debug for MyDebug<'a, \
114 T> {{
115 fn fmt(&self, formatter: &mut core::fmt::Formatter) -> \
116 core::fmt::Result {{
117 {format_trait}::{format_method}(self.0, formatter)
118 }}
119 }}
120
121 MyDebug(&self.{field_name})
122 }};
123 ",
124 format_trait = format_trait,
125 format_method = format_method,
126 field_name = field_name
127 ))
128 .unwrap(),
129 );
130
131 let statement = if name.is_empty() {
132 format!("builder.entry(&RawString({key:?}), &arg);", key = key)
133 } else {
134 format!("builder.field({key:?}, &arg);", key = key)
135 };
136
137 builder_tokens.extend(TokenStream::from_str(&statement).unwrap());
138 },
139 None => match format_method {
140 Some(format_method) => {
141 let ty = field.ty.clone().into_token_stream().to_string();
142
143 builder_tokens.extend(
144 TokenStream::from_str(&format!(
145 "
146 let arg = {{
147 struct MyDebug<'a>(&'a {ty});
148
149 impl<'a> core::fmt::Debug for MyDebug<'a> {{
150 fn fmt(&self, formatter: &mut \
151 core::fmt::Formatter) -> core::fmt::Result {{
152 {format_method}(self.0, formatter)
153 }}
154 }}
155
156 MyDebug(&self.{field_name})
157 }};
158 ",
159 ty = ty,
160 format_method = format_method,
161 field_name = field_name
162 ))
163 .unwrap(),
164 );
165
166 let statement = if name.is_empty() {
167 format!("builder.entry(&RawString({key:?}), &arg);", key = key)
168 } else {
169 format!("builder.field({key:?}, &arg);", key = key)
170 };
171
172 builder_tokens.extend(TokenStream::from_str(&statement).unwrap());
173 },
174 None => {
175 let statement = if name.is_empty() {
176 format!(
177 "builder.entry(&RawString({key:?}), &self.{field_name});",
178 key = key,
179 field_name = field_name
180 )
181 } else {
182 format!(
183 "builder.field({key:?}, &self.{field_name});",
184 key = key,
185 field_name = field_name
186 )
187 };
188
189 builder_tokens.extend(TokenStream::from_str(&statement).unwrap());
190 },
191 },
192 }
193
194 has_fields = true;
195 }
196 }
197 } else {
198 builder_tokens.extend(quote!(let mut builder = formatter.debug_tuple(#name);));
199
200 if let Data::Struct(data) = &ast.data {
201 for (index, field) in data.fields.iter().enumerate() {
202 let field_attribute = FieldAttributeBuilder {
203 name: FieldAttributeName::Default,
204 enable_name: false,
205 enable_ignore: true,
206 enable_impl: true,
207 }
208 .from_attributes(&field.attrs, traits);
209
210 if field_attribute.ignore {
211 continue;
212 }
213
214 let format_trait = field_attribute.format_trait;
215 let format_method = field_attribute.format_method;
216
217 let field_name = if let Some(ident) = field.ident.as_ref() {
218 ident.to_string()
219 } else {
220 format!("{}", index)
221 };
222
223 match format_trait {
224 Some(format_trait) => {
225 let format_method = format_method.unwrap();
226
227 builder_tokens.extend(
228 TokenStream::from_str(&format!(
229 "
230 let arg = {{
231 struct MyDebug<'a, T: {format_trait}>(&'a T);
232
233 impl<'a, T: {format_trait}> core::fmt::Debug for MyDebug<'a, \
234 T> {{
235 fn fmt(&self, formatter: &mut core::fmt::Formatter) -> \
236 core::fmt::Result {{
237 {format_trait}::{format_method}(self.0, formatter)
238 }}
239 }}
240
241 MyDebug(&self.{field_name})
242 }};
243 ",
244 format_trait = format_trait,
245 format_method = format_method,
246 field_name = field_name
247 ))
248 .unwrap(),
249 );
250
251 builder_tokens
252 .extend(TokenStream::from_str("builder.field(&arg);").unwrap());
253 },
254 None => match format_method {
255 Some(format_method) => {
256 let ty = field.ty.clone().into_token_stream().to_string();
257
258 builder_tokens.extend(
259 TokenStream::from_str(&format!(
260 "
261 let arg = {{
262 struct MyDebug<'a>(&'a {ty});
263
264 impl<'a> core::fmt::Debug for MyDebug<'a> {{
265 fn fmt(&self, formatter: &mut \
266 core::fmt::Formatter) -> core::fmt::Result {{
267 {format_method}(self.0, formatter)
268 }}
269 }}
270
271 MyDebug(&self.{field_name})
272 }};
273 ",
274 ty = ty,
275 format_method = format_method,
276 field_name = field_name
277 ))
278 .unwrap(),
279 );
280
281 builder_tokens
282 .extend(TokenStream::from_str("builder.field(&arg);").unwrap());
283 },
284 None => {
285 let statement = format!(
286 "builder.field(&self.{field_name});",
287 field_name = field_name
288 );
289
290 builder_tokens.extend(TokenStream::from_str(&statement).unwrap());
291 },
292 },
293 }
294
295 has_fields = true;
296 }
297 }
298 }
299
300 if name.is_empty() && !has_fields {
301 panic::unit_struct_need_name();
302 }
303
304 let ident = &ast.ident;
305
306 let mut generics_cloned: Generics = ast.generics.clone();
307
308 let where_clause = generics_cloned.make_where_clause();
309
310 for where_predicate in bound {
311 where_clause.predicates.push(where_predicate);
312 }
313
314 let (impl_generics, ty_generics, where_clause) = generics_cloned.split_for_impl();
315
316 let debug_impl = quote! {
317 impl #impl_generics core::fmt::Debug for #ident #ty_generics #where_clause {
318 #[inline]
319 fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
320 #builder_tokens
321 builder.finish()
322 }
323 }
324 };
325
326 tokens.extend(debug_impl);
327 }
328}