educe/trait_handlers/debug/
debug_enum.rs

1use std::{fmt::Write, 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 DebugEnumHandler;
14
15impl TraitHandler for DebugEnumHandler {
16    fn trait_meta_handler(
17        ast: &DeriveInput,
18        tokens: &mut TokenStream,
19        traits: &[Trait],
20        meta: &Meta,
21    ) {
22        let type_attribute = TypeAttributeBuilder {
23            enable_flag:        true,
24            name:               TypeAttributeName::Disable,
25            enable_name:        true,
26            named_field:        false,
27            enable_named_field: false,
28            enable_bound:       true,
29        }
30        .from_debug_meta(meta);
31
32        let enum_name = ast.ident.to_string();
33
34        let name = type_attribute.name.into_string_by_ident(&ast.ident);
35
36        let bound = type_attribute
37            .bound
38            .into_punctuated_where_predicates_by_generic_parameters(&ast.generics.params);
39
40        let mut builder_tokens = TokenStream::new();
41        let mut has_variants = false;
42
43        let mut match_tokens = String::from("match self {");
44
45        if let Data::Enum(data) = &ast.data {
46            for variant in data.variants.iter() {
47                let type_attribute = TypeAttributeBuilder {
48                    enable_flag:        false,
49                    name:               TypeAttributeName::Default,
50                    enable_name:        true,
51                    named_field:        matches!(&variant.fields, Fields::Named(_)),
52                    enable_named_field: true,
53                    enable_bound:       false,
54                }
55                .from_attributes(&variant.attrs, traits);
56
57                let variant_name = type_attribute.name.into_string_by_ident(&variant.ident);
58
59                let named_field = type_attribute.named_field;
60
61                let variant_ident = variant.ident.to_string();
62
63                let name = combine_names(&name, variant_name);
64
65                match &variant.fields {
66                    Fields::Unit => {
67                        // TODO Unit
68                        if name.is_empty() {
69                            panic::unit_variant_need_name();
70                        }
71
72                        match_tokens
73                            .write_fmt(format_args!(
74                                "{enum_name}::{variant_ident} => {{ formatter.write_str({name:?}) \
75                                 }}",
76                                enum_name = enum_name,
77                                variant_ident = variant_ident,
78                                name = name
79                            ))
80                            .unwrap();
81
82                        has_variants = true;
83                    },
84                    Fields::Named(fields) => {
85                        // TODO Struct
86                        let mut has_fields = false;
87
88                        let mut pattern_tokens = String::new();
89                        let mut block_tokens = String::new();
90
91                        if named_field {
92                            if name.is_empty() {
93                                block_tokens.push_str(
94                                    "
95                                    struct RawString(&'static str);
96
97                                    impl core::fmt::Debug for RawString {{
98                                        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> \
99                                     core::fmt::Result {{
100                                            f.write_str(self.0)
101                                        }}
102                                    }}
103                                ",
104                                );
105
106                                block_tokens.push_str("let mut builder = formatter.debug_map();");
107                            } else {
108                                block_tokens
109                                    .write_fmt(format_args!(
110                                        "let mut builder = formatter.debug_struct({name:?});",
111                                        name = name
112                                    ))
113                                    .unwrap();
114                            }
115
116                            for field in fields.named.iter() {
117                                let field_attribute = FieldAttributeBuilder {
118                                    name:          FieldAttributeName::Default,
119                                    enable_name:   true,
120                                    enable_ignore: true,
121                                    enable_impl:   true,
122                                }
123                                .from_attributes(&field.attrs, traits);
124
125                                let field_name = field.ident.as_ref().unwrap().to_string();
126
127                                if field_attribute.ignore {
128                                    pattern_tokens
129                                        .write_fmt(format_args!(
130                                            "{field_name}: _,",
131                                            field_name = field_name
132                                        ))
133                                        .unwrap();
134                                    continue;
135                                }
136
137                                let rename = field_attribute.name.into_option_string();
138
139                                let format_trait = field_attribute.format_trait;
140                                let format_method = field_attribute.format_method;
141
142                                let key = rename.unwrap_or_else(|| field_name.clone());
143
144                                pattern_tokens
145                                    .write_fmt(format_args!(
146                                        "{field_name},",
147                                        field_name = field_name
148                                    ))
149                                    .unwrap();
150
151                                match format_trait {
152                                    Some(format_trait) => {
153                                        let format_method = format_method.unwrap();
154
155                                        block_tokens
156                                            .write_fmt(format_args!(
157                                                "
158                                            let arg = {{
159                                                struct MyDebug<'a, T: {format_trait}>(&'a T);
160
161                                                impl<'a, T: {format_trait}> core::fmt::Debug for \
162                                                 MyDebug<'a, T> {{
163                                                    fn fmt(&self, formatter: &mut \
164                                                 core::fmt::Formatter) -> core::fmt::Result {{
165                                                        {format_trait}::{format_method}(self.0, \
166                                                 formatter)
167                                                    }}
168                                                }}
169
170                                                MyDebug({field_name})
171                                            }};
172                                        ",
173                                                format_trait = format_trait,
174                                                format_method = format_method,
175                                                field_name = field_name
176                                            ))
177                                            .unwrap();
178
179                                        let statement = if name.is_empty() {
180                                            format!(
181                                                "builder.entry(&RawString({key:?}), &arg);",
182                                                key = key
183                                            )
184                                        } else {
185                                            format!("builder.field({key:?}, &arg);", key = key)
186                                        };
187
188                                        block_tokens.push_str(&statement);
189                                    },
190                                    None => match format_method {
191                                        Some(format_method) => {
192                                            let ty =
193                                                field.ty.clone().into_token_stream().to_string();
194
195                                            block_tokens
196                                                .write_fmt(format_args!(
197                                                    "
198                                                    let arg = {{
199                                                        struct MyDebug<'a>(&'a {ty});
200
201                                                        impl<'a> core::fmt::Debug for MyDebug<'a> \
202                                                     {{
203                                                            fn fmt(&self, formatter: &mut \
204                                                     core::fmt::Formatter) -> core::fmt::Result {{
205                                                                {format_method}(self.0, formatter)
206                                                            }}
207                                                        }}
208
209                                                        MyDebug({field_name})
210                                                    }};
211                                                ",
212                                                    ty = ty,
213                                                    format_method = format_method,
214                                                    field_name = field_name
215                                                ))
216                                                .unwrap();
217
218                                            let statement = if name.is_empty() {
219                                                format!(
220                                                    "builder.entry(&RawString({key:?}), &arg);",
221                                                    key = key
222                                                )
223                                            } else {
224                                                format!("builder.field({key:?}, &arg);", key = key)
225                                            };
226
227                                            block_tokens.push_str(&statement);
228                                        },
229                                        None => {
230                                            let statement = if name.is_empty() {
231                                                format!(
232                                                    "builder.entry(&RawString({key:?}), \
233                                                     {field_name});",
234                                                    key = key,
235                                                    field_name = field_name
236                                                )
237                                            } else {
238                                                format!(
239                                                    "builder.field({key:?}, {field_name});",
240                                                    key = key,
241                                                    field_name = field_name
242                                                )
243                                            };
244
245                                            block_tokens.push_str(&statement);
246                                        },
247                                    },
248                                }
249
250                                has_fields = true;
251                            }
252                        } else {
253                            block_tokens
254                                .write_fmt(format_args!(
255                                    "let mut builder = formatter.debug_tuple({name:?});",
256                                    name = name
257                                ))
258                                .unwrap();
259
260                            for field in fields.named.iter() {
261                                let field_attribute = FieldAttributeBuilder {
262                                    name:          FieldAttributeName::Default,
263                                    enable_name:   false,
264                                    enable_ignore: true,
265                                    enable_impl:   true,
266                                }
267                                .from_attributes(&field.attrs, traits);
268
269                                let field_name = field.ident.as_ref().unwrap().to_string();
270
271                                if field_attribute.ignore {
272                                    pattern_tokens
273                                        .write_fmt(format_args!(
274                                            "{field_name}: _,",
275                                            field_name = field_name
276                                        ))
277                                        .unwrap();
278                                    continue;
279                                }
280
281                                let format_trait = field_attribute.format_trait;
282                                let format_method = field_attribute.format_method;
283
284                                pattern_tokens
285                                    .write_fmt(format_args!(
286                                        "{field_name},",
287                                        field_name = field_name
288                                    ))
289                                    .unwrap();
290
291                                match format_trait {
292                                    Some(format_trait) => {
293                                        let format_method = format_method.unwrap();
294
295                                        block_tokens
296                                            .write_fmt(format_args!(
297                                                "
298                                            let arg = {{
299                                                struct MyDebug<'a, T: {format_trait}>(&'a T);
300
301                                                impl<'a, T: {format_trait}> core::fmt::Debug for \
302                                                 MyDebug<'a, T> {{
303                                                    fn fmt(&self, formatter: &mut \
304                                                 core::fmt::Formatter) -> core::fmt::Result {{
305                                                        {format_trait}::{format_method}(self.0, \
306                                                 formatter)
307                                                    }}
308                                                }}
309
310                                                MyDebug({field_name})
311                                            }};
312                                        ",
313                                                format_trait = format_trait,
314                                                format_method = format_method,
315                                                field_name = field_name
316                                            ))
317                                            .unwrap();
318
319                                        block_tokens.push_str("builder.field(&arg);");
320                                    },
321                                    None => match format_method {
322                                        Some(format_method) => {
323                                            let ty =
324                                                field.ty.clone().into_token_stream().to_string();
325
326                                            block_tokens
327                                                .write_fmt(format_args!(
328                                                    "
329                                                    let arg = {{
330                                                        struct MyDebug<'a>(&'a {ty});
331
332                                                        impl<'a> core::fmt::Debug for MyDebug<'a> \
333                                                     {{
334                                                            fn fmt(&self, formatter: &mut \
335                                                     core::fmt::Formatter) -> core::fmt::Result {{
336                                                                {format_method}(self.0, formatter)
337                                                            }}
338                                                        }}
339
340                                                        MyDebug({field_name})
341                                                    }};
342                                                ",
343                                                    ty = ty,
344                                                    format_method = format_method,
345                                                    field_name = field_name
346                                                ))
347                                                .unwrap();
348
349                                            block_tokens.push_str("builder.field(&arg);");
350                                        },
351                                        None => {
352                                            let statement = format!(
353                                                "builder.field({field_name});",
354                                                field_name = field_name
355                                            );
356
357                                            block_tokens.push_str(&statement);
358                                        },
359                                    },
360                                }
361
362                                has_fields = true;
363                            }
364                        }
365
366                        if name.is_empty() && !has_fields {
367                            panic::unit_struct_need_name();
368                        }
369
370                        block_tokens.push_str("return builder.finish();");
371
372                        match_tokens
373                            .write_fmt(format_args!(
374                                "{enum_name}::{variant_ident}{{ {pattern_tokens} }} => {{ \
375                                 {block_tokens} }}",
376                                enum_name = enum_name,
377                                variant_ident = variant_ident,
378                                pattern_tokens = pattern_tokens,
379                                block_tokens = block_tokens
380                            ))
381                            .unwrap();
382
383                        has_variants = true;
384                    },
385                    Fields::Unnamed(fields) => {
386                        // TODO Tuple
387                        let mut has_fields = false;
388
389                        let mut pattern_tokens = String::new();
390                        let mut block_tokens = String::new();
391
392                        if named_field {
393                            if name.is_empty() {
394                                block_tokens.push_str(
395                                    "
396                                    struct RawString(&'static str);
397
398                                    impl core::fmt::Debug for RawString {{
399                                        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> \
400                                     core::fmt::Result {{
401                                            f.write_str(self.0)
402                                        }}
403                                    }}
404                                ",
405                                );
406
407                                block_tokens.push_str("let mut builder = formatter.debug_map();");
408                            } else {
409                                block_tokens
410                                    .write_fmt(format_args!(
411                                        "let mut builder = formatter.debug_struct({name:?});",
412                                        name = name
413                                    ))
414                                    .unwrap();
415                            }
416
417                            for (index, field) in fields.unnamed.iter().enumerate() {
418                                let field_attribute = FieldAttributeBuilder {
419                                    name:          FieldAttributeName::Default,
420                                    enable_name:   true,
421                                    enable_ignore: true,
422                                    enable_impl:   true,
423                                }
424                                .from_attributes(&field.attrs, traits);
425
426                                if field_attribute.ignore {
427                                    pattern_tokens.push_str("_,");
428                                    continue;
429                                }
430
431                                let rename = field_attribute.name.into_option_string();
432
433                                let format_trait = field_attribute.format_trait;
434                                let format_method = field_attribute.format_method;
435
436                                let (key, field_name) = match rename {
437                                    Some(rename) => (rename, format!("{}", index)),
438                                    None => (format!("_{}", index), format!("{}", index)),
439                                };
440
441                                pattern_tokens
442                                    .write_fmt(format_args!(
443                                        "_{field_name},",
444                                        field_name = field_name
445                                    ))
446                                    .unwrap();
447
448                                match format_trait {
449                                    Some(format_trait) => {
450                                        let format_method = format_method.unwrap();
451
452                                        block_tokens
453                                            .write_fmt(format_args!(
454                                                "
455                                            let arg = {{
456                                                struct MyDebug<'a, T: {format_trait}>(&'a T);
457
458                                                impl<'a, T: {format_trait}> core::fmt::Debug for \
459                                                 MyDebug<'a, T> {{
460                                                    fn fmt(&self, formatter: &mut \
461                                                 core::fmt::Formatter) -> core::fmt::Result {{
462                                                        {format_trait}::{format_method}(self.0, \
463                                                 formatter)
464                                                    }}
465                                                }}
466
467                                                MyDebug(_{field_name})
468                                            }};
469                                        ",
470                                                format_trait = format_trait,
471                                                format_method = format_method,
472                                                field_name = field_name
473                                            ))
474                                            .unwrap();
475
476                                        let statement = if name.is_empty() {
477                                            format!(
478                                                "builder.entry(&RawString({key:?}), &arg);",
479                                                key = key
480                                            )
481                                        } else {
482                                            format!("builder.field({key:?}, &arg);", key = key)
483                                        };
484
485                                        block_tokens.push_str(&statement);
486                                    },
487                                    None => match format_method {
488                                        Some(format_method) => {
489                                            let ty =
490                                                field.ty.clone().into_token_stream().to_string();
491
492                                            block_tokens
493                                                .write_fmt(format_args!(
494                                                    "
495                                                    let arg = {{
496                                                        struct MyDebug<'a>(&'a {ty});
497
498                                                        impl<'a> core::fmt::Debug for MyDebug<'a> \
499                                                     {{
500                                                            fn fmt(&self, formatter: &mut \
501                                                     core::fmt::Formatter) -> core::fmt::Result {{
502                                                                {format_method}(self.0, formatter)
503                                                            }}
504                                                        }}
505
506                                                        MyDebug(_{field_name})
507                                                    }};
508                                                ",
509                                                    ty = ty,
510                                                    format_method = format_method,
511                                                    field_name = field_name
512                                                ))
513                                                .unwrap();
514
515                                            let statement = if name.is_empty() {
516                                                format!(
517                                                    "builder.entry(&RawString({key:?}), &arg);",
518                                                    key = key
519                                                )
520                                            } else {
521                                                format!("builder.field({key:?}, &arg);", key = key)
522                                            };
523
524                                            block_tokens.push_str(&statement);
525                                        },
526                                        None => {
527                                            let statement = if name.is_empty() {
528                                                format!(
529                                                    "builder.entry(&RawString({key:?}), \
530                                                     {field_name});",
531                                                    key = key,
532                                                    field_name = field_name
533                                                )
534                                            } else {
535                                                format!(
536                                                    "builder.field({key:?}, _{field_name});",
537                                                    key = key,
538                                                    field_name = field_name
539                                                )
540                                            };
541
542                                            block_tokens.push_str(&statement);
543                                        },
544                                    },
545                                }
546
547                                has_fields = true;
548                            }
549                        } else {
550                            block_tokens
551                                .write_fmt(format_args!(
552                                    "let mut builder = formatter.debug_tuple({name:?});",
553                                    name = name
554                                ))
555                                .unwrap();
556
557                            for (index, field) in fields.unnamed.iter().enumerate() {
558                                let field_attribute = FieldAttributeBuilder {
559                                    name:          FieldAttributeName::Default,
560                                    enable_name:   false,
561                                    enable_ignore: true,
562                                    enable_impl:   true,
563                                }
564                                .from_attributes(&field.attrs, traits);
565
566                                if field_attribute.ignore {
567                                    pattern_tokens.push_str("_,");
568                                    continue;
569                                }
570
571                                let format_trait = field_attribute.format_trait;
572                                let format_method = field_attribute.format_method;
573
574                                let field_name = format!("{}", index);
575
576                                pattern_tokens
577                                    .write_fmt(format_args!(
578                                        "_{field_name},",
579                                        field_name = field_name
580                                    ))
581                                    .unwrap();
582
583                                match format_trait {
584                                    Some(format_trait) => {
585                                        let format_method = format_method.unwrap();
586
587                                        block_tokens
588                                            .write_fmt(format_args!(
589                                                "
590                                            let arg = {{
591                                                struct MyDebug<'a, T: {format_trait}>(&'a T);
592
593                                                impl<'a, T: {format_trait}> core::fmt::Debug for \
594                                                 MyDebug<'a, T> {{
595                                                    fn fmt(&self, formatter: &mut \
596                                                 core::fmt::Formatter) -> core::fmt::Result {{
597                                                        {format_trait}::{format_method}(self.0, \
598                                                 formatter)
599                                                    }}
600                                                }}
601
602                                                MyDebug(_{field_name})
603                                            }};
604                                        ",
605                                                format_trait = format_trait,
606                                                format_method = format_method,
607                                                field_name = field_name
608                                            ))
609                                            .unwrap();
610
611                                        block_tokens.push_str("builder.field(&arg);");
612                                    },
613                                    None => match format_method {
614                                        Some(format_method) => {
615                                            let ty =
616                                                field.ty.clone().into_token_stream().to_string();
617
618                                            block_tokens
619                                                .write_fmt(format_args!(
620                                                    "
621                                                    let arg = {{
622                                                        struct MyDebug<'a>(&'a {ty});
623
624                                                        impl<'a> core::fmt::Debug for MyDebug<'a> \
625                                                     {{
626                                                            fn fmt(&self, formatter: &mut \
627                                                     core::fmt::Formatter) -> core::fmt::Result {{
628                                                                {format_method}(self.0, formatter)
629                                                            }}
630                                                        }}
631
632                                                        MyDebug(_{field_name})
633                                                    }};
634                                                ",
635                                                    ty = ty,
636                                                    format_method = format_method,
637                                                    field_name = field_name
638                                                ))
639                                                .unwrap();
640
641                                            block_tokens.push_str("builder.field(&arg);");
642                                        },
643                                        None => {
644                                            let statement = format!(
645                                                "builder.field(_{field_name});",
646                                                field_name = field_name
647                                            );
648
649                                            block_tokens.push_str(&statement);
650                                        },
651                                    },
652                                }
653
654                                has_fields = true;
655                            }
656                        }
657
658                        if name.is_empty() && !has_fields {
659                            panic::unit_struct_need_name();
660                        }
661
662                        block_tokens.push_str("return builder.finish();");
663
664                        match_tokens
665                            .write_fmt(format_args!(
666                                "{enum_name}::{variant_ident}( {pattern_tokens} ) => {{ \
667                                 {block_tokens} }}",
668                                enum_name = enum_name,
669                                variant_ident = variant_ident,
670                                pattern_tokens = pattern_tokens,
671                                block_tokens = block_tokens
672                            ))
673                            .unwrap();
674
675                        has_variants = true;
676                    },
677                }
678            }
679        }
680
681        match_tokens.push('}');
682
683        builder_tokens.extend(TokenStream::from_str(&match_tokens).unwrap());
684
685        if name.is_empty() && !has_variants {
686            panic::unit_enum_need_name();
687        }
688
689        let ident = &ast.ident;
690
691        let mut generics_cloned: Generics = ast.generics.clone();
692
693        let where_clause = generics_cloned.make_where_clause();
694
695        for where_predicate in bound {
696            where_clause.predicates.push(where_predicate);
697        }
698
699        let (impl_generics, ty_generics, where_clause) = generics_cloned.split_for_impl();
700
701        let debug_impl = quote! {
702            impl #impl_generics core::fmt::Debug for #ident #ty_generics #where_clause {
703                #[inline]
704                #[allow(clippy::unneeded_field_pattern)]
705                fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result {
706                    #builder_tokens
707                }
708            }
709        };
710
711        tokens.extend(debug_impl);
712    }
713}
714
715fn combine_names(name: &str, variant_name: String) -> String {
716    if name.is_empty() {
717        if variant_name.is_empty() {
718            String::new()
719        } else {
720            variant_name
721        }
722    } else {
723        let mut name = name.to_string();
724
725        if !variant_name.is_empty() {
726            if !variant_name.starts_with("::") {
727                name.push_str("::");
728            }
729
730            name.push_str(&variant_name);
731        }
732
733        name
734    }
735}