• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::{cfg, file, lookup};
2 use anyhow::Result;
3 use proc_macro2::{Ident, Span, TokenStream};
4 use quote::{format_ident, quote};
5 use syn_codegen::{Data, Definitions, Node, Type};
6 
7 const HASH_SRC: &str = "src/gen/hash.rs";
8 
skip(field_type: &Type) -> bool9 fn skip(field_type: &Type) -> bool {
10     match field_type {
11         Type::Ext(ty) => ty == "Span",
12         Type::Token(_) | Type::Group(_) => true,
13         Type::Box(inner) => skip(inner),
14         Type::Tuple(inner) => inner.iter().all(skip),
15         _ => false,
16     }
17 }
18 
expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream19 fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream {
20     let type_name = &node.ident;
21     let ident = Ident::new(type_name, Span::call_site());
22 
23     match &node.data {
24         Data::Enum(variants) if variants.is_empty() => quote!(match *self {}),
25         Data::Enum(variants) => {
26             let arms = variants
27                 .iter()
28                 .enumerate()
29                 .map(|(i, (variant_name, fields))| {
30                     let i = u8::try_from(i).unwrap();
31                     let variant = Ident::new(variant_name, Span::call_site());
32                     if fields.is_empty() {
33                         quote! {
34                             #ident::#variant => {
35                                 state.write_u8(#i);
36                             }
37                         }
38                     } else {
39                         let mut pats = Vec::new();
40                         let mut hashes = Vec::new();
41                         for (i, field) in fields.iter().enumerate() {
42                             if skip(field) {
43                                 pats.push(format_ident!("_"));
44                                 continue;
45                             }
46                             let var = format_ident!("v{}", i);
47                             let mut hashed_val = quote!(#var);
48                             match field {
49                                 Type::Ext(ty) if ty == "TokenStream" => {
50                                     hashed_val = quote!(TokenStreamHelper(#hashed_val));
51                                 }
52                                 Type::Ext(ty) if ty == "Literal" => {
53                                     hashed_val = quote!(#hashed_val.to_string());
54                                 }
55                                 _ => {}
56                             }
57                             hashes.push(quote! {
58                                 #hashed_val.hash(state);
59                             });
60                             pats.push(var);
61                         }
62                         let mut cfg = None;
63                         if node.ident == "Expr" {
64                             if let Type::Syn(ty) = &fields[0] {
65                                 if !lookup::node(defs, ty).features.any.contains("derive") {
66                                     cfg = Some(quote!(#[cfg(feature = "full")]));
67                                 }
68                             }
69                         }
70                         quote! {
71                             #cfg
72                             #ident::#variant(#(#pats),*) => {
73                                 state.write_u8(#i);
74                                 #(#hashes)*
75                             }
76                         }
77                     }
78                 });
79             let nonexhaustive = if node.ident == "Expr" {
80                 Some(quote! {
81                     #[cfg(not(feature = "full"))]
82                     _ => unreachable!(),
83                 })
84             } else {
85                 None
86             };
87             quote! {
88                 match self {
89                     #(#arms)*
90                     #nonexhaustive
91                 }
92             }
93         }
94         Data::Struct(fields) => fields
95             .iter()
96             .filter_map(|(f, ty)| {
97                 if skip(ty) {
98                     return None;
99                 }
100                 let ident = Ident::new(f, Span::call_site());
101                 let mut val = quote!(self.#ident);
102                 if let Type::Ext(ty) = ty {
103                     if ty == "TokenStream" {
104                         val = quote!(TokenStreamHelper(&#val));
105                     }
106                 }
107                 Some(quote! {
108                     #val.hash(state);
109                 })
110             })
111             .collect(),
112         Data::Private => unreachable!(),
113     }
114 }
115 
expand_impl(defs: &Definitions, node: &Node) -> TokenStream116 fn expand_impl(defs: &Definitions, node: &Node) -> TokenStream {
117     let manual_hash = node.data == Data::Private
118         || node.ident == "Member"
119         || node.ident == "Index"
120         || node.ident == "Lifetime";
121     if manual_hash {
122         return TokenStream::new();
123     }
124 
125     let ident = Ident::new(&node.ident, Span::call_site());
126     let cfg_features = cfg::features(&node.features, "extra-traits");
127 
128     let body = expand_impl_body(defs, node);
129 
130     let hasher = match &node.data {
131         Data::Struct(_) if body.is_empty() => quote!(_state),
132         Data::Enum(variants) if variants.is_empty() => quote!(_state),
133         _ => quote!(state),
134     };
135 
136     quote! {
137         #cfg_features
138         impl Hash for #ident {
139             fn hash<H>(&self, #hasher: &mut H)
140             where
141                 H: Hasher,
142             {
143                 #body
144             }
145         }
146     }
147 }
148 
generate(defs: &Definitions) -> Result<()>149 pub fn generate(defs: &Definitions) -> Result<()> {
150     let mut impls = TokenStream::new();
151     for node in &defs.types {
152         impls.extend(expand_impl(defs, node));
153     }
154 
155     file::write(
156         HASH_SRC,
157         quote! {
158             #[cfg(any(feature = "derive", feature = "full"))]
159             use crate::tt::TokenStreamHelper;
160             use crate::*;
161             use std::hash::{Hash, Hasher};
162 
163             #impls
164         },
165     )?;
166 
167     Ok(())
168 }
169