-
Notifications
You must be signed in to change notification settings - Fork 348
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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, although they are slightly different. Before: ``` error[E0277]: the trait bound `NotImplemented: content_hash::ContentHash` is not satisfied --> lib/src/content_hash.rs:242:30 | 242 | struct Broken{t: 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 std::collections::HashMap<K, V> BTreeMap<K, V> std::collections::HashSet<K> content_hash::tests::test_struct_sanity::Foo and 38 others ``` After: ``` error[E0277]: the trait bound `NotImplemented: content_hash::ContentHash` is not satisfied --> lib/src/content_hash.rs:247:13 | 247 | t: 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 std::collections::HashMap<K, V> BTreeMap<K, V> std::collections::HashSet<K> content_hash::tests::test_struct_sanity::Foo and 38 others For more information about this error, try `rustc --explain E0277`. error: could not compile `jj-lib` (lib test) due to 2 previous errors ```
- Loading branch information
1 parent
2652809
commit 3c44d84
Showing
7 changed files
with
124 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
[package] | ||
name = "proc-macros" | ||
publish = false | ||
|
||
version = { workspace = true } | ||
edition = { workspace = true } | ||
license = { workspace = true } | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
syn = {workspace=true} | ||
quote ={workspace=true} | ||
proc-macro2 = {workspace=true} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
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; | ||
quote_spanned! {f.span()=> | ||
crate::content_hash::ContentHash::hash(&self.#field_name, state); | ||
// self.#field_name.hash(state); | ||
} | ||
}); | ||
quote! { | ||
#(#hash_statements)* | ||
} | ||
} | ||
Fields::Unnamed(ref fields) => { | ||
let hash_statements = fields.unnamed.iter().enumerate().map(|(i, f)| { | ||
let index = Index::from(i); | ||
quote_spanned! {f.span() => | ||
crate::content_hash::ContentHash::hash(&self.#index, state); | ||
} | ||
}); | ||
quote! { | ||
#(#hash_statements)* | ||
} | ||
} | ||
Fields::Unit => { | ||
quote! {} | ||
} | ||
}, | ||
_ => unimplemented!("ContentHash can only be derived for structs."), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
mod content_hash; | ||
|
||
extern crate proc_macro; | ||
|
||
use quote::quote; | ||
use syn::{parse_macro_input, DeriveInput}; | ||
|
||
#[proc_macro_derive(ContentHash)] | ||
pub fn derive_content_hash(input: proc_macro::TokenStream) -> proc_macro::TokenStream { | ||
let input = parse_macro_input!(input as DeriveInput); | ||
|
||
// The name of the struct. | ||
let name = &input.ident; | ||
|
||
// Generate an expression to hash each of the fields in the struct. | ||
let hash_impl = content_hash::generate_hash_impl(&input.data); | ||
|
||
let expanded = quote! { | ||
impl crate::content_hash::ContentHash for #name { | ||
fn hash(&self, state: &mut impl digest::Update) { | ||
#hash_impl | ||
} | ||
} | ||
}; | ||
expanded.into() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters