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