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:
Micha Reiser 2023-03-03 19:29:49 +01:00 committed by GitHub
parent d1288dc2b1
commit cdbe2ee496
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 842 additions and 331 deletions

13
Cargo.lock generated
View file

@ -1982,6 +1982,7 @@ dependencies = [
"path-absolutize", "path-absolutize",
"regex", "regex",
"result-like", "result-like",
"ruff_cache",
"ruff_macros", "ruff_macros",
"ruff_python", "ruff_python",
"ruff_rustpython", "ruff_rustpython",
@ -2005,6 +2006,17 @@ dependencies = [
"wasm-bindgen-test", "wasm-bindgen-test",
] ]
[[package]]
name = "ruff_cache"
version = "0.0.0"
dependencies = [
"filetime",
"globset",
"itertools",
"regex",
"ruff_macros",
]
[[package]] [[package]]
name = "ruff_cli" name = "ruff_cli"
version = "0.0.253" version = "0.0.253"
@ -2033,6 +2045,7 @@ dependencies = [
"rayon", "rayon",
"regex", "regex",
"ruff", "ruff",
"ruff_cache",
"rustc-hash", "rustc-hash",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -19,6 +19,7 @@ doctest = false
ruff_macros = { path = "../ruff_macros" } ruff_macros = { path = "../ruff_macros" }
ruff_python = { path = "../ruff_python" } ruff_python = { path = "../ruff_python" }
ruff_rustpython = { path = "../ruff_rustpython" } ruff_rustpython = { path = "../ruff_rustpython" }
ruff_cache = { path = "../ruff_cache" }
anyhow = { workspace = true } anyhow = { workspace = true }
bisection = { version = "0.1.0" } bisection = { version = "0.1.0" }

View file

@ -1,13 +1,12 @@
use std::ops::Deref;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use globset::GlobMatcher;
use log::debug; use log::debug;
use path_absolutize::{path_dedot, Absolutize}; use path_absolutize::{path_dedot, Absolutize};
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use crate::registry::Rule; use crate::registry::Rule;
use crate::settings::hashable::{HashableGlobMatcher, HashableHashSet};
/// Extract the absolute path and basename (as strings) from a Path. /// Extract the absolute path and basename (as strings) from a Path.
pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> { pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
@ -25,11 +24,7 @@ pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> {
/// Create a set with codes matching the pattern/code pairs. /// Create a set with codes matching the pattern/code pairs.
pub(crate) fn ignores_from_path<'a>( pub(crate) fn ignores_from_path<'a>(
path: &Path, path: &Path,
pattern_code_pairs: &'a [( pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, FxHashSet<Rule>)],
HashableGlobMatcher,
HashableGlobMatcher,
HashableHashSet<Rule>,
)],
) -> FxHashSet<&'a Rule> { ) -> FxHashSet<&'a Rule> {
let (file_path, file_basename) = extract_path_names(path).expect("Unable to parse filename"); let (file_path, file_basename) = extract_path_names(path).expect("Unable to parse filename");
pattern_code_pairs pattern_code_pairs
@ -39,8 +34,8 @@ pub(crate) fn ignores_from_path<'a>(
debug!( debug!(
"Adding per-file ignores for {:?} due to basename match on {:?}: {:?}", "Adding per-file ignores for {:?} due to basename match on {:?}: {:?}",
path, path,
basename.deref().glob().regex(), basename.glob().regex(),
&**codes codes
); );
return Some(codes.iter()); return Some(codes.iter());
} }
@ -48,8 +43,8 @@ pub(crate) fn ignores_from_path<'a>(
debug!( debug!(
"Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}", "Adding per-file ignores for {:?} due to absolute match on {:?}: {:?}",
path, path,
absolute.deref().glob().regex(), absolute.glob().regex(),
&**codes codes
); );
return Some(codes.iter()); return Some(codes.iter());
} }

View file

@ -13,7 +13,6 @@ pub use violation::{AutofixKind, Availability as AutofixAvailability};
mod ast; mod ast;
mod autofix; mod autofix;
pub mod cache;
mod checkers; mod checkers;
mod codes; mod codes;
mod cst; mod cst;

View file

@ -1,5 +1,6 @@
//! Settings for the `flake-annotations` plugin. //! Settings for the `flake-annotations` plugin.
use ruff_macros::CacheKey;
use ruff_macros::ConfigurationOptions; use ruff_macros::ConfigurationOptions;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -60,7 +61,7 @@ pub struct Options {
pub ignore_fully_untyped: Option<bool>, pub ignore_fully_untyped: Option<bool>,
} }
#[derive(Debug, Default, Hash)] #[derive(Debug, Default, CacheKey)]
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub struct Settings { pub struct Settings {
pub mypy_init_return: bool, pub mypy_init_return: bool,

View file

@ -1,6 +1,6 @@
//! Settings for the `flake8-bandit` plugin. //! Settings for the `flake8-bandit` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -45,7 +45,7 @@ pub struct Options {
pub check_typed_exception: Option<bool>, pub check_typed_exception: Option<bool>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct Settings { pub struct Settings {
pub hardcoded_tmp_directory: Vec<String>, pub hardcoded_tmp_directory: Vec<String>,
pub check_typed_exception: bool, pub check_typed_exception: bool,

View file

@ -1,6 +1,6 @@
//! Settings for the `flake8-bugbear` plugin. //! Settings for the `flake8-bugbear` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -26,7 +26,7 @@ pub struct Options {
pub extend_immutable_calls: Option<Vec<String>>, pub extend_immutable_calls: Option<Vec<String>>,
} }
#[derive(Debug, Default, Hash)] #[derive(Debug, Default, CacheKey)]
pub struct Settings { pub struct Settings {
pub extend_immutable_calls: Vec<String>, pub extend_immutable_calls: Vec<String>,
} }

View file

@ -1,6 +1,6 @@
//! Settings for the `flake8-builtins` plugin. //! Settings for the `flake8-builtins` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -22,7 +22,7 @@ pub struct Options {
pub builtins_ignorelist: Option<Vec<String>>, pub builtins_ignorelist: Option<Vec<String>>,
} }
#[derive(Debug, Default, Hash)] #[derive(Debug, Default, CacheKey)]
pub struct Settings { pub struct Settings {
pub builtins_ignorelist: Vec<String>, pub builtins_ignorelist: Vec<String>,
} }

View file

@ -1,6 +1,6 @@
//! Settings for the `flake8-comprehensions` plugin. //! Settings for the `flake8-comprehensions` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -22,7 +22,7 @@ pub struct Options {
pub allow_dict_calls_with_keyword_arguments: Option<bool>, pub allow_dict_calls_with_keyword_arguments: Option<bool>,
} }
#[derive(Debug, Default, Hash)] #[derive(Debug, Default, CacheKey)]
pub struct Settings { pub struct Settings {
pub allow_dict_calls_with_keyword_arguments: bool, pub allow_dict_calls_with_keyword_arguments: bool,
} }

View file

@ -1,6 +1,6 @@
//! Settings for the `flake8-errmsg` plugin. //! Settings for the `flake8-errmsg` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -18,7 +18,7 @@ pub struct Options {
pub max_string_length: Option<usize>, pub max_string_length: Option<usize>,
} }
#[derive(Debug, Default, Hash)] #[derive(Debug, Default, CacheKey)]
pub struct Settings { pub struct Settings {
pub max_string_length: usize, pub max_string_length: usize,
} }

View file

@ -1,6 +1,6 @@
//! Settings for the `flake8-implicit-str-concat` plugin. //! Settings for the `flake8-implicit-str-concat` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -32,7 +32,7 @@ pub struct Options {
pub allow_multiline: Option<bool>, pub allow_multiline: Option<bool>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct Settings { pub struct Settings {
pub allow_multiline: bool, pub allow_multiline: bool,
} }

View file

@ -1,14 +1,10 @@
//! Settings for import conventions. //! Settings for import conventions.
use std::hash::Hash; use ruff_macros::{CacheKey, ConfigurationOptions};
use ruff_macros::ConfigurationOptions;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::settings::hashable::HashableHashMap;
const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[ const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[
("altair", "alt"), ("altair", "alt"),
("matplotlib", "mpl"), ("matplotlib", "mpl"),
@ -64,9 +60,9 @@ pub struct Options {
pub extend_aliases: Option<FxHashMap<String, String>>, pub extend_aliases: Option<FxHashMap<String, String>>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct Settings { pub struct Settings {
pub aliases: HashableHashMap<String, String>, pub aliases: FxHashMap<String, String>,
} }
fn default_aliases() -> FxHashMap<String, String> { fn default_aliases() -> FxHashMap<String, String> {
@ -90,7 +86,7 @@ fn resolve_aliases(options: Options) -> FxHashMap<String, String> {
impl Default for Settings { impl Default for Settings {
fn default() -> Self { fn default() -> Self {
Self { Self {
aliases: default_aliases().into(), aliases: default_aliases(),
} }
} }
} }
@ -98,7 +94,7 @@ impl Default for Settings {
impl From<Options> for Settings { impl From<Options> for Settings {
fn from(options: Options) -> Self { fn from(options: Options) -> Self {
Self { Self {
aliases: resolve_aliases(options).into(), aliases: resolve_aliases(options),
} }
} }
} }
@ -106,7 +102,7 @@ impl From<Options> for Settings {
impl From<Settings> for Options { impl From<Settings> for Options {
fn from(settings: Settings) -> Self { fn from(settings: Settings) -> Self {
Self { Self {
aliases: Some(settings.aliases.into()), aliases: Some(settings.aliases),
extend_aliases: None, extend_aliases: None,
} }
} }

View file

@ -1,6 +1,6 @@
//! Settings for the `flake8-pytest-style` plugin. //! Settings for the `flake8-pytest-style` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -113,7 +113,7 @@ pub struct Options {
pub mark_parentheses: Option<bool>, pub mark_parentheses: Option<bool>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct Settings { pub struct Settings {
pub fixture_parentheses: bool, pub fixture_parentheses: bool,
pub parametrize_names_type: types::ParametrizeNameType, pub parametrize_names_type: types::ParametrizeNameType,

View file

@ -1,9 +1,10 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use ruff_macros::CacheKey;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Copy, Debug, CacheKey, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum ParametrizeNameType { pub enum ParametrizeNameType {
#[serde(rename = "csv")] #[serde(rename = "csv")]
Csv, Csv,
@ -29,7 +30,7 @@ impl Display for ParametrizeNameType {
} }
} }
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Copy, Debug, CacheKey, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum ParametrizeValuesType { pub enum ParametrizeValuesType {
#[serde(rename = "tuple")] #[serde(rename = "tuple")]
Tuple, Tuple,
@ -52,7 +53,7 @@ impl Display for ParametrizeValuesType {
} }
} }
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Copy, Debug, CacheKey, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum ParametrizeValuesRowType { pub enum ParametrizeValuesRowType {
#[serde(rename = "tuple")] #[serde(rename = "tuple")]
Tuple, Tuple,

View file

@ -1,10 +1,10 @@
//! Settings for the `flake8-quotes` plugin. //! Settings for the `flake8-quotes` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")] #[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum Quote { pub enum Quote {
/// Use single quotes. /// Use single quotes.
@ -71,7 +71,7 @@ pub struct Options {
pub avoid_escape: Option<bool>, pub avoid_escape: Option<bool>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct Settings { pub struct Settings {
pub inline_quotes: Quote, pub inline_quotes: Quote,
pub multiline_quotes: Quote, pub multiline_quotes: Quote,

View file

@ -1,6 +1,6 @@
//! Settings for the `flake8-self` plugin. //! Settings for the `flake8-self` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -28,7 +28,7 @@ pub struct Options {
pub ignore_names: Option<Vec<String>>, pub ignore_names: Option<Vec<String>>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct Settings { pub struct Settings {
pub ignore_names: Vec<String>, pub ignore_names: Vec<String>,
} }

View file

@ -1,4 +1,4 @@
use ruff_macros::{define_violation, derive_message_formats}; use ruff_macros::{define_violation, derive_message_formats, CacheKey};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use rustpython_parser::ast::{Alias, Expr, Located}; use rustpython_parser::ast::{Alias, Expr, Located};
use schemars::JsonSchema; use schemars::JsonSchema;
@ -7,12 +7,11 @@ use serde::{Deserialize, Serialize};
use crate::ast::types::{CallPath, Range}; use crate::ast::types::{CallPath, Range};
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::Diagnostic; use crate::registry::Diagnostic;
use crate::settings::hashable::HashableHashMap;
use crate::violation::Violation; use crate::violation::Violation;
pub type Settings = HashableHashMap<String, ApiBan>; pub type Settings = FxHashMap<String, ApiBan>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")] #[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub struct ApiBan { pub struct ApiBan {
/// The message to display when the API is used. /// The message to display when the API is used.
@ -147,8 +146,7 @@ mod tests {
msg: "Use typing_extensions.TypedDict instead.".to_string(), msg: "Use typing_extensions.TypedDict instead.".to_string(),
}, },
), ),
]) ]),
.into(),
..Default::default() ..Default::default()
}, },
..Settings::for_rules(vec![Rule::BannedApi]) ..Settings::for_rules(vec![Rule::BannedApi])

View file

@ -1,10 +1,12 @@
//! Rules from [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/). //! Rules from [flake8-tidy-imports](https://pypi.org/project/flake8-tidy-imports/).
use ruff_macros::CacheKey;
pub mod options; pub mod options;
pub mod banned_api; pub mod banned_api;
pub mod relative_imports; pub mod relative_imports;
#[derive(Debug, Hash, Default)] #[derive(Debug, CacheKey, Default)]
pub struct Settings { pub struct Settings {
pub ban_relative_imports: relative_imports::Settings, pub ban_relative_imports: relative_imports::Settings,
pub banned_api: banned_api::Settings, pub banned_api: banned_api::Settings,

View file

@ -48,7 +48,7 @@ impl From<Options> for Settings {
fn from(options: Options) -> Self { fn from(options: Options) -> Self {
Self { Self {
ban_relative_imports: options.ban_relative_imports.unwrap_or(Strictness::Parents), ban_relative_imports: options.ban_relative_imports.unwrap_or(Strictness::Parents),
banned_api: options.banned_api.unwrap_or_default().into(), banned_api: options.banned_api.unwrap_or_default(),
} }
} }
} }
@ -57,7 +57,7 @@ impl From<Settings> for Options {
fn from(settings: Settings) -> Self { fn from(settings: Settings) -> Self {
Self { Self {
ban_relative_imports: Some(settings.ban_relative_imports), ban_relative_imports: Some(settings.ban_relative_imports),
banned_api: Some(settings.banned_api.into()), banned_api: Some(settings.banned_api),
} }
} }
} }

View file

@ -2,7 +2,7 @@ use rustpython_parser::ast::{Stmt, StmtKind};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use ruff_macros::{define_violation, derive_message_formats}; use ruff_macros::{define_violation, derive_message_formats, CacheKey};
use ruff_python::identifiers::is_module_name; use ruff_python::identifiers::is_module_name;
use crate::ast::helpers::{create_stmt, from_relative_import, unparse_stmt}; use crate::ast::helpers::{create_stmt, from_relative_import, unparse_stmt};
@ -15,7 +15,7 @@ use crate::violation::{AutofixKind, Availability, Violation};
pub type Settings = Strictness; pub type Settings = Strictness;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema, Default)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey, JsonSchema, Default)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")] #[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum Strictness { pub enum Strictness {
/// Ban imports that extend into the parent module or beyond. /// Ban imports that extend into the parent module or beyond.

View file

@ -1,6 +1,6 @@
//! Settings for the `flake8-type-checking` plugin. //! Settings for the `flake8-type-checking` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -36,7 +36,7 @@ pub struct Options {
pub exempt_modules: Option<Vec<String>>, pub exempt_modules: Option<Vec<String>>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct Settings { pub struct Settings {
pub strict: bool, pub strict: bool,
pub exempt_modules: Vec<String>, pub exempt_modules: Vec<String>,

View file

@ -1,6 +1,6 @@
//! Settings for the `flake8-unused-arguments` plugin. //! Settings for the `flake8-unused-arguments` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -22,7 +22,7 @@ pub struct Options {
pub ignore_variadic_names: Option<bool>, pub ignore_variadic_names: Option<bool>,
} }
#[derive(Debug, Default, Hash)] #[derive(Debug, Default, CacheKey)]
pub struct Settings { pub struct Settings {
pub ignore_variadic_names: bool, pub ignore_variadic_names: bool,
} }

View file

@ -3,6 +3,7 @@ use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use log::debug; use log::debug;
use ruff_macros::CacheKey;
use ruff_python::sys::KNOWN_STANDARD_LIBRARY; use ruff_python::sys::KNOWN_STANDARD_LIBRARY;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -12,7 +13,17 @@ use super::types::{ImportBlock, Importable};
use crate::settings::types::PythonVersion; use crate::settings::types::PythonVersion;
#[derive( #[derive(
Debug, PartialOrd, Ord, PartialEq, Eq, Clone, Serialize, Deserialize, JsonSchema, Hash, EnumIter, Debug,
PartialOrd,
Ord,
PartialEq,
Eq,
Clone,
Serialize,
Deserialize,
JsonSchema,
CacheKey,
EnumIter,
)] )]
#[serde(deny_unknown_fields, rename_all = "kebab-case")] #[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum ImportType { pub enum ImportType {

View file

@ -2,13 +2,13 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use super::categorize::ImportType; use super::categorize::ImportType;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")] #[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum RelativeImportsOrder { pub enum RelativeImportsOrder {
/// Place "closer" imports (fewer `.` characters, most local) before /// Place "closer" imports (fewer `.` characters, most local) before
@ -265,7 +265,7 @@ pub struct Options {
pub forced_separate: Option<Vec<String>>, pub forced_separate: Option<Vec<String>>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub struct Settings { pub struct Settings {
pub required_imports: BTreeSet<String>, pub required_imports: BTreeSet<String>,

View file

@ -1,6 +1,6 @@
//! Settings for the `mccabe` plugin. //! Settings for the `mccabe` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -25,7 +25,7 @@ pub struct Options {
pub max_complexity: Option<usize>, pub max_complexity: Option<usize>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct Settings { pub struct Settings {
pub max_complexity: usize, pub max_complexity: usize,
} }

View file

@ -1,6 +1,6 @@
//! Settings for the `pep8-naming` plugin. //! Settings for the `pep8-naming` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -69,7 +69,7 @@ pub struct Options {
pub staticmethod_decorators: Option<Vec<String>>, pub staticmethod_decorators: Option<Vec<String>>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct Settings { pub struct Settings {
pub ignore_names: Vec<String>, pub ignore_names: Vec<String>,
pub classmethod_decorators: Vec<String>, pub classmethod_decorators: Vec<String>,

View file

@ -1,6 +1,6 @@
//! Settings for the `pycodestyle` plugin. //! Settings for the `pycodestyle` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -32,7 +32,7 @@ pub struct Options {
pub ignore_overlong_task_comments: Option<bool>, pub ignore_overlong_task_comments: Option<bool>,
} }
#[derive(Debug, Default, Hash)] #[derive(Debug, Default, CacheKey)]
pub struct Settings { pub struct Settings {
pub max_doc_length: Option<usize>, pub max_doc_length: Option<usize>,
pub ignore_overlong_task_comments: bool, pub ignore_overlong_task_comments: bool,

View file

@ -2,13 +2,13 @@
use std::collections::BTreeSet; use std::collections::BTreeSet;
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::registry::Rule; use crate::registry::Rule;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, CacheKey)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")] #[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum Convention { pub enum Convention {
/// Use Google-style docstrings. /// Use Google-style docstrings.
@ -112,7 +112,7 @@ pub struct Options {
pub property_decorators: Option<Vec<String>>, pub property_decorators: Option<Vec<String>>,
} }
#[derive(Debug, Default, Hash)] #[derive(Debug, Default, CacheKey)]
pub struct Settings { pub struct Settings {
pub convention: Option<Convention>, pub convention: Option<Convention>,
pub ignore_decorators: BTreeSet<String>, pub ignore_decorators: BTreeSet<String>,

View file

@ -129,7 +129,7 @@ mod tests {
let diagnostics = test_path( let diagnostics = test_path(
Path::new("pyflakes/F841_0.py"), Path::new("pyflakes/F841_0.py"),
&settings::Settings { &settings::Settings {
dummy_variable_rgx: Regex::new(r"^z$").unwrap().into(), dummy_variable_rgx: Regex::new(r"^z$").unwrap(),
..settings::Settings::for_rule(Rule::UnusedVariable) ..settings::Settings::for_rule(Rule::UnusedVariable)
}, },
)?; )?;

View file

@ -99,7 +99,7 @@ mod tests {
let diagnostics = test_path( let diagnostics = test_path(
Path::new("pylint/too_many_arguments_params.py"), Path::new("pylint/too_many_arguments_params.py"),
&Settings { &Settings {
dummy_variable_rgx: Regex::new(r"skip_.*").unwrap().into(), dummy_variable_rgx: Regex::new(r"skip_.*").unwrap(),
..Settings::for_rules(vec![Rule::TooManyArguments]) ..Settings::for_rules(vec![Rule::TooManyArguments])
}, },
)?; )?;

View file

@ -1,3 +1,4 @@
use regex::Regex;
use std::{fmt, iter}; use std::{fmt, iter};
use rustpython_parser::ast::{Expr, ExprContext, ExprKind, Stmt, StmtKind, Withitem}; use rustpython_parser::ast::{Expr, ExprContext, ExprKind, Stmt, StmtKind, Withitem};
@ -12,7 +13,6 @@ use crate::ast::visitor;
use crate::ast::visitor::Visitor; use crate::ast::visitor::Visitor;
use crate::checkers::ast::Checker; use crate::checkers::ast::Checker;
use crate::registry::Diagnostic; use crate::registry::Diagnostic;
use crate::settings::hashable::HashableRegex;
use crate::violation::Violation; use crate::violation::Violation;
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)] #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Copy)]
@ -142,7 +142,7 @@ struct ExprWithInnerBindingKind<'a> {
} }
struct InnerForWithAssignTargetsVisitor<'a> { struct InnerForWithAssignTargetsVisitor<'a> {
dummy_variable_rgx: &'a HashableRegex, dummy_variable_rgx: &'a Regex,
assignment_targets: Vec<ExprWithInnerBindingKind<'a>>, assignment_targets: Vec<ExprWithInnerBindingKind<'a>>,
} }
@ -213,7 +213,7 @@ where
fn assignment_targets_from_expr<'a, U>( fn assignment_targets_from_expr<'a, U>(
expr: &'a Expr<U>, expr: &'a Expr<U>,
dummy_variable_rgx: &'a HashableRegex, dummy_variable_rgx: &'a Regex,
) -> Box<dyn Iterator<Item = &'a Expr<U>> + 'a> { ) -> Box<dyn Iterator<Item = &'a Expr<U>> + 'a> {
// The Box is necessary to ensure the match arms have the same return type - we can't use // The Box is necessary to ensure the match arms have the same return type - we can't use
// a cast to "impl Iterator", since at the time of writing that is only allowed for // a cast to "impl Iterator", since at the time of writing that is only allowed for
@ -266,7 +266,7 @@ fn assignment_targets_from_expr<'a, U>(
fn assignment_targets_from_with_items<'a, U>( fn assignment_targets_from_with_items<'a, U>(
items: &'a [Withitem<U>], items: &'a [Withitem<U>],
dummy_variable_rgx: &'a HashableRegex, dummy_variable_rgx: &'a Regex,
) -> impl Iterator<Item = &'a Expr<U>> + 'a { ) -> impl Iterator<Item = &'a Expr<U>> + 'a {
items items
.iter() .iter()
@ -280,7 +280,7 @@ fn assignment_targets_from_with_items<'a, U>(
fn assignment_targets_from_assign_targets<'a, U>( fn assignment_targets_from_assign_targets<'a, U>(
targets: &'a [Expr<U>], targets: &'a [Expr<U>],
dummy_variable_rgx: &'a HashableRegex, dummy_variable_rgx: &'a Regex,
) -> impl Iterator<Item = &'a Expr<U>> + 'a { ) -> impl Iterator<Item = &'a Expr<U>> + 'a {
targets targets
.iter() .iter()

View file

@ -1,13 +1,11 @@
//! Settings for the `pylint` plugin. //! Settings for the `pylint` plugin.
use std::hash::Hash;
use anyhow::anyhow; use anyhow::anyhow;
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use rustpython_parser::ast::Constant; use rustpython_parser::ast::Constant;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, CacheKey, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "kebab-case")] #[serde(deny_unknown_fields, rename_all = "kebab-case")]
pub enum ConstantType { pub enum ConstantType {
Bytes, Bytes,
@ -72,7 +70,7 @@ pub struct Options {
pub max_statements: Option<usize>, pub max_statements: Option<usize>,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct Settings { pub struct Settings {
pub allow_magic_value_types: Vec<ConstantType>, pub allow_magic_value_types: Vec<ConstantType>,
pub max_args: usize, pub max_args: usize,

View file

@ -1,6 +1,6 @@
//! Settings for the `pyupgrade` plugin. //! Settings for the `pyupgrade` plugin.
use ruff_macros::ConfigurationOptions; use ruff_macros::{CacheKey, ConfigurationOptions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -31,7 +31,7 @@ pub struct Options {
pub keep_runtime_typing: Option<bool>, pub keep_runtime_typing: Option<bool>,
} }
#[derive(Debug, Default, Hash)] #[derive(Debug, Default, CacheKey)]
pub struct Settings { pub struct Settings {
pub keep_runtime_typing: bool, pub keep_runtime_typing: bool,
} }

View file

@ -34,7 +34,7 @@ mod tests {
let diagnostics = test_path( let diagnostics = test_path(
Path::new("ruff/confusables.py"), Path::new("ruff/confusables.py"),
&settings::Settings { &settings::Settings {
allowed_confusables: FxHashSet::from_iter(['', 'ρ', '']).into(), allowed_confusables: FxHashSet::from_iter(['', 'ρ', '']),
..settings::Settings::for_rules(vec![ ..settings::Settings::for_rules(vec![
Rule::AmbiguousUnicodeCharacterString, Rule::AmbiguousUnicodeCharacterString,
Rule::AmbiguousUnicodeCharacterDocstring, Rule::AmbiguousUnicodeCharacterDocstring,

View file

@ -2,8 +2,8 @@ use once_cell::sync::Lazy;
use path_absolutize::path_dedot; use path_absolutize::path_dedot;
use regex::Regex; use regex::Regex;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use std::collections::HashSet;
use super::hashable::{HashableGlobSet, HashableHashSet};
use super::types::{FilePattern, PythonVersion}; use super::types::{FilePattern, PythonVersion};
use super::Settings; use super::Settings;
use crate::codes::{self, RuleCodePrefix}; use crate::codes::{self, RuleCodePrefix};
@ -15,6 +15,7 @@ use crate::rules::{
flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments, flake8_quotes, flake8_self, flake8_tidy_imports, flake8_type_checking, flake8_unused_arguments,
isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade,
}; };
use crate::settings::types::FilePatternSet;
pub const PREFIXES: &[RuleSelector] = &[ pub const PREFIXES: &[RuleSelector] = &[
prefix_to_selector(RuleCodePrefix::Pycodestyle(codes::Pycodestyle::E)), prefix_to_selector(RuleCodePrefix::Pycodestyle(codes::Pycodestyle::E)),
@ -59,12 +60,12 @@ impl Default for Settings {
fn default() -> Self { fn default() -> Self {
Self { Self {
rules: PREFIXES.iter().flat_map(IntoIterator::into_iter).into(), rules: PREFIXES.iter().flat_map(IntoIterator::into_iter).into(),
allowed_confusables: FxHashSet::from_iter([]).into(), allowed_confusables: FxHashSet::from_iter([]),
builtins: vec![], builtins: vec![],
dummy_variable_rgx: DUMMY_VARIABLE_RGX.clone().into(), dummy_variable_rgx: DUMMY_VARIABLE_RGX.clone(),
exclude: HashableGlobSet::new(EXCLUDE.clone()).unwrap(), exclude: FilePatternSet::try_from_vec(EXCLUDE.clone()).unwrap(),
extend_exclude: HashableGlobSet::empty(), extend_exclude: FilePatternSet::default(),
external: HashableHashSet::default(), external: HashSet::default(),
force_exclude: false, force_exclude: false,
ignore_init_module_imports: false, ignore_init_module_imports: false,
line_length: LINE_LENGTH, line_length: LINE_LENGTH,

View file

@ -1,6 +1,7 @@
use crate::fix; use crate::fix;
use ruff_macros::CacheKey;
#[derive(Debug, Copy, Clone, Hash, result_like::BoolLike)] #[derive(Debug, Copy, Clone, CacheKey, result_like::BoolLike)]
pub enum Autofix { pub enum Autofix {
Enabled, Enabled,
Disabled, Disabled,

View file

@ -1,179 +0,0 @@
use derivative::Derivative;
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use globset::{GlobMatcher, GlobSet};
use itertools::Itertools;
use regex::Regex;
use rustc_hash::{FxHashMap, FxHashSet};
use super::types::FilePattern;
#[derive(Debug)]
pub struct HashableRegex(Regex);
impl Hash for HashableRegex {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_str().hash(state);
}
}
impl From<Regex> for HashableRegex {
fn from(regex: Regex) -> Self {
Self(regex)
}
}
impl Deref for HashableRegex {
type Target = Regex;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug)]
pub struct HashableGlobMatcher(GlobMatcher);
impl From<GlobMatcher> for HashableGlobMatcher {
fn from(matcher: GlobMatcher) -> Self {
Self(matcher)
}
}
impl Deref for HashableGlobMatcher {
type Target = GlobMatcher;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Hash for HashableGlobMatcher {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.glob().hash(state);
}
}
#[derive(Derivative)]
#[derivative(Debug)]
pub struct HashableGlobSet {
patterns: Vec<FilePattern>,
#[derivative(Debug = "ignore")]
globset: GlobSet,
}
impl HashableGlobSet {
pub fn new(patterns: Vec<FilePattern>) -> anyhow::Result<Self> {
let mut builder = globset::GlobSetBuilder::new();
for pattern in &patterns {
pattern.clone().add_to(&mut builder)?;
}
let globset = builder.build()?;
Ok(HashableGlobSet { patterns, globset })
}
pub fn empty() -> Self {
Self {
patterns: Vec::new(),
globset: GlobSet::empty(),
}
}
}
impl Deref for HashableGlobSet {
type Target = GlobSet;
fn deref(&self) -> &Self::Target {
&self.globset
}
}
impl Hash for HashableGlobSet {
fn hash<H: Hasher>(&self, state: &mut H) {
for pattern in self.patterns.iter().sorted() {
pattern.hash(state);
}
}
}
#[derive(Debug, Clone)]
pub struct HashableHashSet<T>(FxHashSet<T>);
impl<T: Hash + Ord> Hash for HashableHashSet<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
for v in self.0.iter().sorted() {
v.hash(state);
}
}
}
impl<T> Default for HashableHashSet<T> {
fn default() -> Self {
Self(FxHashSet::default())
}
}
impl<T> From<FxHashSet<T>> for HashableHashSet<T> {
fn from(set: FxHashSet<T>) -> Self {
Self(set)
}
}
impl<T> From<HashableHashSet<T>> for FxHashSet<T> {
fn from(set: HashableHashSet<T>) -> Self {
set.0
}
}
impl<T> Deref for HashableHashSet<T> {
type Target = FxHashSet<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct HashableHashMap<K, V>(FxHashMap<K, V>);
impl<K: Hash + Ord, V: Hash> Hash for HashableHashMap<K, V> {
fn hash<H: Hasher>(&self, state: &mut H) {
for key in self.0.keys().sorted() {
key.hash(state);
self.0[key].hash(state);
}
}
}
impl<K, V> Default for HashableHashMap<K, V> {
fn default() -> Self {
Self(FxHashMap::default())
}
}
impl<K, V> From<FxHashMap<K, V>> for HashableHashMap<K, V> {
fn from(map: FxHashMap<K, V>) -> Self {
Self(map)
}
}
impl<K, V> From<HashableHashMap<K, V>> for FxHashMap<K, V> {
fn from(map: HashableHashMap<K, V>) -> Self {
map.0
}
}
impl<K, V> Deref for HashableHashMap<K, V> {
type Target = FxHashMap<K, V>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<K, V> DerefMut for HashableHashMap<K, V> {
fn deref_mut(&mut self) -> &mut <Self as Deref>::Target {
&mut self.0
}
}

View file

@ -5,13 +5,13 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use globset::Glob; use globset::{Glob, GlobMatcher};
use regex::Regex;
use ruff_cache::cache_dir;
use rustc_hash::{FxHashMap, FxHashSet}; use rustc_hash::{FxHashMap, FxHashSet};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use self::hashable::{HashableGlobMatcher, HashableGlobSet, HashableHashSet, HashableRegex};
use self::rule_table::RuleTable; use self::rule_table::RuleTable;
use crate::cache::cache_dir;
use crate::registry::{Rule, RuleNamespace, INCOMPATIBLE_CODES}; use crate::registry::{Rule, RuleNamespace, INCOMPATIBLE_CODES};
use crate::rule_selector::{RuleSelector, Specificity}; use crate::rule_selector::{RuleSelector, Specificity};
use crate::rules::{ use crate::rules::{
@ -21,13 +21,13 @@ use crate::rules::{
isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade, isort, mccabe, pep8_naming, pycodestyle, pydocstyle, pylint, pyupgrade,
}; };
use crate::settings::configuration::Configuration; use crate::settings::configuration::Configuration;
use crate::settings::types::{PerFileIgnore, PythonVersion, SerializationFormat}; use crate::settings::types::{FilePatternSet, PerFileIgnore, PythonVersion, SerializationFormat};
use crate::warn_user_once; use crate::warn_user_once;
use ruff_macros::CacheKey;
pub mod configuration; pub mod configuration;
pub mod defaults; pub mod defaults;
pub mod flags; pub mod flags;
pub mod hashable;
pub mod options; pub mod options;
pub mod options_base; pub mod options_base;
pub mod pyproject; pub mod pyproject;
@ -74,31 +74,27 @@ pub struct CliSettings {
pub update_check: bool, pub update_check: bool,
} }
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
#[allow(clippy::struct_excessive_bools)] #[allow(clippy::struct_excessive_bools)]
pub struct Settings { pub struct Settings {
pub rules: RuleTable, pub rules: RuleTable,
pub per_file_ignores: Vec<( pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, FxHashSet<Rule>)>,
HashableGlobMatcher,
HashableGlobMatcher,
HashableHashSet<Rule>,
)>,
pub show_source: bool, pub show_source: bool,
pub target_version: PythonVersion, pub target_version: PythonVersion,
// Resolver settings // Resolver settings
pub exclude: HashableGlobSet, pub exclude: FilePatternSet,
pub extend_exclude: HashableGlobSet, pub extend_exclude: FilePatternSet,
pub force_exclude: bool, pub force_exclude: bool,
pub respect_gitignore: bool, pub respect_gitignore: bool,
pub project_root: PathBuf, pub project_root: PathBuf,
// Rule-specific settings // Rule-specific settings
pub allowed_confusables: HashableHashSet<char>, pub allowed_confusables: FxHashSet<char>,
pub builtins: Vec<String>, pub builtins: Vec<String>,
pub dummy_variable_rgx: HashableRegex, pub dummy_variable_rgx: Regex,
pub external: HashableHashSet<String>, pub external: FxHashSet<String>,
pub ignore_init_module_imports: bool, pub ignore_init_module_imports: bool,
pub line_length: usize, pub line_length: usize,
pub namespace_packages: Vec<PathBuf>, pub namespace_packages: Vec<PathBuf>,
@ -146,18 +142,16 @@ impl Settings {
allowed_confusables: config allowed_confusables: config
.allowed_confusables .allowed_confusables
.map(FxHashSet::from_iter) .map(FxHashSet::from_iter)
.unwrap_or_default() .unwrap_or_default(),
.into(),
builtins: config.builtins.unwrap_or_default(), builtins: config.builtins.unwrap_or_default(),
dummy_variable_rgx: config dummy_variable_rgx: config
.dummy_variable_rgx .dummy_variable_rgx
.unwrap_or_else(|| defaults::DUMMY_VARIABLE_RGX.clone()) .unwrap_or_else(|| defaults::DUMMY_VARIABLE_RGX.clone()),
.into(), exclude: FilePatternSet::try_from_vec(
exclude: HashableGlobSet::new(
config.exclude.unwrap_or_else(|| defaults::EXCLUDE.clone()), config.exclude.unwrap_or_else(|| defaults::EXCLUDE.clone()),
)?, )?,
extend_exclude: HashableGlobSet::new(config.extend_exclude)?, extend_exclude: FilePatternSet::try_from_vec(config.extend_exclude)?,
external: FxHashSet::from_iter(config.external.unwrap_or_default()).into(), external: FxHashSet::from_iter(config.external.unwrap_or_default()),
force_exclude: config.force_exclude.unwrap_or(false), force_exclude: config.force_exclude.unwrap_or(false),
@ -414,13 +408,7 @@ impl From<&Configuration> for RuleTable {
/// Given a list of patterns, create a `GlobSet`. /// Given a list of patterns, create a `GlobSet`.
pub fn resolve_per_file_ignores( pub fn resolve_per_file_ignores(
per_file_ignores: Vec<PerFileIgnore>, per_file_ignores: Vec<PerFileIgnore>,
) -> Result< ) -> Result<Vec<(GlobMatcher, GlobMatcher, FxHashSet<Rule>)>> {
Vec<(
HashableGlobMatcher,
HashableGlobMatcher,
HashableHashSet<Rule>,
)>,
> {
per_file_ignores per_file_ignores
.into_iter() .into_iter()
.map(|per_file_ignore| { .map(|per_file_ignore| {
@ -431,7 +419,7 @@ pub fn resolve_per_file_ignores(
// Construct basename matcher. // Construct basename matcher.
let basename = Glob::new(&per_file_ignore.basename)?.compile_matcher(); let basename = Glob::new(&per_file_ignore.basename)?.compile_matcher();
Ok((absolute.into(), basename.into(), per_file_ignore.rules)) Ok((absolute, basename, per_file_ignore.rules))
}) })
.collect() .collect()
} }

View file

@ -1,23 +1,23 @@
use std::collections::hash_map; use std::collections::{hash_map, HashMap};
use ruff_macros::CacheKey;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use super::hashable::HashableHashMap;
use crate::registry::Rule; use crate::registry::Rule;
/// A table to keep track of which rules are enabled /// A table to keep track of which rules are enabled
/// and Whether they should be autofixed. /// and Whether they should be autofixed.
#[derive(Debug, Hash)] #[derive(Debug, CacheKey)]
pub struct RuleTable { pub struct RuleTable {
/// Maps rule codes to a boolean indicating if the rule should be autofixed. /// Maps rule codes to a boolean indicating if the rule should be autofixed.
enabled: HashableHashMap<Rule, bool>, enabled: FxHashMap<Rule, bool>,
} }
impl RuleTable { impl RuleTable {
/// Creates a new empty rule table. /// Creates a new empty rule table.
pub fn empty() -> Self { pub fn empty() -> Self {
Self { Self {
enabled: HashableHashMap::default(), enabled: HashMap::default(),
} }
} }
@ -53,8 +53,6 @@ impl<I: IntoIterator<Item = Rule>> From<I> for RuleTable {
for code in codes { for code in codes {
enabled.insert(code, true); enabled.insert(code, true);
} }
Self { Self { enabled }
enabled: enabled.into(),
}
} }
} }

View file

@ -1,22 +1,23 @@
use std::hash::Hash; use std::hash::{Hash, Hasher};
use std::ops::Deref; use std::ops::Deref;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Result};
use clap::ValueEnum; use clap::ValueEnum;
use globset::{Glob, GlobSetBuilder}; use globset::{Glob, GlobSet, GlobSetBuilder};
use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_macros::CacheKey;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{de, Deserialize, Deserializer, Serialize}; use serde::{de, Deserialize, Deserializer, Serialize};
use super::hashable::HashableHashSet;
use crate::registry::Rule; use crate::registry::Rule;
use crate::rule_selector::RuleSelector; use crate::rule_selector::RuleSelector;
use crate::{fs, warn_user_once}; use crate::{fs, warn_user_once};
#[derive( #[derive(
Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema, Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize, JsonSchema, CacheKey,
)] )]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
pub enum PythonVersion { pub enum PythonVersion {
@ -61,7 +62,7 @@ impl PythonVersion {
} }
} }
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] #[derive(Debug, Clone, CacheKey, PartialEq, PartialOrd, Eq, Ord)]
pub enum FilePattern { pub enum FilePattern {
Builtin(&'static str), Builtin(&'static str),
User(String, PathBuf), User(String, PathBuf),
@ -97,11 +98,51 @@ impl FromStr for FilePattern {
} }
} }
#[derive(Debug, Clone, Default)]
pub struct FilePatternSet {
set: GlobSet,
cache_key: u64,
}
impl FilePatternSet {
pub fn try_from_vec(patterns: Vec<FilePattern>) -> Result<Self, anyhow::Error> {
let mut builder = GlobSetBuilder::new();
let mut hasher = CacheKeyHasher::new();
for pattern in patterns {
pattern.cache_key(&mut hasher);
pattern.add_to(&mut builder)?;
}
let set = builder.build()?;
Ok(FilePatternSet {
set,
cache_key: hasher.finish(),
})
}
}
impl Deref for FilePatternSet {
type Target = GlobSet;
fn deref(&self) -> &Self::Target {
&self.set
}
}
impl CacheKey for FilePatternSet {
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_usize(self.set.len());
state.write_u64(self.cache_key);
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PerFileIgnore { pub struct PerFileIgnore {
pub(crate) basename: String, pub(crate) basename: String,
pub(crate) absolute: PathBuf, pub(crate) absolute: PathBuf,
pub(crate) rules: HashableHashSet<Rule>, pub(crate) rules: FxHashSet<Rule>,
} }
impl PerFileIgnore { impl PerFileIgnore {
@ -116,7 +157,7 @@ impl PerFileIgnore {
Self { Self {
basename: pattern, basename: pattern,
absolute, absolute,
rules: rules.into(), rules,
} }
} }
} }

View file

@ -0,0 +1,15 @@
[package]
name = "ruff_cache"
version = "0.0.0"
publish = false
edition = { workspace = true }
rust-version = { workspace = true }
[dependencies]
itertools = { workspace = true }
globset = { version = "0.4.9" }
regex = { workspace = true }
filetime = { version = "0.2.17" }
[dev-dependencies]
ruff_macros = { path = "../ruff_macros" }

View file

@ -0,0 +1,376 @@
use itertools::Itertools;
use regex::Regex;
use std::borrow::Cow;
use std::collections::hash_map::DefaultHasher;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, Default)]
pub struct CacheKeyHasher {
inner: DefaultHasher,
}
impl CacheKeyHasher {
pub fn new() -> Self {
Self {
inner: DefaultHasher::new(),
}
}
}
impl Deref for CacheKeyHasher {
type Target = DefaultHasher;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for CacheKeyHasher {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
/// A type that be used as part of a cache key.
///
/// A cache looks up artefacts by a cache key. Many cache keys are composed of sub-keys. For example,
/// caching the lint results of a file depend at least on the file content, the user settings, and linter version.
/// Types implementing the [`CacheKey`] trait can be used as part of a cache key by which artefacts are queried.
///
/// ## Implementing `CacheKey`
///
/// You can derive [`CacheKey`] with `#[derive(CacheKey)]` if all fields implement [`CacheKey`]. The resulting
/// cache key will be the combination of the values from calling `cache_key` on each field.
///
/// ```
/// # use ruff_macros::CacheKey;
///
/// #[derive(CacheKey)]
/// struct Test {
/// name: String,
/// version: u32,
/// }
/// ```
///
/// If you need more control over computing the cache key, you can of course implement the [`CacheKey]` yourself:
///
/// ```
/// use ruff_cache::{CacheKey, CacheKeyHasher};
///
/// struct Test {
/// name: String,
/// version: u32,
/// other: String
/// }
///
/// impl CacheKey for Test {
/// fn cache_key(&self, state: &mut CacheKeyHasher) {
/// self.name.cache_key(state);
/// self.version.cache_key(state);
/// }
/// }
/// ```
///
/// ## Portability
///
/// Ideally, the cache key is portable across platforms but this is not yet a strict requirement.
///
/// ## Using [`Hash`]
///
/// You can defer to the [`Hash`] implementation for non-composite types.
/// Be aware, that the [`Hash`] implementation may not be portable.
///
/// ## Why a new trait rather than reusing [`Hash`]?
/// The main reason is that hashes and cache keys have different constraints:
///
/// * Cache keys are less performance sensitive: Hashes must be super fast to compute for performant hashed-collections. That's
/// why some standard types don't implement [`Hash`] where it would be safe to to implement [`CacheKey`], e.g. `HashSet`
/// * Cache keys must be deterministic where hash keys do not have this constraint. That's why pointers don't implement [`CacheKey`] but they implement [`Hash`].
/// * Ideally, cache keys are portable
///
/// [`Hash`](std::hash::Hash)
pub trait CacheKey {
fn cache_key(&self, state: &mut CacheKeyHasher);
fn cache_key_slice(data: &[Self], state: &mut CacheKeyHasher)
where
Self: Sized,
{
for piece in data {
piece.cache_key(state);
}
}
}
impl CacheKey for bool {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_u8(u8::from(*self));
}
}
impl CacheKey for char {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_u32(*self as u32);
}
}
impl CacheKey for usize {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_usize(*self);
}
}
impl CacheKey for u128 {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_u128(*self);
}
}
impl CacheKey for u64 {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_u64(*self);
}
}
impl CacheKey for u32 {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_u32(*self);
}
}
impl CacheKey for u16 {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_u16(*self);
}
}
impl CacheKey for u8 {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_u8(*self);
}
}
impl CacheKey for isize {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_isize(*self);
}
}
impl CacheKey for i128 {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_i128(*self);
}
}
impl CacheKey for i64 {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_i64(*self);
}
}
impl CacheKey for i32 {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_i32(*self);
}
}
impl CacheKey for i16 {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_i16(*self);
}
}
impl CacheKey for i8 {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_i8(*self);
}
}
macro_rules! impl_cache_key_tuple {
() => (
impl CacheKey for () {
#[inline]
fn cache_key(&self, _state: &mut CacheKeyHasher) {}
}
);
( $($name:ident)+) => (
impl<$($name: CacheKey),+> CacheKey for ($($name,)+) where last_type!($($name,)+): ?Sized {
#[allow(non_snake_case)]
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
let ($(ref $name,)+) = *self;
$($name.cache_key(state);)+
}
}
);
}
macro_rules! last_type {
($a:ident,) => { $a };
($a:ident, $($rest_a:ident,)+) => { last_type!($($rest_a,)+) };
}
impl_cache_key_tuple! {}
impl_cache_key_tuple! { T }
impl_cache_key_tuple! { T B }
impl_cache_key_tuple! { T B C }
impl_cache_key_tuple! { T B C D }
impl_cache_key_tuple! { T B C D E }
impl_cache_key_tuple! { T B C D E F }
impl_cache_key_tuple! { T B C D E F G }
impl_cache_key_tuple! { T B C D E F G H }
impl_cache_key_tuple! { T B C D E F G H I }
impl_cache_key_tuple! { T B C D E F G H I J }
impl_cache_key_tuple! { T B C D E F G H I J K }
impl_cache_key_tuple! { T B C D E F G H I J K L }
impl CacheKey for str {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
self.hash(&mut **state);
}
}
impl CacheKey for String {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
self.hash(&mut **state);
}
}
impl<T: CacheKey> CacheKey for Option<T> {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
match self {
None => state.write_usize(0),
Some(value) => {
state.write_usize(1);
value.cache_key(state);
}
}
}
}
impl<T: CacheKey> CacheKey for [T] {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_usize(self.len());
CacheKey::cache_key_slice(self, state);
}
}
impl<T: ?Sized + CacheKey> CacheKey for &T {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
(**self).cache_key(state);
}
}
impl<T: ?Sized + CacheKey> CacheKey for &mut T {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
(**self).cache_key(state);
}
}
impl<T> CacheKey for Vec<T>
where
T: CacheKey,
{
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_usize(self.len());
CacheKey::cache_key_slice(self, state);
}
}
impl<K, V, S> CacheKey for HashMap<K, V, S>
where
K: CacheKey + Ord,
V: CacheKey,
{
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_usize(self.len());
for (key, value) in self
.iter()
.sorted_by(|(left, _), (right, _)| left.cmp(right))
{
key.cache_key(state);
value.cache_key(state);
}
}
}
impl<V: CacheKey + Ord, S> CacheKey for HashSet<V, S> {
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_usize(self.len());
for value in self.iter().sorted() {
value.cache_key(state);
}
}
}
impl<V: CacheKey> CacheKey for BTreeSet<V> {
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_usize(self.len());
for item in self {
item.cache_key(state);
}
}
}
impl<K: CacheKey + Ord, V: CacheKey> CacheKey for BTreeMap<K, V> {
fn cache_key(&self, state: &mut CacheKeyHasher) {
state.write_usize(self.len());
for (key, value) in self {
key.cache_key(state);
value.cache_key(state);
}
}
}
impl CacheKey for Path {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
self.hash(&mut **state);
}
}
impl CacheKey for PathBuf {
#[inline]
fn cache_key(&self, state: &mut CacheKeyHasher) {
self.hash(&mut **state);
}
}
impl<V: ?Sized> CacheKey for Cow<'_, V>
where
V: CacheKey + ToOwned,
{
fn cache_key(&self, state: &mut CacheKeyHasher) {
(**self).cache_key(state);
}
}
impl CacheKey for Regex {
fn cache_key(&self, state: &mut CacheKeyHasher) {
self.as_str().cache_key(state);
}
}

View file

@ -0,0 +1,9 @@
use crate::{CacheKey, CacheKeyHasher};
use filetime::FileTime;
use std::hash::Hash;
impl CacheKey for FileTime {
fn cache_key(&self, state: &mut CacheKeyHasher) {
self.hash(&mut **state);
}
}

View file

@ -0,0 +1,14 @@
use crate::{CacheKey, CacheKeyHasher};
use globset::{Glob, GlobMatcher};
impl CacheKey for GlobMatcher {
fn cache_key(&self, state: &mut CacheKeyHasher) {
self.glob().cache_key(state);
}
}
impl CacheKey for Glob {
fn cache_key(&self, state: &mut CacheKeyHasher) {
self.glob().cache_key(state);
}
}

View file

@ -1,3 +1,9 @@
mod cache_key;
pub mod filetime;
pub mod globset;
pub use cache_key::{CacheKey, CacheKeyHasher};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
pub const CACHE_DIR_NAME: &str = ".ruff_cache"; pub const CACHE_DIR_NAME: &str = ".ruff_cache";

View file

@ -0,0 +1,108 @@
use ruff_cache::{CacheKey, CacheKeyHasher};
use ruff_macros::CacheKey;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[derive(CacheKey, Hash)]
struct UnitStruct;
#[derive(CacheKey, Hash)]
struct NamedFieldsStruct {
a: String,
b: String,
}
#[derive(CacheKey, Hash)]
struct UnnamedFieldsStruct(String, String);
#[derive(CacheKey, Hash)]
enum Enum {
Unit,
UnnamedFields(String, String),
NamedFields { a: String, b: String },
}
#[test]
fn unit_struct_cache_key() {
let mut key = CacheKeyHasher::new();
UnitStruct.cache_key(&mut key);
let mut hash = DefaultHasher::new();
UnitStruct.hash(&mut hash);
assert_eq!(hash.finish(), key.finish());
}
#[test]
fn named_field_struct() {
let mut key = CacheKeyHasher::new();
let named_fields = NamedFieldsStruct {
a: "Hello".into(),
b: "World".into(),
};
named_fields.cache_key(&mut key);
let mut hash = DefaultHasher::new();
named_fields.hash(&mut hash);
assert_eq!(hash.finish(), key.finish());
}
#[test]
fn unnamed_field_struct() {
let mut key = CacheKeyHasher::new();
let unnamed_fields = UnnamedFieldsStruct("Hello".into(), "World".into());
unnamed_fields.cache_key(&mut key);
let mut hash = DefaultHasher::new();
unnamed_fields.hash(&mut hash);
assert_eq!(hash.finish(), key.finish());
}
#[test]
fn enum_unit_variant() {
let mut key = CacheKeyHasher::new();
let variant = Enum::Unit;
variant.cache_key(&mut key);
let mut hash = DefaultHasher::new();
variant.hash(&mut hash);
assert_eq!(hash.finish(), key.finish());
}
#[test]
fn enum_named_fields_variant() {
let mut key = CacheKeyHasher::new();
let variant = Enum::NamedFields {
a: "Hello".to_string(),
b: "World".to_string(),
};
variant.cache_key(&mut key);
let mut hash = DefaultHasher::new();
variant.hash(&mut hash);
assert_eq!(hash.finish(), key.finish());
}
#[test]
fn enum_unnamed_fields_variant() {
let mut key = CacheKeyHasher::new();
let variant = Enum::UnnamedFields("Hello".to_string(), "World".to_string());
variant.cache_key(&mut key);
let mut hash = DefaultHasher::new();
variant.hash(&mut hash);
assert_eq!(hash.finish(), key.finish());
}

View file

@ -25,6 +25,7 @@ doc = false
[dependencies] [dependencies]
ruff = { path = "../ruff" } ruff = { path = "../ruff" }
ruff_cache = { path = "../ruff_cache" }
annotate-snippets = { version = "0.9.1", features = ["color"] } annotate-snippets = { version = "0.9.1", features = ["color"] }
anyhow = { workspace = true } anyhow = { workspace = true }

View file

@ -1,6 +1,5 @@
use std::collections::hash_map::DefaultHasher;
use std::fs; use std::fs;
use std::hash::{Hash, Hasher}; use std::hash::Hasher;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
@ -10,6 +9,7 @@ use log::error;
use path_absolutize::Absolutize; use path_absolutize::Absolutize;
use ruff::message::Message; use ruff::message::Message;
use ruff::settings::{flags, AllSettings, Settings}; use ruff::settings::{flags, AllSettings, Settings};
use ruff_cache::{CacheKey, CacheKeyHasher};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::fs::PermissionsExt; use std::os::unix::fs::PermissionsExt;
@ -37,18 +37,18 @@ fn cache_key<P: AsRef<Path>>(
settings: &Settings, settings: &Settings,
autofix: flags::Autofix, autofix: flags::Autofix,
) -> u64 { ) -> u64 {
let mut hasher = DefaultHasher::new(); let mut hasher = CacheKeyHasher::new();
CARGO_PKG_VERSION.hash(&mut hasher); CARGO_PKG_VERSION.cache_key(&mut hasher);
path.as_ref().absolutize().unwrap().hash(&mut hasher); path.as_ref().absolutize().unwrap().cache_key(&mut hasher);
package package
.as_ref() .as_ref()
.map(|path| path.as_ref().absolutize().unwrap()) .map(|path| path.as_ref().absolutize().unwrap())
.hash(&mut hasher); .cache_key(&mut hasher);
FileTime::from_last_modification_time(metadata).hash(&mut hasher); FileTime::from_last_modification_time(metadata).cache_key(&mut hasher);
#[cfg(unix)] #[cfg(unix)]
metadata.permissions().mode().hash(&mut hasher); metadata.permissions().mode().cache_key(&mut hasher);
settings.hash(&mut hasher); settings.cache_key(&mut hasher);
autofix.hash(&mut hasher); autofix.cache_key(&mut hasher);
hasher.finish() hasher.finish()
} }

View file

@ -6,9 +6,9 @@ use colored::Colorize;
use path_absolutize::path_dedot; use path_absolutize::path_dedot;
use walkdir::WalkDir; use walkdir::WalkDir;
use ruff::cache::CACHE_DIR_NAME;
use ruff::fs; use ruff::fs;
use ruff::logging::LogLevel; use ruff::logging::LogLevel;
use ruff_cache::CACHE_DIR_NAME;
/// Clear any caches in the current directory or any subdirectories. /// Clear any caches in the current directory or any subdirectories.
pub fn clean(level: LogLevel) -> Result<()> { pub fn clean(level: LogLevel) -> Result<()> {

View file

@ -15,3 +15,4 @@ quote = { version = "1.0.21" }
syn = { version = "1.0.103", features = ["derive", "parsing", "extra-traits"] } syn = { version = "1.0.103", features = ["derive", "parsing", "extra-traits"] }
textwrap = { version = "0.16.0" } textwrap = { version = "0.16.0" }
itertools = { workspace = true } itertools = { workspace = true }

View 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
}
}
))
}

View file

@ -1,8 +1,10 @@
//! This crate implements internal macros for the `ruff` library. //! This crate implements internal macros for the `ruff` library.
use crate::cache_key::derive_cache_key;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput, ItemFn}; use syn::{parse_macro_input, DeriveInput, ItemFn};
mod cache_key;
mod config; mod config;
mod define_violation; mod define_violation;
mod derive_message_formats; mod derive_message_formats;
@ -20,6 +22,16 @@ pub fn derive_config(input: proc_macro::TokenStream) -> proc_macro::TokenStream
.into() .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] #[proc_macro]
pub fn register_rules(item: proc_macro::TokenStream) -> proc_macro::TokenStream { pub fn register_rules(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let mapping = parse_macro_input!(item as register_rules::Input); let mapping = parse_macro_input!(item as register_rules::Input);

View file

@ -56,6 +56,7 @@ pub fn register_rules(input: &Input) -> proc_macro2::TokenStream {
Hash, Hash,
PartialOrd, PartialOrd,
Ord, Ord,
::ruff_macros::CacheKey,
AsRefStr, AsRefStr,
::strum_macros::IntoStaticStr, ::strum_macros::IntoStaticStr,
)] )]