ok/jj
1
0
Fork 0
forked from mirrors/jj

Add support for deriving ContentHash for Enums

Here's an example of what the derived output looks like for an enum:

```rust
pub enum TreeValue {
    File { id: FileId, executable: bool },
    Symlink(SymlinkId),
    Tree(TreeId),
    GitSubmodule(CommitId),
    Conflict(ConflictId),
}
#[automatically_derived]
impl ::jj_lib::content_hash::ContentHash for TreeValue {
    fn hash(&self, state: &mut impl digest::Update) {
        match self {
            Self::File { id, executable } => {
                state.update(&0u32.to_le_bytes());
                ::jj_lib::content_hash::ContentHash::hash(id, state);
                ::jj_lib::content_hash::ContentHash::hash(executable, state);
            }
            Self::Symlink(field_0) => {
                state.update(&1u32.to_le_bytes());
                ::jj_lib::content_hash::ContentHash::hash(field_0, state);
            }
            Self::Tree(field_0) => {
                state.update(&2u32.to_le_bytes());
                ::jj_lib::content_hash::ContentHash::hash(field_0, state);
            }
            Self::GitSubmodule(field_0) => {
                state.update(&3u32.to_le_bytes());
                ::jj_lib::content_hash::ContentHash::hash(field_0, state);
            }
            Self::Conflict(field_0) => {
                state.update(&4u32.to_le_bytes());
                ::jj_lib::content_hash::ContentHash::hash(field_0, state);
            }
        }
    }
}
```


#3054
This commit is contained in:
Evan Mesterhazy 2024-02-14 13:28:11 -05:00 committed by Evan Mesterhazy
parent 8e1a6c708f
commit 966a5505e2
2 changed files with 76 additions and 4 deletions

View file

@ -1,7 +1,7 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{parse_quote, Data, Fields, GenericParam, Generics, Index};
use syn::{parse_quote, Data, Field, Fields, GenericParam, Generics, Index};
pub fn add_trait_bounds(mut generics: Generics) -> Generics {
for param in &mut generics.params {
@ -46,6 +46,66 @@ pub fn generate_hash_impl(data: &Data) -> TokenStream {
quote! {}
}
},
_ => unimplemented!("ContentHash can only be derived for structs."),
// Generates a match statement with a match arm and hash implementation
// for each of the variants in the enum.
Data::Enum(ref data) => {
let match_hash_statements = data.variants.iter().enumerate().map(|(i, v)| {
let variant_id = &v.ident;
match &v.fields {
Fields::Named(fields) => {
let bindings = enum_bindings(fields.named.iter());
let ix = index_to_ordinal(i);
quote_spanned! {v.span() =>
Self::#variant_id{ #(#bindings),* } => {
::jj_lib::content_hash::ContentHash::hash(&#ix, state);
#( ::jj_lib::content_hash::ContentHash::hash(#bindings, state); )*
}
}
}
Fields::Unnamed(fields) => {
let bindings = enum_bindings(fields.unnamed.iter());
let ix = index_to_ordinal(i);
quote_spanned! {v.span() =>
Self::#variant_id( #(#bindings),* ) => {
::jj_lib::content_hash::ContentHash::hash(&#ix, state);
#( ::jj_lib::content_hash::ContentHash::hash(#bindings, state); )*
}
}
}
Fields::Unit => {
let ix = index_to_ordinal(i);
quote_spanned! {v.span() =>
Self::#variant_id => {
::jj_lib::content_hash::ContentHash::hash(&#ix, state);
}
}
}
}
});
quote! {
match self {
#(#match_hash_statements)*
}
}
}
Data::Union(_) => unimplemented!("ContentHash cannot be derived for unions."),
}
}
// The documentation for `ContentHash` specifies that the hash impl for each
// enum variant should hash the ordinal number of the enum variant as a little
// endian u32 before hashing the variant's fields, if any.
fn index_to_ordinal(ix: usize) -> u32 {
u32::try_from(ix).expect("The number of enum variants overflows a u32.")
}
fn enum_bindings<'a>(fields: impl IntoIterator<Item = &'a Field>) -> Vec<Ident> {
fields
.into_iter()
.enumerate()
.map(|(i, f)| {
// If the field is named, use the name, otherwise generate a placeholder name.
f.ident.clone().unwrap_or(format_ident!("field_{}", i))
})
.collect::<Vec<_>>()
}

View file

@ -264,6 +264,18 @@ mod tests {
);
}
// Test that the derived version of `ContentHash` matches the that's
// manually implemented for `std::Option`.
#[test]
fn derive_for_enum() {
#[derive(ContentHash)]
enum MyOption<T> {
None,
Some(T),
}
assert_eq!(hash(&Option::<i32>::None), hash(&MyOption::<i32>::None));
assert_eq!(hash(&Some(1)), hash(&MyOption::Some(1)));
}
// This will be removed once all uses of content_hash! are replaced by the
// derive version.
#[test]