educe/trait_handlers/debug/
debug_struct.rs

1use 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}