mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:45:24 +00:00

This PR introduces a new `CacheKey` trait for types that can be used as a cache key. I'm not entirely sure if this is worth the "overhead", but I was surprised to find `HashableHashSet` and got scared when I looked at the time complexity of the `hash` function. These implementations must be extremely slow in hashed collections. I then searched for usages and quickly realized that only the cache uses these `Hash` implementations, where performance is less sensitive. This PR introduces a new `CacheKey` trait to communicate the difference between a hash and computing a key for the cache. The new trait can be implemented for types that don't implement `Hash` for performance reasons, and we can define additional constraints on the implementation: For example, we'll want to enforce portability when we add remote caching support. Using a different trait further allows us not to implement it for types without stable identities (e.g. pointers) or use other implementations than the standard hash function.
103 lines
3.5 KiB
Rust
103 lines
3.5 KiB
Rust
use proc_macro2::TokenStream;
|
|
use quote::{format_ident, quote};
|
|
use syn::spanned::Spanned;
|
|
use syn::{Data, DeriveInput, Error, Fields};
|
|
|
|
pub fn derive_cache_key(item: &DeriveInput) -> syn::Result<TokenStream> {
|
|
let fields = match &item.data {
|
|
Data::Enum(item_enum) => {
|
|
let arms = item_enum.variants.iter().enumerate().map(|(i, variant)| {
|
|
let variant_name = &variant.ident;
|
|
|
|
match &variant.fields {
|
|
Fields::Named(fields) => {
|
|
let field_names: Vec<_> = fields
|
|
.named
|
|
.iter()
|
|
.map(|field| field.ident.clone().unwrap())
|
|
.collect();
|
|
|
|
let fields_code = field_names
|
|
.iter()
|
|
.map(|field| quote!(#field.cache_key(key);));
|
|
|
|
quote! {
|
|
Self::#variant_name{#(#field_names),*} => {
|
|
key.write_usize(#i);
|
|
#(#fields_code)*
|
|
}
|
|
}
|
|
}
|
|
Fields::Unnamed(fields) => {
|
|
let field_names: Vec<_> = fields
|
|
.unnamed
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, _)| format_ident!("field_{i}"))
|
|
.collect();
|
|
|
|
let fields_code = field_names
|
|
.iter()
|
|
.map(|field| quote!(#field.cache_key(key);));
|
|
|
|
quote! {
|
|
Self::#variant_name(#(#field_names),*) => {
|
|
key.write_usize(#i);
|
|
#(#fields_code)*
|
|
}
|
|
}
|
|
}
|
|
Fields::Unit => {
|
|
quote! {
|
|
Self::#variant_name => {
|
|
key.write_usize(#i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
quote! {
|
|
match self {
|
|
#(#arms)*
|
|
}
|
|
}
|
|
}
|
|
|
|
Data::Struct(item_struct) => {
|
|
let fields = item_struct.fields.iter().enumerate().map(|(i, field)| {
|
|
let field_attr = match &field.ident {
|
|
Some(ident) => quote!(self.#ident),
|
|
None => {
|
|
let index = syn::Index::from(i);
|
|
quote!(self.#index)
|
|
}
|
|
};
|
|
|
|
quote!(#field_attr.cache_key(key);)
|
|
});
|
|
|
|
quote! {#(#fields)*}
|
|
}
|
|
|
|
Data::Union(_) => {
|
|
return Err(Error::new(
|
|
item.span(),
|
|
"CacheKey does not support unions. Only structs and enums are supported",
|
|
))
|
|
}
|
|
};
|
|
|
|
let name = &item.ident;
|
|
let (impl_generics, ty_generics, where_clause) = &item.generics.split_for_impl();
|
|
|
|
Ok(quote!(
|
|
impl #impl_generics ruff_cache::CacheKey for #name #ty_generics #where_clause {
|
|
fn cache_key(&self, key: &mut ruff_cache::CacheKeyHasher) {
|
|
use std::hash::Hasher;
|
|
use ruff_cache::CacheKey;
|
|
#fields
|
|
}
|
|
}
|
|
))
|
|
}
|