xref: /third_party/rust/crates/syn/codegen/src/hash.rs (revision fad3a1d3)
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