educe/trait_handlers/debug/
debug_enum.rs

1use quote::{format_ident, quote, ToTokens};
2use syn::{Data, DeriveInput, Fields, Meta, Type};
3
4use super::models::{FieldAttributeBuilder, FieldName, TypeAttributeBuilder, TypeName};
5use crate::{common::path::path_to_string, supported_traits::Trait, trait_handlers::TraitHandler};
6
7pub(crate) struct DebugEnumHandler;
8
9impl TraitHandler for DebugEnumHandler {
10    fn trait_meta_handler(
11        ast: &DeriveInput,
12        token_stream: &mut proc_macro2::TokenStream,
13        traits: &[Trait],
14        meta: &Meta,
15    ) -> syn::Result<()> {
16        let type_attribute = TypeAttributeBuilder {
17            enable_flag:        true,
18            enable_unsafe:      false,
19            enable_name:        true,
20            enable_named_field: false,
21            enable_bound:       true,
22            name:               TypeName::Disable,
23            named_field:        false,
24        }
25        .build_from_debug_meta(meta)?;
26
27        let name = type_attribute.name.to_ident_by_ident(&ast.ident);
28
29        let mut debug_types: Vec<&Type> = Vec::new();
30
31        let mut builder_token_stream = proc_macro2::TokenStream::new();
32
33        let mut arms_token_stream = proc_macro2::TokenStream::new();
34
35        if let Data::Enum(data) = &ast.data {
36            for variant in data.variants.iter() {
37                let type_attribute = TypeAttributeBuilder {
38                    enable_flag:        false,
39                    enable_unsafe:      false,
40                    enable_name:        true,
41                    enable_named_field: true,
42                    enable_bound:       false,
43                    name:               TypeName::Default,
44                    named_field:        matches!(&variant.fields, Fields::Named(_)),
45                }
46                .build_from_attributes(&variant.attrs, traits)?;
47
48                let variant_ident = &variant.ident;
49
50                let variant_name = type_attribute.name.to_ident_by_ident(variant_ident);
51
52                let named_field = type_attribute.named_field;
53
54                let name_string = if let Some(name) = name {
55                    if let Some(variant_name) = variant_name {
56                        Some(path_to_string(&syn::parse2(quote!(#name::#variant_name)).unwrap()))
57                    } else {
58                        Some(name.into_token_stream().to_string())
59                    }
60                } else {
61                    variant_name.map(|variant_name| variant_name.into_token_stream().to_string())
62                };
63
64                match &variant.fields {
65                    Fields::Unit => {
66                        if name_string.is_none() {
67                            return Err(super::panic::unit_variant_need_name(variant));
68                        }
69
70                        arms_token_stream
71                            .extend(quote!( Self::#variant_ident => f.write_str(#name_string), ));
72                    },
73                    Fields::Named(fields) => {
74                        let mut has_fields = false;
75
76                        let mut pattern_token_stream = proc_macro2::TokenStream::new();
77                        let mut block_token_stream = proc_macro2::TokenStream::new();
78
79                        if named_field {
80                            block_token_stream
81                                .extend(create_named_field_builder(name_string.as_deref()));
82
83                            for field in fields.named.iter() {
84                                let field_attribute = FieldAttributeBuilder {
85                                    enable_name:   true,
86                                    enable_ignore: true,
87                                    enable_method: true,
88                                    name:          FieldName::Default,
89                                }
90                                .build_from_attributes(&field.attrs, traits)?;
91
92                                let field_name_real = field.ident.as_ref().unwrap();
93                                let field_name_var = format_ident!("_{}", field_name_real);
94
95                                if field_attribute.ignore {
96                                    pattern_token_stream.extend(quote!(#field_name_real: _,));
97
98                                    continue;
99                                }
100
101                                let key = match field_attribute.name {
102                                    FieldName::Custom(name) => name,
103                                    FieldName::Default => field_name_real.clone(),
104                                };
105
106                                pattern_token_stream
107                                    .extend(quote!(#field_name_real: #field_name_var,));
108
109                                let ty = &field.ty;
110
111                                if let Some(method) = field_attribute.method {
112                                    block_token_stream.extend(super::common::create_format_arg(
113                                        ast,
114                                        ty,
115                                        &method,
116                                        quote!(#field_name_var),
117                                    ));
118
119                                    block_token_stream.extend(if name_string.is_some() {
120                                        quote! (builder.field(stringify!(#key), &arg);)
121                                    } else {
122                                        quote! (builder.entry(&Educe__RawString(stringify!(#key)), &arg);)
123                                    });
124                                } else {
125                                    debug_types.push(ty);
126
127                                    block_token_stream.extend(if name_string.is_some() {
128                                        quote! (builder.field(stringify!(#key), #field_name_var);)
129                                    } else {
130                                        quote! (builder.entry(&Educe__RawString(stringify!(#key)), #field_name_var);)
131                                    });
132                                }
133
134                                has_fields = true;
135                            }
136                        } else {
137                            block_token_stream
138                                .extend(quote!(let mut builder = f.debug_tuple(#name_string);));
139
140                            for field in fields.named.iter() {
141                                let field_attribute = FieldAttributeBuilder {
142                                    enable_name:   false,
143                                    enable_ignore: true,
144                                    enable_method: true,
145                                    name:          FieldName::Default,
146                                }
147                                .build_from_attributes(&field.attrs, traits)?;
148
149                                let field_name_real = field.ident.as_ref().unwrap();
150                                let field_name_var = format_ident!("_{}", field_name_real);
151
152                                if field_attribute.ignore {
153                                    pattern_token_stream.extend(quote!(#field_name_real: _,));
154
155                                    continue;
156                                }
157
158                                pattern_token_stream
159                                    .extend(quote!(#field_name_real: #field_name_var,));
160
161                                let ty = &field.ty;
162
163                                if let Some(method) = field_attribute.method {
164                                    block_token_stream.extend(super::common::create_format_arg(
165                                        ast,
166                                        ty,
167                                        &method,
168                                        quote!(#field_name_var),
169                                    ));
170
171                                    block_token_stream.extend(quote! (builder.field(&arg);));
172                                } else {
173                                    debug_types.push(ty);
174
175                                    block_token_stream
176                                        .extend(quote! (builder.field(#field_name_var);));
177                                }
178
179                                has_fields = true;
180                            }
181                        }
182
183                        if !has_fields && name_string.is_none() {
184                            return Err(super::panic::unit_struct_need_name(variant_ident));
185                        }
186
187                        arms_token_stream.extend(quote! {
188                            Self::#variant_ident { #pattern_token_stream } => {
189                                #block_token_stream
190
191                                builder.finish()
192                            },
193                        });
194                    },
195                    Fields::Unnamed(fields) => {
196                        let mut has_fields = false;
197
198                        let mut pattern_token_stream = proc_macro2::TokenStream::new();
199                        let mut block_token_stream = proc_macro2::TokenStream::new();
200
201                        if named_field {
202                            block_token_stream
203                                .extend(create_named_field_builder(name_string.as_deref()));
204
205                            for (index, field) in fields.unnamed.iter().enumerate() {
206                                let field_attribute = FieldAttributeBuilder {
207                                    enable_name:   true,
208                                    enable_ignore: true,
209                                    enable_method: true,
210                                    name:          FieldName::Default,
211                                }
212                                .build_from_attributes(&field.attrs, traits)?;
213
214                                if field_attribute.ignore {
215                                    pattern_token_stream.extend(quote!(_,));
216
217                                    continue;
218                                }
219
220                                let field_name_var = format_ident!("_{}", index);
221
222                                let key = match field_attribute.name {
223                                    FieldName::Custom(name) => name,
224                                    FieldName::Default => field_name_var.clone(),
225                                };
226
227                                pattern_token_stream.extend(quote!(#field_name_var,));
228
229                                let ty = &field.ty;
230
231                                if let Some(method) = field_attribute.method {
232                                    block_token_stream.extend(super::common::create_format_arg(
233                                        ast,
234                                        ty,
235                                        &method,
236                                        quote!(#field_name_var),
237                                    ));
238
239                                    block_token_stream.extend(if name_string.is_some() {
240                                        quote! (builder.field(stringify!(#key), &arg);)
241                                    } else {
242                                        quote! (builder.entry(&Educe__RawString(stringify!(#key)), &arg);)
243                                    });
244                                } else {
245                                    debug_types.push(ty);
246
247                                    block_token_stream.extend(if name_string.is_some() {
248                                        quote! (builder.field(stringify!(#key), #field_name_var);)
249                                    } else {
250                                        quote! (builder.entry(&Educe__RawString(stringify!(#key)), #field_name_var);)
251                                    });
252                                }
253
254                                has_fields = true;
255                            }
256                        } else {
257                            block_token_stream
258                                .extend(quote!(let mut builder = f.debug_tuple(#name_string);));
259
260                            for (index, field) in fields.unnamed.iter().enumerate() {
261                                let field_attribute = FieldAttributeBuilder {
262                                    enable_name:   false,
263                                    enable_ignore: true,
264                                    enable_method: true,
265                                    name:          FieldName::Default,
266                                }
267                                .build_from_attributes(&field.attrs, traits)?;
268
269                                if field_attribute.ignore {
270                                    pattern_token_stream.extend(quote!(_,));
271
272                                    continue;
273                                }
274
275                                let field_name_var = format_ident!("_{}", index);
276
277                                pattern_token_stream.extend(quote!(#field_name_var,));
278
279                                let ty = &field.ty;
280
281                                if let Some(method) = field_attribute.method {
282                                    block_token_stream.extend(super::common::create_format_arg(
283                                        ast,
284                                        ty,
285                                        &method,
286                                        quote!(#field_name_var),
287                                    ));
288
289                                    block_token_stream.extend(quote! (builder.field(&arg);));
290                                } else {
291                                    debug_types.push(ty);
292
293                                    block_token_stream
294                                        .extend(quote! (builder.field(#field_name_var);));
295                                }
296
297                                has_fields = true;
298                            }
299                        }
300
301                        if !has_fields && name_string.is_none() {
302                            return Err(super::panic::unit_struct_need_name(variant_ident));
303                        }
304
305                        arms_token_stream.extend(quote! {
306                            Self::#variant_ident ( #pattern_token_stream ) => {
307                                #block_token_stream
308
309                                builder.finish()
310                            },
311                        });
312                    },
313                }
314            }
315        }
316
317        let ident = &ast.ident;
318
319        if arms_token_stream.is_empty() {
320            if let Some(ident) = name {
321                builder_token_stream.extend(quote! {
322                    f.write_str(stringify!(#ident))
323                });
324            } else {
325                return Err(super::panic::unit_enum_need_name(ident));
326            }
327        } else {
328            builder_token_stream.extend(quote! {
329                match self {
330                    #arms_token_stream
331                }
332            });
333        }
334
335        let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
336            &ast.generics.params,
337            &syn::parse2(quote!(::core::fmt::Debug)).unwrap(),
338            &debug_types,
339            &[],
340        );
341
342        let mut generics = ast.generics.clone();
343        let where_clause = generics.make_where_clause();
344
345        for where_predicate in bound {
346            where_clause.predicates.push(where_predicate);
347        }
348
349        let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
350
351        token_stream.extend(quote! {
352            impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause {
353                #[inline]
354                fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
355                    #builder_token_stream
356                }
357            }
358        });
359
360        Ok(())
361    }
362}
363
364#[inline]
365fn create_named_field_builder(name_string: Option<&str>) -> proc_macro2::TokenStream {
366    if let Some(name_string) = name_string {
367        quote!(let mut builder = f.debug_struct(#name_string);)
368    } else {
369        super::common::create_debug_map_builder()
370    }
371}