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    // no_context impl is able to derive by-field, which is closer to a perfect derive.
78    s.add_bounds(match mode {
79        HashStableMode::Normal | HashStableMode::Generic => synstructure::AddBounds::Generics,
80        HashStableMode::NoContext => synstructure::AddBounds::Fields,
81    });
82
83    // For generic impl, add `where __CTX: HashStableContext`.
84    match mode {
85        HashStableMode::Normal => {}
86        HashStableMode::Generic => {
87            s.add_where_predicate(parse_quote! { __CTX: crate::HashStableContext });
88        }
89        HashStableMode::NoContext => {}
90    }
91
92    s.add_impl_generic(generic);
93
94    let discriminant = hash_stable_discriminant(&mut s);
95    let body = hash_stable_body(&mut s);
96
97    let context: syn::Type = match mode {
98        HashStableMode::Normal => {
99            parse_quote!(::rustc_query_system::ich::StableHashingContext<'__ctx>)
100        }
101        HashStableMode::Generic | HashStableMode::NoContext => parse_quote!(__CTX),
102    };
103
104    s.bound_impl(
105        quote!(
106            ::rustc_data_structures::stable_hasher::HashStable<
107                #context
108            >
109        ),
110        quote! {
111            #[inline]
112            fn hash_stable(
113                &self,
114                __hcx: &mut #context,
115                __hasher: &mut ::rustc_data_structures::stable_hasher::StableHasher) {
116                #discriminant
117                match *self { #body }
118            }
119        },
120    )
121}
122
123fn hash_stable_discriminant(s: &mut synstructure::Structure<'_>) -> proc_macro2::TokenStream {
124    match s.ast().data {
125        syn::Data::Enum(_) => quote! {
126            ::std::mem::discriminant(self).hash_stable(__hcx, __hasher);
127        },
128        syn::Data::Struct(_) => quote! {},
129        syn::Data::Union(_) => panic!("cannot derive on union"),
130    }
131}
132
133fn hash_stable_body(s: &mut synstructure::Structure<'_>) -> proc_macro2::TokenStream {
134    s.each(|bi| {
135        let attrs = parse_attributes(bi.ast());
136        if attrs.ignore {
137            quote! {}
138        } else if let Some(project) = attrs.project {
139            quote! {
140                (&#bi.#project).hash_stable(__hcx, __hasher);
141            }
142        } else {
143            quote! {
144                #bi.hash_stable(__hcx, __hasher);
145            }
146        }
147    })
148}