jj/lib/proc-macros/src/content_hash.rs
Evan Mesterhazy 965d6ce4e4 Implement a procedural macro to derive the ContentHash trait for structs
This is a no-op in terms of function, but provides a nicer way to derive the
ContentHash trait for structs using the `#[derive(ContentHash)]` syntax used
for other traits such as `Debug`.

This commit only adds the macro. A subsequent commit will replace uses of
`content_hash!{}` with `#[derive(ContentHash)]`.

The new macro generates nice error messages, just like the old macro:

```
error[E0277]: the trait bound `NotImplemented: content_hash::ContentHash` is not satisfied
   --> lib/src/content_hash.rs:265:16
    |
265 |             z: NotImplemented,
    |                ^^^^^^^^^^^^^^ the trait `content_hash::ContentHash` is not implemented for `NotImplemented`
    |
    = help: the following other types implement trait `content_hash::ContentHash`:
              bool
              i32
              i64
              u8
              u32
              u64
              std::collections::HashMap<K, V>
              BTreeMap<K, V>
            and 38 others
```

This commit does two things to make proc macros re-exported by jj_lib useable
by deps:

1. jj_lib needs to be able refer to itself as `jj_lib` which it does
   by adding an `extern crate self as jj_lib` declaration.

2. jj_lib::content_hash needs to re-export the `digest::Update` type so that
   users of jj_lib can use the `#[derive(ContentHash)]` proc macro without
   directly depending on the digest crate. This is done by re-exporting it
   as `DigestUpdate`.


#3054
2024-02-20 11:29:05 -05:00

40 lines
1.4 KiB
Rust

use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{Data, Fields, Index};
pub fn generate_hash_impl(data: &Data) -> TokenStream {
match *data {
Data::Struct(ref data) => match data.fields {
Fields::Named(ref fields) => {
let hash_statements = fields.named.iter().map(|f| {
let field_name = &f.ident;
let ty = &f.ty;
quote_spanned! {ty.span()=>
<#ty as ::jj_lib::content_hash::ContentHash>::hash(
&self.#field_name, state);
}
});
quote! {
#(#hash_statements)*
}
}
Fields::Unnamed(ref fields) => {
let hash_statements = fields.unnamed.iter().enumerate().map(|(i, f)| {
let index = Index::from(i);
let ty = &f.ty;
quote_spanned! {ty.span() =>
<#ty as ::jj_lib::content_hash::ContentHash>::hash(&self.#index, state);
}
});
quote! {
#(#hash_statements)*
}
}
Fields::Unit => {
quote! {}
}
},
_ => unimplemented!("ContentHash can only be derived for structs."),
}
}