1use crate::{cfg, file, lookup}; 2use anyhow::Result; 3use proc_macro2::{Ident, Span, TokenStream}; 4use quote::{format_ident, quote}; 5use syn_codegen::{Data, Definitions, Node, Type}; 6 7const HASH_SRC: &str = "src/gen/hash.rs"; 8 9fn 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 19fn 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 116fn 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 149pub 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