rustc_macros/
hash_stable.rs

1use proc_macro2::Ident;
2use quote::quote;
3use syn::parse_quote;
4
5struct Attributes {
6    ignore: bool,
7    project: Option<Ident>,
8}
9
10fn parse_attributes(field: &syn::Field) -> Attributes {
11    let mut attrs = Attributes { ignore: false, project: None };
12    for attr in &field.attrs {
13        let meta = &attr.meta;
14        if !meta.path().is_ident("stable_hasher") {
15            continue;
16        }
17        let mut any_attr = false;
18        let _ = attr.parse_nested_meta(|nested| {
19            if nested.path.is_ident("ignore") {
20                attrs.ignore = true;
21                any_attr = true;
22            }
23            if nested.path.is_ident("project") {
24                let _ = nested.parse_nested_meta(|meta| {
25                    if attrs.project.is_none() {
26                        attrs.project = meta.path.get_ident().cloned();
27                    }
28                    any_attr = true;
29                    Ok(())
30                });
31            }
32            Ok(())
33        });
34        if !any_attr {
35            panic!("error parsing stable_hasher");
36        }
37    }
38    attrs
39}
40
41pub(crate) fn hash_stable_derive(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
42    hash_stable_derive_with_mode(s, HashStableMode::Normal)
43}
44
45pub(crate) fn hash_stable_generic_derive(
46    s: synstructure::Structure<'_>,
47) -> proc_macro2::TokenStream {
48    hash_stable_derive_with_mode(s, HashStableMode::Generic)
49}
50
51pub(crate) fn hash_stable_no_context_derive(
52    s: synstructure::Structure<'_>,
53) -> proc_macro2::TokenStream {
54    hash_stable_derive_with_mode(s, HashStableMode::NoContext)
55}
56
57enum HashStableMode {
58    // Use the query-system aware stable hashing context.
59    Normal,
60    // Emit a generic implementation that uses a crate-local `StableHashingContext`
61    // trait, when the crate is upstream of `rustc_middle`.
62    Generic,
63    // Emit a hash-stable implementation that takes no context,
64    // and emits per-field where clauses for (almost-)perfect derives.
65    NoContext,
66}
67
68fn hash_stable_derive_with_mode(
69    mut s: synstructure::Structure<'_>,
70    mode: HashStableMode,
71) -> proc_macro2::TokenStream {
72    let generic: syn::GenericParam = match mode {
73        HashStableMode::Normal => parse_quote!('__ctx),
74        HashStableMode::Generic | HashStableMode::NoContext => parse_quote!(__CTX),
75    };
76
77    s.underscore_const(true);
78
79    // no_context impl is able to derive by-field, which is closer to a perfect derive.
80    s.add_bounds(match mode {
81        HashStableMode::Normal | HashStableMode::Generic => synstructure::AddBounds::Generics,
82        HashStableMode::NoContext => synstructure::AddBounds::Fields,
83    });
84
85    // For generic impl, add `where __CTX: HashStableContext`.
86    match mode {
87        HashStableMode::Normal => {}
88        HashStableMode::Generic => {
89            s.add_where_predicate(parse_quote! { __CTX: crate::HashStableContext });
90        }
91        HashStableMode::NoContext => {}
92    }
93
94    s.add_impl_generic(generic);
95
96    let discriminant = hash_stable_discriminant(&mut s);
97    let body = hash_stable_body(&mut s);
98
99    let context: syn::Type = match mode {
100        HashStableMode::Normal => {
101            parse_quote!(::rustc_query_system::ich::StableHashingContext<'__ctx>)
102        }
103        HashStableMode::Generic | HashStableMode::NoContext => parse_quote!(__CTX),
104    };
105
106    s.bound_impl(
107        quote!(
108            ::rustc_data_structures::stable_hasher::HashStable<
109                #context
110            >
111        ),
112        quote! {
113            #[inline]
114            fn hash_stable(
115                &self,
116                __hcx: &mut #context,
117                __hasher: &mut ::rustc_data_structures::stable_hasher::StableHasher) {
118                #discriminant
119                match *self { #body }
120            }
121        },
122    )
123}
124
125fn hash_stable_discriminant(s: &mut synstructure::Structure<'_>) -> proc_macro2::TokenStream {
126    match s.ast().data {
127        syn::Data::Enum(_) => quote! {
128            ::std::mem::discriminant(self).hash_stable(__hcx, __hasher);
129        },
130        syn::Data::Struct(_) => quote! {},
131        syn::Data::Union(_) => panic!("cannot derive on union"),
132    }
133}
134
135fn hash_stable_body(s: &mut synstructure::Structure<'_>) -> proc_macro2::TokenStream {
136    s.each(|bi| {
137        let attrs = parse_attributes(bi.ast());
138        if attrs.ignore {
139            quote! {}
140        } else if let Some(project) = attrs.project {
141            quote! {
142                (&#bi.#project).hash_stable(__hcx, __hasher);
143            }
144        } else {
145            quote! {
146                #bi.hash_stable(__hcx, __hasher);
147            }
148        }
149    })
150}