Skip to main content

rustc_macros/
hash_stable.rs

1use proc_macro2::Ident;
2use quote::quote;
3
4struct Attributes {
5    ignore: bool,
6    project: Option<Ident>,
7}
8
9fn parse_attributes(field: &syn::Field) -> Attributes {
10    let mut attrs = Attributes { ignore: false, project: None };
11    for attr in &field.attrs {
12        let meta = &attr.meta;
13        if !meta.path().is_ident("stable_hasher") {
14            continue;
15        }
16        let mut any_attr = false;
17        let _ = attr.parse_nested_meta(|nested| {
18            if nested.path.is_ident("ignore") {
19                attrs.ignore = true;
20                any_attr = true;
21            }
22            if nested.path.is_ident("project") {
23                let _ = nested.parse_nested_meta(|meta| {
24                    if attrs.project.is_none() {
25                        attrs.project = meta.path.get_ident().cloned();
26                    }
27                    any_attr = true;
28                    Ok(())
29                });
30            }
31            Ok(())
32        });
33        if !any_attr {
34            panic!("error parsing stable_hasher");
35        }
36    }
37    attrs
38}
39
40pub(crate) fn hash_stable_derive(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
41    hash_stable_derive_with_mode(s, HashStableMode::Normal)
42}
43
44pub(crate) fn hash_stable_no_context_derive(
45    s: synstructure::Structure<'_>,
46) -> proc_macro2::TokenStream {
47    hash_stable_derive_with_mode(s, HashStableMode::NoContext)
48}
49
50enum HashStableMode {
51    // Do a normal derive, where any generic type parameter gets a `StableHash` bound.
52    // For example, in `struct Abc<T, U>(T, U)` the added bounds are `T: StableHash` and
53    // `U: StableHash`.
54    Normal,
55
56    // Do an (almost-)perfect derive, where any field with a generic type parameter gets a
57    // `StableHash` bound. For example, in `struct Def<T, U>(T, U::Assoc)` the added bounds are
58    // `T::StableHash` and `U::Assoc: StableHash` (not `U: StableHash`).
59    //
60    // This is used most commonly in `rustc_type_ir` for types like `TyKind<I: Interner>`.
61    // `Interner` does not impl `StableHash`, but the fields of `TyKind` do not use `I` itself,
62    // instead only using associated types from `I` such as `I::Region`. On types like `TyKind` we
63    // typically also see the use of `derive_where` for built-in traits such as `Debug`.
64    NoContext,
65}
66
67fn hash_stable_derive_with_mode(
68    mut s: synstructure::Structure<'_>,
69    mode: HashStableMode,
70) -> proc_macro2::TokenStream {
71    let add_bounds = match mode {
72        HashStableMode::Normal => synstructure::AddBounds::Generics,
73        HashStableMode::NoContext => synstructure::AddBounds::Fields,
74    };
75
76    s.add_bounds(add_bounds);
77
78    let discriminant = hash_stable_discriminant(&mut s);
79    let body = hash_stable_body(&mut s);
80
81    s.bound_impl(
82        quote!(::rustc_data_structures::stable_hasher::StableHash),
83        quote! {
84            #[inline]
85            fn stable_hash<__Hcx: ::rustc_data_structures::stable_hasher::StableHashCtxt>(
86                &self,
87                __hcx: &mut __Hcx,
88                __hasher: &mut ::rustc_data_structures::stable_hasher::StableHasher
89            ) {
90                #discriminant
91                match *self { #body }
92            }
93        },
94    )
95}
96
97fn hash_stable_discriminant(s: &mut synstructure::Structure<'_>) -> proc_macro2::TokenStream {
98    match s.ast().data {
99        syn::Data::Enum(_) => quote! {
100            ::std::mem::discriminant(self).stable_hash(__hcx, __hasher);
101        },
102        syn::Data::Struct(_) => quote! {},
103        syn::Data::Union(_) => panic!("cannot derive on union"),
104    }
105}
106
107fn hash_stable_body(s: &mut synstructure::Structure<'_>) -> proc_macro2::TokenStream {
108    s.each(|bi| {
109        let attrs = parse_attributes(bi.ast());
110        if attrs.ignore {
111            quote! {}
112        } else if let Some(project) = attrs.project {
113            quote! {
114                (&#bi.#project).stable_hash(__hcx, __hasher);
115            }
116        } else {
117            quote! {
118                #bi.stable_hash(__hcx, __hasher);
119            }
120        }
121    })
122}