educe/trait_handlers/hash/
hash_enum.rs

1use quote::{format_ident, quote};
2use syn::{Data, DeriveInput, Fields, Meta, Path, Type};
3
4use super::{
5    models::{FieldAttributeBuilder, TypeAttributeBuilder},
6    TraitHandler,
7};
8use crate::Trait;
9
10pub(crate) struct HashEnumHandler;
11
12impl TraitHandler for HashEnumHandler {
13    #[inline]
14    fn trait_meta_handler(
15        ast: &DeriveInput,
16        token_stream: &mut proc_macro2::TokenStream,
17        traits: &[Trait],
18        meta: &Meta,
19    ) -> syn::Result<()> {
20        let type_attribute =
21            TypeAttributeBuilder {
22                enable_flag: true, enable_unsafe: false, enable_bound: true
23            }
24            .build_from_hash_meta(meta)?;
25
26        let mut hash_types: Vec<&Type> = Vec::new();
27
28        let mut hash_token_stream = proc_macro2::TokenStream::new();
29
30        let mut arms_token_stream = proc_macro2::TokenStream::new();
31
32        if let Data::Enum(data) = &ast.data {
33            for (variant_index, variant) in data.variants.iter().enumerate() {
34                let _ = TypeAttributeBuilder {
35                    enable_flag:   false,
36                    enable_unsafe: false,
37                    enable_bound:  false,
38                }
39                .build_from_attributes(&variant.attrs, traits)?;
40
41                let variant_ident = &variant.ident;
42
43                let built_in_hash: Path = syn::parse2(quote!(::core::hash::Hash::hash)).unwrap();
44
45                match &variant.fields {
46                    Fields::Unit => {
47                        arms_token_stream.extend(quote! {
48                            Self::#variant_ident => {
49                                ::core::hash::Hash::hash(&#variant_index, state);
50                            }
51                        });
52                    },
53                    Fields::Named(_) => {
54                        let mut pattern_token_stream = proc_macro2::TokenStream::new();
55                        let mut block_token_stream = proc_macro2::TokenStream::new();
56
57                        for field in variant.fields.iter() {
58                            let field_attribute = FieldAttributeBuilder {
59                                enable_ignore: true,
60                                enable_method: true,
61                            }
62                            .build_from_attributes(&field.attrs, traits)?;
63
64                            let field_name_real = field.ident.as_ref().unwrap();
65                            let field_name_var = format_ident!("v_{}", field_name_real);
66
67                            if field_attribute.ignore {
68                                pattern_token_stream.extend(quote!(#field_name_real: _,));
69
70                                continue;
71                            }
72
73                            pattern_token_stream.extend(quote!(#field_name_real: #field_name_var,));
74
75                            let hash = field_attribute.method.as_ref().unwrap_or_else(|| {
76                                hash_types.push(&field.ty);
77                                &built_in_hash
78                            });
79
80                            block_token_stream.extend(quote!( #hash(#field_name_var, state); ));
81                        }
82
83                        arms_token_stream.extend(quote! {
84                            Self::#variant_ident { #pattern_token_stream } => {
85                                ::core::hash::Hash::hash(&#variant_index, state);
86
87                                #block_token_stream
88                            }
89                        });
90                    },
91                    Fields::Unnamed(_) => {
92                        let mut pattern_token_stream = proc_macro2::TokenStream::new();
93                        let mut block_token_stream = proc_macro2::TokenStream::new();
94
95                        for (index, field) in variant.fields.iter().enumerate() {
96                            let field_attribute = FieldAttributeBuilder {
97                                enable_ignore: true,
98                                enable_method: true,
99                            }
100                            .build_from_attributes(&field.attrs, traits)?;
101
102                            let field_name_var = format_ident!("_{}", index);
103
104                            if field_attribute.ignore {
105                                pattern_token_stream.extend(quote!(_,));
106
107                                continue;
108                            }
109
110                            pattern_token_stream.extend(quote!(#field_name_var,));
111
112                            let hash = field_attribute.method.as_ref().unwrap_or_else(|| {
113                                hash_types.push(&field.ty);
114                                &built_in_hash
115                            });
116
117                            block_token_stream.extend(quote!( #hash(#field_name_var, state); ));
118                        }
119
120                        arms_token_stream.extend(quote! {
121                            Self::#variant_ident ( #pattern_token_stream ) => {
122                                ::core::hash::Hash::hash(&#variant_index, state);
123
124                                #block_token_stream
125                            }
126                        });
127                    },
128                }
129            }
130        }
131
132        if !arms_token_stream.is_empty() {
133            hash_token_stream.extend(quote! {
134                match self {
135                    #arms_token_stream
136                }
137            });
138        }
139
140        let ident = &ast.ident;
141
142        let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types(
143            &ast.generics.params,
144            &syn::parse2(quote!(::core::hash::Hash)).unwrap(),
145            &hash_types,
146            &[],
147        );
148
149        let mut generics = ast.generics.clone();
150        let where_clause = generics.make_where_clause();
151
152        for where_predicate in bound {
153            where_clause.predicates.push(where_predicate);
154        }
155
156        let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
157
158        token_stream.extend(quote! {
159            impl #impl_generics ::core::hash::Hash for #ident #ty_generics #where_clause {
160                #[inline]
161                fn hash<H: ::core::hash::Hasher>(&self, state: &mut H) {
162                    #hash_token_stream
163                }
164            }
165        });
166
167        Ok(())
168    }
169}