educe/trait_handlers/clone/
clone_enum.rs

1use std::{fmt::Write, str::FromStr};
2
3use proc_macro2::TokenStream;
4use quote::quote;
5use syn::{punctuated::Punctuated, Data, DeriveInput, Fields, Generics, Meta};
6
7use super::{
8    super::TraitHandler,
9    models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder},
10};
11use crate::Trait;
12
13pub struct CloneEnumHandler;
14
15impl TraitHandler for CloneEnumHandler {
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, enable_bound: true
24        }
25        .from_clone_meta(meta);
26
27        let mut bound = Punctuated::new();
28
29        let mut clone_tokens = TokenStream::new();
30        let mut clone_from_tokens = TokenStream::new();
31
32        if let Data::Enum(data) = &ast.data {
33            let mut variant_idents = Vec::new();
34            let mut field_attributes_names: Vec<(bool, Vec<FieldAttribute>, Vec<String>)> =
35                Vec::new();
36
37            #[cfg(feature = "Copy")]
38            let mut has_custom_clone_method = false;
39
40            for variant in data.variants.iter() {
41                let _ = TypeAttributeBuilder {
42                    enable_flag: false, enable_bound: false
43                }
44                .from_attributes(&variant.attrs, traits);
45
46                let mut field_attributes = Vec::new();
47                let mut field_names = Vec::new();
48                let mut is_tuple = true;
49
50                match &variant.fields {
51                    Fields::Unit => (),
52                    Fields::Named(fields) => {
53                        // TODO Struct
54                        is_tuple = false;
55
56                        for field in fields.named.iter() {
57                            let field_attribute = FieldAttributeBuilder {
58                                enable_impl: true
59                            }
60                            .from_attributes(&field.attrs, traits);
61
62                            let field_name = field.ident.as_ref().unwrap().to_string();
63
64                            #[cfg(feature = "Copy")]
65                            if field_attribute.clone_method.is_some() {
66                                has_custom_clone_method = true;
67                            }
68
69                            field_attributes.push(field_attribute);
70                            field_names.push(field_name);
71                        }
72                    },
73                    Fields::Unnamed(fields) => {
74                        // TODO Tuple
75                        for (index, field) in fields.unnamed.iter().enumerate() {
76                            let field_attribute = FieldAttributeBuilder {
77                                enable_impl: true
78                            }
79                            .from_attributes(&field.attrs, traits);
80
81                            let field_name = format!("_{}", index);
82
83                            #[cfg(feature = "Copy")]
84                            if field_attribute.clone_method.is_some() {
85                                has_custom_clone_method = true;
86                            }
87
88                            field_attributes.push(field_attribute);
89                            field_names.push(field_name);
90                        }
91                    },
92                }
93
94                variant_idents.push(variant.ident.to_string());
95                field_attributes_names.push((is_tuple, field_attributes, field_names));
96            }
97
98            let enum_name = ast.ident.to_string();
99
100            #[cfg(feature = "Copy")]
101            let contains_copy = !has_custom_clone_method && traits.contains(&Trait::Copy);
102
103            #[cfg(not(feature = "Copy"))]
104            let contains_copy = false;
105
106            if contains_copy {
107                bound = type_attribute
108                    .bound
109                    .into_punctuated_where_predicates_by_generic_parameters_with_copy(
110                        &ast.generics.params,
111                    );
112
113                clone_tokens.extend(quote!(*self));
114
115                let mut match_tokens = String::from("match self {");
116
117                for (index, variant_ident) in variant_idents.iter().enumerate() {
118                    let field_attributes_names = &field_attributes_names[index];
119                    let is_tuple = field_attributes_names.0;
120                    let field_attributes = &field_attributes_names.1;
121                    let field_names = &field_attributes_names.2;
122                    let is_unit = field_names.is_empty();
123
124                    if is_unit {
125                        match_tokens
126                            .write_fmt(format_args!(
127                                "{enum_name}::{variant_ident} => {{ if let \
128                                 {enum_name}::{variant_ident} = _source {{ done = true; }} }}",
129                                enum_name = enum_name,
130                                variant_ident = variant_ident
131                            ))
132                            .unwrap();
133                    } else {
134                        let mut pattern_tokens = String::new();
135                        let mut pattern_2_tokens = String::new();
136
137                        if is_tuple {
138                            for field_name in field_names {
139                                pattern_tokens
140                                    .write_fmt(format_args!(
141                                        "{field_name},",
142                                        field_name = field_name
143                                    ))
144                                    .unwrap();
145                                pattern_2_tokens
146                                    .write_fmt(format_args!(
147                                        "___{field_name},",
148                                        field_name = field_name
149                                    ))
150                                    .unwrap();
151                            }
152                        } else {
153                            for field_name in field_names {
154                                pattern_tokens
155                                    .write_fmt(format_args!(
156                                        "{field_name},",
157                                        field_name = field_name
158                                    ))
159                                    .unwrap();
160                                pattern_2_tokens
161                                    .write_fmt(format_args!(
162                                        "{field_name}: ___{field_name},",
163                                        field_name = field_name
164                                    ))
165                                    .unwrap();
166                            }
167                        }
168
169                        let mut block_tokens = String::new();
170
171                        for (index, field_attribute) in field_attributes.iter().enumerate() {
172                            let field_name = &field_names[index];
173
174                            let clone_trait = &field_attribute.clone_trait;
175                            let clone_method = &field_attribute.clone_method;
176
177                            match clone_trait {
178                                Some(clone_trait) => {
179                                    let clone_method = clone_method.as_ref().unwrap();
180
181                                    block_tokens
182                                        .write_fmt(format_args!(
183                                            "*{field_name} = \
184                                             {clone_trait}::{clone_method}(___{field_name});",
185                                            clone_trait = clone_trait,
186                                            clone_method = clone_method,
187                                            field_name = field_name
188                                        ))
189                                        .unwrap();
190                                },
191                                None => match clone_method {
192                                    Some(clone_method) => {
193                                        block_tokens
194                                            .write_fmt(format_args!(
195                                                "*{field_name} = {clone_method}(___{field_name});",
196                                                clone_method = clone_method,
197                                                field_name = field_name
198                                            ))
199                                            .unwrap();
200                                    },
201                                    None => {
202                                        block_tokens
203                                            .write_fmt(format_args!(
204                                                "core::clone::Clone::clone_from({field_name}, \
205                                                 ___{field_name});",
206                                                field_name = field_name
207                                            ))
208                                            .unwrap();
209                                    },
210                                },
211                            }
212                        }
213
214                        if is_tuple {
215                            match_tokens
216                                .write_fmt(format_args!(
217                                    "{enum_name}::{variant_ident} ( {pattern_tokens} ) => {{ if \
218                                     let {enum_name}::{variant_ident} ( {pattern_2_tokens} ) = \
219                                     _source {{ {block_tokens} done = true; }} }}",
220                                    enum_name = enum_name,
221                                    variant_ident = variant_ident,
222                                    pattern_tokens = pattern_tokens,
223                                    pattern_2_tokens = pattern_2_tokens,
224                                    block_tokens = block_tokens
225                                ))
226                                .unwrap();
227                        } else {
228                            match_tokens
229                                .write_fmt(format_args!(
230                                    "{enum_name}::{variant_ident} {{ {pattern_tokens} }} => {{ if \
231                                     let {enum_name}::{variant_ident} {{ {pattern_2_tokens} }} = \
232                                     _source {{ {block_tokens} }} done = true; }}",
233                                    enum_name = enum_name,
234                                    variant_ident = variant_ident,
235                                    pattern_tokens = pattern_tokens,
236                                    pattern_2_tokens = pattern_2_tokens,
237                                    block_tokens = block_tokens
238                                ))
239                                .unwrap();
240                        }
241                    }
242                }
243
244                match_tokens.push('}');
245
246                clone_from_tokens.extend(TokenStream::from_str(&match_tokens).unwrap());
247            } else {
248                bound = type_attribute
249                    .bound
250                    .into_punctuated_where_predicates_by_generic_parameters(&ast.generics.params);
251
252                let mut clone_match_tokens = String::from("match self {");
253                let mut clone_from_match_tokens = String::from("match self {");
254
255                for (index, variant_ident) in variant_idents.iter().enumerate() {
256                    let field_attributes_names = &field_attributes_names[index];
257                    let is_tuple = field_attributes_names.0;
258                    let field_attributes = &field_attributes_names.1;
259                    let field_names = &field_attributes_names.2;
260                    let is_unit = field_names.is_empty();
261
262                    if is_unit {
263                        clone_match_tokens
264                            .write_fmt(format_args!(
265                                "{enum_name}::{variant_ident} => {{ {enum_name}::{variant_ident} \
266                                 }}",
267                                enum_name = enum_name,
268                                variant_ident = variant_ident
269                            ))
270                            .unwrap();
271                        clone_from_match_tokens
272                            .write_fmt(format_args!(
273                                "{enum_name}::{variant_ident} => {{ if let \
274                                 {enum_name}::{variant_ident} = _source {{ done = true; }} }}",
275                                enum_name = enum_name,
276                                variant_ident = variant_ident
277                            ))
278                            .unwrap();
279                    } else {
280                        let mut pattern_tokens = String::new();
281                        let mut pattern_2_tokens = String::new();
282                        let mut pattern_3_tokens = String::new();
283
284                        if is_tuple {
285                            let mut clone = format!(
286                                "{enum_name}::{variant_ident}(",
287                                enum_name = enum_name,
288                                variant_ident = variant_ident
289                            );
290                            let mut clone_from = String::new();
291
292                            for field_name in field_names {
293                                pattern_tokens
294                                    .write_fmt(format_args!(
295                                        "{field_name},",
296                                        field_name = field_name
297                                    ))
298                                    .unwrap();
299                                pattern_2_tokens
300                                    .write_fmt(format_args!(
301                                        "{field_name},",
302                                        field_name = field_name
303                                    ))
304                                    .unwrap();
305                                pattern_3_tokens
306                                    .write_fmt(format_args!(
307                                        "___{field_name},",
308                                        field_name = field_name
309                                    ))
310                                    .unwrap();
311                            }
312
313                            for (index, field_attribute) in field_attributes.iter().enumerate() {
314                                let field_name = &field_names[index];
315
316                                let clone_trait = &field_attribute.clone_trait;
317                                let clone_method = &field_attribute.clone_method;
318
319                                match clone_trait {
320                                    Some(clone_trait) => {
321                                        let clone_method = clone_method.as_ref().unwrap();
322
323                                        clone
324                                            .write_fmt(format_args!(
325                                                "{clone_trait}::{clone_method}({field_name}),",
326                                                clone_trait = clone_trait,
327                                                clone_method = clone_method,
328                                                field_name = field_name
329                                            ))
330                                            .unwrap();
331                                        clone_from
332                                            .write_fmt(format_args!(
333                                                "*{field_name} = \
334                                                 {clone_trait}::{clone_method}(___{field_name});",
335                                                clone_trait = clone_trait,
336                                                clone_method = clone_method,
337                                                field_name = field_name
338                                            ))
339                                            .unwrap();
340                                    },
341                                    None => {
342                                        match clone_method {
343                                            Some(clone_method) => {
344                                                clone
345                                                    .write_fmt(format_args!(
346                                                        "{clone_method}({field_name}),",
347                                                        clone_method = clone_method,
348                                                        field_name = field_name
349                                                    ))
350                                                    .unwrap();
351                                                clone_from
352                                                    .write_fmt(format_args!(
353                                                        "*{field_name} = \
354                                                         {clone_method}(___{field_name});",
355                                                        clone_method = clone_method,
356                                                        field_name = field_name
357                                                    ))
358                                                    .unwrap();
359                                            },
360                                            None => {
361                                                clone
362                                                    .write_fmt(format_args!(
363                                                        "core::clone::Clone::clone({field_name}),",
364                                                        field_name = field_name
365                                                    ))
366                                                    .unwrap();
367                                                clone_from.write_fmt(format_args!("core::clone::Clone::clone_from({field_name}, ___{field_name});", field_name = field_name)).unwrap();
368                                            },
369                                        }
370                                    },
371                                }
372                            }
373
374                            clone.push(')');
375
376                            clone_match_tokens
377                                .write_fmt(format_args!(
378                                    "{enum_name}::{variant_ident} ( {pattern_tokens} ) => {{ \
379                                     {clone} }}",
380                                    enum_name = enum_name,
381                                    variant_ident = variant_ident,
382                                    pattern_tokens = pattern_tokens,
383                                    clone = clone
384                                ))
385                                .unwrap();
386                            clone_from_match_tokens
387                                .write_fmt(format_args!(
388                                    "{enum_name}::{variant_ident} ( {pattern_2_tokens} ) => {{ if \
389                                     let {enum_name}::{variant_ident} ( {pattern_3_tokens} ) = \
390                                     _source {{ {block_tokens} done = true; }} }}",
391                                    enum_name = enum_name,
392                                    variant_ident = variant_ident,
393                                    pattern_2_tokens = pattern_2_tokens,
394                                    pattern_3_tokens = pattern_3_tokens,
395                                    block_tokens = clone_from
396                                ))
397                                .unwrap();
398                        } else {
399                            let mut clone = format!(
400                                "{enum_name}::{variant_ident} {{",
401                                enum_name = enum_name,
402                                variant_ident = variant_ident
403                            );
404                            let mut clone_from = String::new();
405
406                            for field_name in field_names {
407                                pattern_tokens
408                                    .write_fmt(format_args!(
409                                        "{field_name},",
410                                        field_name = field_name
411                                    ))
412                                    .unwrap();
413                                pattern_2_tokens
414                                    .write_fmt(format_args!(
415                                        "{field_name},",
416                                        field_name = field_name
417                                    ))
418                                    .unwrap();
419                                pattern_3_tokens
420                                    .write_fmt(format_args!(
421                                        "{field_name}: ___{field_name},",
422                                        field_name = field_name
423                                    ))
424                                    .unwrap();
425                            }
426
427                            for (index, field_attribute) in field_attributes.iter().enumerate() {
428                                let field_name = &field_names[index];
429
430                                let clone_trait = &field_attribute.clone_trait;
431                                let clone_method = &field_attribute.clone_method;
432
433                                match clone_trait {
434                                    Some(clone_trait) => {
435                                        let clone_method = clone_method.as_ref().unwrap();
436
437                                        clone
438                                            .write_fmt(format_args!(
439                                                "{field_name}: \
440                                                 {clone_trait}::{clone_method}({field_name}),",
441                                                clone_trait = clone_trait,
442                                                clone_method = clone_method,
443                                                field_name = field_name
444                                            ))
445                                            .unwrap();
446                                        clone_from
447                                            .write_fmt(format_args!(
448                                                "*{field_name} = \
449                                                 {clone_trait}::{clone_method}(___{field_name});",
450                                                clone_trait = clone_trait,
451                                                clone_method = clone_method,
452                                                field_name = field_name
453                                            ))
454                                            .unwrap();
455                                    },
456                                    None => {
457                                        match clone_method {
458                                            Some(clone_method) => {
459                                                clone
460                                                    .write_fmt(format_args!(
461                                                        "{field_name}: \
462                                                         {clone_method}({field_name}),",
463                                                        clone_method = clone_method,
464                                                        field_name = field_name
465                                                    ))
466                                                    .unwrap();
467                                                clone_from
468                                                    .write_fmt(format_args!(
469                                                        "*{field_name} = \
470                                                         {clone_method}(___{field_name});",
471                                                        clone_method = clone_method,
472                                                        field_name = field_name
473                                                    ))
474                                                    .unwrap();
475                                            },
476                                            None => {
477                                                clone
478                                                    .write_fmt(format_args!(
479                                                        "{field_name}: \
480                                                         core::clone::Clone::clone({field_name}),",
481                                                        field_name = field_name
482                                                    ))
483                                                    .unwrap();
484                                                clone_from.write_fmt(format_args!("core::clone::Clone::clone_from({field_name}, ___{field_name});", field_name = field_name)).unwrap();
485                                            },
486                                        }
487                                    },
488                                }
489                            }
490
491                            clone.push('}');
492
493                            clone_match_tokens
494                                .write_fmt(format_args!(
495                                    "{enum_name}::{variant_ident} {{ {pattern_tokens} }} => {{ \
496                                     {clone} }}",
497                                    enum_name = enum_name,
498                                    variant_ident = variant_ident,
499                                    pattern_tokens = pattern_tokens,
500                                    clone = clone
501                                ))
502                                .unwrap();
503                            clone_from_match_tokens
504                                .write_fmt(format_args!(
505                                    "{enum_name}::{variant_ident} {{ {pattern_2_tokens} }} => {{ \
506                                     if let {enum_name}::{variant_ident} {{ {pattern_3_tokens} }} \
507                                     = _source {{ {block_tokens} }} done = true; }}",
508                                    enum_name = enum_name,
509                                    variant_ident = variant_ident,
510                                    pattern_2_tokens = pattern_2_tokens,
511                                    pattern_3_tokens = pattern_3_tokens,
512                                    block_tokens = clone_from
513                                ))
514                                .unwrap();
515                        }
516                    }
517                }
518
519                clone_match_tokens.push('}');
520                clone_from_match_tokens.push('}');
521
522                clone_tokens.extend(TokenStream::from_str(&clone_match_tokens).unwrap());
523                clone_from_tokens.extend(TokenStream::from_str(&clone_from_match_tokens).unwrap());
524            }
525        }
526
527        let ident = &ast.ident;
528
529        let mut generics_cloned: Generics = ast.generics.clone();
530
531        let where_clause = generics_cloned.make_where_clause();
532
533        for where_predicate in bound {
534            where_clause.predicates.push(where_predicate);
535        }
536
537        let (impl_generics, ty_generics, where_clause) = generics_cloned.split_for_impl();
538
539        let compare_impl = quote! {
540            impl #impl_generics core::clone::Clone for #ident #ty_generics #where_clause {
541                #[inline]
542                fn clone(&self) -> Self {
543                    #clone_tokens
544                }
545
546                #[allow(clippy::incorrect_clone_impl_on_copy_type)]
547                #[inline]
548                fn clone_from(&mut self, _source: &Self) {
549                    let mut done = false;
550
551                    #clone_from_tokens
552
553                    if !done {
554                        *self = core::clone::Clone::clone(_source);
555                    }
556                }
557            }
558        };
559
560        tokens.extend(compare_impl);
561    }
562}