mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 18:28:24 +00:00
refactor: Introduce CacheKey
trait (#3323)
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.
This commit is contained in:
parent
d1288dc2b1
commit
cdbe2ee496
53 changed files with 842 additions and 331 deletions
103
crates/ruff_macros/src/cache_key.rs
Normal file
103
crates/ruff_macros/src/cache_key.rs
Normal file
|
@ -0,0 +1,103 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
//! This crate implements internal macros for the `ruff` library.
|
||||
|
||||
use crate::cache_key::derive_cache_key;
|
||||
use proc_macro::TokenStream;
|
||||
use syn::{parse_macro_input, DeriveInput, ItemFn};
|
||||
|
||||
mod cache_key;
|
||||
mod config;
|
||||
mod define_violation;
|
||||
mod derive_message_formats;
|
||||
|
@ -20,6 +22,16 @@ pub fn derive_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream
|
|||
.into()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(CacheKey)]
|
||||
pub fn cache_key(input: TokenStream) -> TokenStream {
|
||||
let item = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
let result = derive_cache_key(&item);
|
||||
let stream = result.unwrap_or_else(|err| err.to_compile_error());
|
||||
|
||||
TokenStream::from(stream)
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn register_rules(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let mapping = parse_macro_input!(item as register_rules::Input);
|
||||
|
|
|
@ -56,6 +56,7 @@ pub fn register_rules(input: &Input) -> proc_macro2::TokenStream {
|
|||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
::ruff_macros::CacheKey,
|
||||
AsRefStr,
|
||||
::strum_macros::IntoStaticStr,
|
||||
)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue