From 8c18b28bc4e0379677771f7048664d4f10f31c8f Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Sun, 15 Jan 2023 08:21:06 +0100 Subject: [PATCH] Derive Hash instead of implementing it by hand The caching mechanism of the CLI (ruff_cli::cache) relies on ruff::settings::Settings implementing the Hash trait. The ruff::settings::Settings struct previously couldn't automatically derive the Hash implementation via the #[derive(Hash)] macro attribute since some of its field types intentionally[1][2] don't implement Hash (namely regex::Regex, globset::GlobMatcher and globset::GlobSet and HashMap and HashSet from the standard library). The code therefore previously implemented the Hash trait by hand for the whole struct. Implementing Hash by hand for structs that are subject to change is a bad idea since it's very easy to forget to update the Hash implementation when adding a new field to the struct. And the Hash implementation indeed was already incorrect by omitting several fields from the hash. This commit introduces wrapper types for Regex, GlobMatcher, GlobSet, HashSet & HashMap that implement Hash so that we can still add #[derive(Hash)] to the Settings struct, guaranteeing a correct hash implementation. [1]: https://github.com/rust-lang/regex/issues/364#issuecomment-301082076 [2]: The standard library doesn't impl Hash for HashSet presumably since sorted() requires an allocation and Hash implementations are generally expected to work without allocations. --- src/fs.rs | 10 +- src/noqa.rs | 15 +- .../flake8_import_conventions/settings.rs | 23 +-- src/rules/flake8_tidy_imports/mod.rs | 3 +- src/rules/flake8_tidy_imports/rules.rs | 8 +- src/rules/flake8_tidy_imports/settings.rs | 25 +-- src/rules/pyflakes/mod.rs | 2 +- src/rules/ruff/mod.rs | 2 +- src/settings/hashable.rs | 176 ++++++++++++++++++ src/settings/mod.rs | 148 ++++++--------- src/settings/types.rs | 15 +- 11 files changed, 279 insertions(+), 148 deletions(-) create mode 100644 src/settings/hashable.rs diff --git a/src/fs.rs b/src/fs.rs index 721a05c18a..a97c25ba1f 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -4,11 +4,11 @@ use std::io::{BufReader, Read}; use std::path::{Path, PathBuf}; use anyhow::{anyhow, Result}; -use globset::GlobMatcher; use path_absolutize::{path_dedot, Absolutize}; use rustc_hash::FxHashSet; use crate::registry::RuleCode; +use crate::settings::hashable::{HashableGlobMatcher, HashableHashSet}; /// Extract the absolute path and basename (as strings) from a Path. pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> { @@ -26,7 +26,11 @@ pub fn extract_path_names(path: &Path) -> Result<(&str, &str)> { /// Create a set with codes matching the pattern/code pairs. pub(crate) fn ignores_from_path<'a>( path: &Path, - pattern_code_pairs: &'a [(GlobMatcher, GlobMatcher, FxHashSet)], + pattern_code_pairs: &'a [( + HashableGlobMatcher, + HashableGlobMatcher, + HashableHashSet, + )], ) -> Result> { let (file_path, file_basename) = extract_path_names(path)?; Ok(pattern_code_pairs @@ -34,7 +38,7 @@ pub(crate) fn ignores_from_path<'a>( .filter(|(absolute, basename, _)| { basename.is_match(file_basename) || absolute.is_match(file_path) }) - .flat_map(|(_, _, codes)| codes) + .flat_map(|(_, _, codes)| codes.iter()) .collect()) } diff --git a/src/noqa.rs b/src/noqa.rs index 0143b00ca0..f164598f00 100644 --- a/src/noqa.rs +++ b/src/noqa.rs @@ -9,6 +9,7 @@ use regex::Regex; use rustc_hash::{FxHashMap, FxHashSet}; use crate::registry::{Diagnostic, RuleCode, CODE_REDIRECTS}; +use crate::settings::hashable::HashableHashSet; use crate::source_code::LineEnding; static NOQA_LINE_REGEX: Lazy = Lazy::new(|| { @@ -84,7 +85,7 @@ pub fn add_noqa( diagnostics: &[Diagnostic], contents: &str, noqa_line_for: &IntMap, - external: &FxHashSet, + external: &HashableHashSet, line_ending: &LineEnding, ) -> Result { let (count, output) = @@ -97,7 +98,7 @@ fn add_noqa_inner( diagnostics: &[Diagnostic], contents: &str, noqa_line_for: &IntMap, - external: &FxHashSet, + external: &HashableHashSet, line_ending: &LineEnding, ) -> (usize, String) { let mut matches_by_line: FxHashMap> = FxHashMap::default(); @@ -208,12 +209,12 @@ fn add_noqa_inner( #[cfg(test)] mod tests { use nohash_hasher::IntMap; - use rustc_hash::FxHashSet; use rustpython_parser::ast::Location; use crate::ast::types::Range; use crate::noqa::{add_noqa_inner, NOQA_LINE_REGEX}; use crate::registry::Diagnostic; + use crate::settings::hashable::HashableHashSet; use crate::source_code::LineEnding; use crate::violations; @@ -236,7 +237,7 @@ mod tests { let diagnostics = vec![]; let contents = "x = 1"; let noqa_line_for = IntMap::default(); - let external = FxHashSet::default(); + let external = HashableHashSet::default(); let (count, output) = add_noqa_inner( &diagnostics, contents, @@ -253,7 +254,7 @@ mod tests { )]; let contents = "x = 1"; let noqa_line_for = IntMap::default(); - let external = FxHashSet::default(); + let external = HashableHashSet::default(); let (count, output) = add_noqa_inner( &diagnostics, contents, @@ -276,7 +277,7 @@ mod tests { ]; let contents = "x = 1 # noqa: E741\n"; let noqa_line_for = IntMap::default(); - let external = FxHashSet::default(); + let external = HashableHashSet::default(); let (count, output) = add_noqa_inner( &diagnostics, contents, @@ -299,7 +300,7 @@ mod tests { ]; let contents = "x = 1 # noqa"; let noqa_line_for = IntMap::default(); - let external = FxHashSet::default(); + let external = HashableHashSet::default(); let (count, output) = add_noqa_inner( &diagnostics, contents, diff --git a/src/rules/flake8_import_conventions/settings.rs b/src/rules/flake8_import_conventions/settings.rs index 392864e0dd..e6474039bf 100644 --- a/src/rules/flake8_import_conventions/settings.rs +++ b/src/rules/flake8_import_conventions/settings.rs @@ -1,13 +1,14 @@ //! Settings for import conventions. -use std::hash::{Hash, Hasher}; +use std::hash::Hash; -use itertools::Itertools; use ruff_macros::ConfigurationOptions; use rustc_hash::FxHashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::settings::hashable::HashableHashMap; + const CONVENTIONAL_ALIASES: &[(&str, &str)] = &[ ("altair", "alt"), ("matplotlib.pyplot", "plt"), @@ -55,17 +56,9 @@ pub struct Options { pub extend_aliases: Option>, } -#[derive(Debug)] +#[derive(Debug, Hash)] pub struct Settings { - pub aliases: FxHashMap, -} - -impl Hash for Settings { - fn hash(&self, state: &mut H) { - for value in self.aliases.iter().sorted() { - value.hash(state); - } - } + pub aliases: HashableHashMap, } fn default_aliases() -> FxHashMap { @@ -89,7 +82,7 @@ fn resolve_aliases(options: Options) -> FxHashMap { impl Default for Settings { fn default() -> Self { Self { - aliases: default_aliases(), + aliases: default_aliases().into(), } } } @@ -97,7 +90,7 @@ impl Default for Settings { impl From for Settings { fn from(options: Options) -> Self { Self { - aliases: resolve_aliases(options), + aliases: resolve_aliases(options).into(), } } } @@ -105,7 +98,7 @@ impl From for Settings { impl From for Options { fn from(settings: Settings) -> Self { Self { - aliases: Some(settings.aliases), + aliases: Some(settings.aliases.into()), extend_aliases: None, } } diff --git a/src/rules/flake8_tidy_imports/mod.rs b/src/rules/flake8_tidy_imports/mod.rs index 3659aec2fb..a1cff8c576 100644 --- a/src/rules/flake8_tidy_imports/mod.rs +++ b/src/rules/flake8_tidy_imports/mod.rs @@ -64,7 +64,8 @@ mod tests { msg: "Use typing_extensions.TypedDict instead.".to_string(), }, ), - ]), + ]) + .into(), ..Default::default() }, ..Settings::for_rules(vec![RuleCode::TID251]) diff --git a/src/rules/flake8_tidy_imports/rules.rs b/src/rules/flake8_tidy_imports/rules.rs index b39901d1f3..a82646424c 100644 --- a/src/rules/flake8_tidy_imports/rules.rs +++ b/src/rules/flake8_tidy_imports/rules.rs @@ -1,10 +1,10 @@ -use rustc_hash::FxHashMap; use rustpython_ast::{Alias, Expr, Located, Stmt}; use super::settings::{BannedApi, Strictness}; use crate::ast::types::Range; use crate::checkers::ast::Checker; use crate::registry::Diagnostic; +use crate::settings::hashable::HashableHashMap; use crate::violations; /// TID252 @@ -31,7 +31,7 @@ pub fn banned_relative_import( pub fn name_is_banned( module: &str, name: &Alias, - banned_apis: &FxHashMap, + banned_apis: &HashableHashMap, ) -> Option { let full_name = format!("{module}.{}", &name.node.name); if let Some(ban) = banned_apis.get(&full_name) { @@ -50,7 +50,7 @@ pub fn name_is_banned( pub fn name_or_parent_is_banned( located: &Located, name: &str, - banned_apis: &FxHashMap, + banned_apis: &HashableHashMap, ) -> Option { let mut name = name; loop { @@ -75,7 +75,7 @@ pub fn name_or_parent_is_banned( /// TID251 pub fn banned_attribute_access(checker: &mut Checker, expr: &Expr) { if let Some(call_path) = checker.resolve_call_path(expr) { - for (banned_path, ban) in &checker.settings.flake8_tidy_imports.banned_api { + for (banned_path, ban) in checker.settings.flake8_tidy_imports.banned_api.iter() { if call_path == banned_path.split('.').collect::>() { checker.diagnostics.push(Diagnostic::new( violations::BannedApi { diff --git a/src/rules/flake8_tidy_imports/settings.rs b/src/rules/flake8_tidy_imports/settings.rs index b0ba8d00cf..cae7638a93 100644 --- a/src/rules/flake8_tidy_imports/settings.rs +++ b/src/rules/flake8_tidy_imports/settings.rs @@ -1,13 +1,14 @@ //! Settings for the `flake8-tidy-imports` plugin. -use std::hash::{Hash, Hasher}; +use std::hash::Hash; -use itertools::Itertools; use ruff_macros::ConfigurationOptions; use rustc_hash::FxHashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use crate::settings::hashable::HashableHashMap; + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash, JsonSchema)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub enum Strictness { @@ -59,17 +60,17 @@ pub struct Options { pub banned_api: Option>, } -#[derive(Debug)] +#[derive(Debug, Hash)] pub struct Settings { pub ban_relative_imports: Strictness, - pub banned_api: FxHashMap, + pub banned_api: HashableHashMap, } impl Default for Settings { fn default() -> Self { Self { ban_relative_imports: Strictness::Parents, - banned_api: FxHashMap::default(), + banned_api: HashableHashMap::default(), } } } @@ -78,7 +79,7 @@ impl From for Settings { fn from(options: Options) -> Self { Self { ban_relative_imports: options.ban_relative_imports.unwrap_or(Strictness::Parents), - banned_api: options.banned_api.unwrap_or_default(), + banned_api: options.banned_api.unwrap_or_default().into(), } } } @@ -87,17 +88,7 @@ impl From for Options { fn from(settings: Settings) -> Self { Self { ban_relative_imports: Some(settings.ban_relative_imports), - banned_api: Some(settings.banned_api), - } - } -} - -impl Hash for Settings { - fn hash(&self, state: &mut H) { - self.ban_relative_imports.hash(state); - for key in self.banned_api.keys().sorted() { - key.hash(state); - self.banned_api[key].hash(state); + banned_api: Some(settings.banned_api.into()), } } } diff --git a/src/rules/pyflakes/mod.rs b/src/rules/pyflakes/mod.rs index f041492f2e..dd49b906d9 100644 --- a/src/rules/pyflakes/mod.rs +++ b/src/rules/pyflakes/mod.rs @@ -121,7 +121,7 @@ mod tests { let diagnostics = test_path( Path::new("./resources/test/fixtures/pyflakes/F841_0.py"), &settings::Settings { - dummy_variable_rgx: Regex::new(r"^z$").unwrap(), + dummy_variable_rgx: Regex::new(r"^z$").unwrap().into(), ..settings::Settings::for_rule(RuleCode::F841) }, )?; diff --git a/src/rules/ruff/mod.rs b/src/rules/ruff/mod.rs index 6f9208bc20..07953fd2ad 100644 --- a/src/rules/ruff/mod.rs +++ b/src/rules/ruff/mod.rs @@ -31,7 +31,7 @@ mod tests { let diagnostics = test_path( Path::new("./resources/test/fixtures/ruff/confusables.py"), &settings::Settings { - allowed_confusables: FxHashSet::from_iter(['−', 'ρ', '∗']), + allowed_confusables: FxHashSet::from_iter(['−', 'ρ', '∗']).into(), ..settings::Settings::for_rules(vec![ RuleCode::RUF001, RuleCode::RUF002, diff --git a/src/settings/hashable.rs b/src/settings/hashable.rs new file mode 100644 index 0000000000..57b68d1b6f --- /dev/null +++ b/src/settings/hashable.rs @@ -0,0 +1,176 @@ +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(&self, state: &mut H) { + self.0.as_str().hash(state); + } +} + +impl From 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 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(&self, state: &mut H) { + self.0.glob().hash(state); + } +} + +#[derive(Debug)] +pub struct HashableGlobSet { + patterns: Vec, + globset: GlobSet, +} + +impl HashableGlobSet { + pub fn new(patterns: Vec) -> anyhow::Result { + 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(&self, state: &mut H) { + for pattern in self.patterns.iter().sorted() { + pattern.hash(state); + } + } +} + +#[derive(Debug, Clone)] +pub struct HashableHashSet(FxHashSet); + +impl Hash for HashableHashSet { + fn hash(&self, state: &mut H) { + for v in self.0.iter().sorted() { + v.hash(state); + } + } +} + +impl Default for HashableHashSet { + fn default() -> Self { + Self(FxHashSet::default()) + } +} + +impl From> for HashableHashSet { + fn from(set: FxHashSet) -> Self { + Self(set) + } +} + +impl From> for FxHashSet { + fn from(set: HashableHashSet) -> Self { + set.0 + } +} + +impl Deref for HashableHashSet { + type Target = FxHashSet; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Debug, Clone)] +pub struct HashableHashMap(FxHashMap); + +impl Hash for HashableHashMap { + fn hash(&self, state: &mut H) { + for key in self.0.keys().sorted() { + key.hash(state); + self.0[key].hash(state); + } + } +} + +impl Default for HashableHashMap { + fn default() -> Self { + Self(FxHashMap::default()) + } +} + +impl From> for HashableHashMap { + fn from(map: FxHashMap) -> Self { + Self(map) + } +} + +impl From> for FxHashMap { + fn from(map: HashableHashMap) -> Self { + map.0 + } +} + +impl Deref for HashableHashMap { + type Target = FxHashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for HashableHashMap { + fn deref_mut(&mut self) -> &mut ::Target { + &mut self.0 + } +} diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 680b4cbb64..70cec04578 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -2,21 +2,20 @@ //! command-line options. Structure is optimized for internal usage, as opposed //! to external visibility or parsing. -use std::hash::{Hash, Hasher}; use std::iter; use std::path::{Path, PathBuf}; use anyhow::{anyhow, Result}; use colored::Colorize; -use globset::{Glob, GlobMatcher, GlobSet}; +use globset::Glob; use itertools::Either::{Left, Right}; -use itertools::Itertools; use once_cell::sync::Lazy; #[cfg(test)] use path_absolutize::path_dedot; use regex::Regex; use rustc_hash::FxHashSet; +use self::hashable::{HashableGlobMatcher, HashableGlobSet, HashableHashSet, HashableRegex}; use crate::cache::cache_dir; use crate::registry::{RuleCode, RuleCodePrefix, SuffixLength, CATEGORIES, INCOMPATIBLE_CODES}; use crate::rules::{ @@ -32,6 +31,7 @@ use crate::warn_user_once; pub mod configuration; pub mod flags; +pub mod hashable; pub mod options; pub mod options_base; pub mod pyproject; @@ -74,22 +74,26 @@ pub struct CliSettings { pub update_check: bool, } -#[derive(Debug)] +#[derive(Debug, Hash)] #[allow(clippy::struct_excessive_bools)] pub struct Settings { - pub allowed_confusables: FxHashSet, + pub allowed_confusables: HashableHashSet, pub builtins: Vec, - pub dummy_variable_rgx: Regex, - pub enabled: FxHashSet, - pub exclude: GlobSet, - pub extend_exclude: GlobSet, - pub external: FxHashSet, - pub fixable: FxHashSet, + pub dummy_variable_rgx: HashableRegex, + pub enabled: HashableHashSet, + pub exclude: HashableGlobSet, + pub extend_exclude: HashableGlobSet, + pub external: HashableHashSet, + pub fixable: HashableHashSet, pub force_exclude: bool, pub ignore_init_module_imports: bool, pub line_length: usize, pub namespace_packages: Vec, - pub per_file_ignores: Vec<(GlobMatcher, GlobMatcher, FxHashSet)>, + pub per_file_ignores: Vec<( + HashableGlobMatcher, + HashableGlobMatcher, + HashableHashSet, + )>, pub required_version: Option, pub respect_gitignore: bool, pub show_source: bool, @@ -148,11 +152,13 @@ impl Settings { allowed_confusables: config .allowed_confusables .map(FxHashSet::from_iter) - .unwrap_or_default(), + .unwrap_or_default() + .into(), builtins: config.builtins.unwrap_or_default(), dummy_variable_rgx: config .dummy_variable_rgx - .unwrap_or_else(|| DEFAULT_DUMMY_VARIABLE_RGX.clone()), + .unwrap_or_else(|| DEFAULT_DUMMY_VARIABLE_RGX.clone()) + .into(), enabled: validate_enabled(resolve_codes( [RuleCodeSpec { select: &config @@ -184,17 +190,21 @@ impl Settings { Right(iter::empty()) }, ), - )), - exclude: resolve_globset(config.exclude.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()))?, - extend_exclude: resolve_globset(config.extend_exclude)?, - external: FxHashSet::from_iter(config.external.unwrap_or_default()), + )) + .into(), + exclude: HashableGlobSet::new( + config.exclude.unwrap_or_else(|| DEFAULT_EXCLUDE.clone()), + )?, + extend_exclude: HashableGlobSet::new(config.extend_exclude)?, + external: FxHashSet::from_iter(config.external.unwrap_or_default()).into(), fixable: resolve_codes( [RuleCodeSpec { select: &config.fixable.unwrap_or_else(|| CATEGORIES.to_vec()), ignore: &config.unfixable.unwrap_or_default(), }] .into_iter(), - ), + ) + .into(), force_exclude: config.force_exclude.unwrap_or(false), ignore_init_module_imports: config.ignore_init_module_imports.unwrap_or_default(), line_length: config.line_length.unwrap_or(88), @@ -250,14 +260,16 @@ impl Settings { #[cfg(test)] pub fn for_rule(rule_code: RuleCode) -> Self { Self { - allowed_confusables: FxHashSet::from_iter([]), + allowed_confusables: FxHashSet::from_iter([]).into(), builtins: vec![], - dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), - enabled: FxHashSet::from_iter([rule_code.clone()]), - exclude: GlobSet::empty(), - extend_exclude: GlobSet::empty(), - external: FxHashSet::default(), - fixable: FxHashSet::from_iter([rule_code]), + dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$") + .unwrap() + .into(), + enabled: FxHashSet::from_iter([rule_code.clone()]).into(), + exclude: HashableGlobSet::empty(), + extend_exclude: HashableGlobSet::empty(), + external: HashableHashSet::default(), + fixable: FxHashSet::from_iter([rule_code]).into(), force_exclude: false, ignore_init_module_imports: false, line_length: 88, @@ -291,14 +303,16 @@ impl Settings { #[cfg(test)] pub fn for_rules(rule_codes: Vec) -> Self { Self { - allowed_confusables: FxHashSet::from_iter([]), + allowed_confusables: HashableHashSet::default(), builtins: vec![], - dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$").unwrap(), - enabled: FxHashSet::from_iter(rule_codes.clone()), - exclude: GlobSet::empty(), - extend_exclude: GlobSet::empty(), - external: FxHashSet::default(), - fixable: FxHashSet::from_iter(rule_codes), + dummy_variable_rgx: Regex::new("^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$") + .unwrap() + .into(), + enabled: FxHashSet::from_iter(rule_codes.clone()).into(), + exclude: HashableGlobSet::empty(), + extend_exclude: HashableGlobSet::empty(), + external: HashableHashSet::default(), + fixable: FxHashSet::from_iter(rule_codes).into(), force_exclude: false, ignore_init_module_imports: false, line_length: 88, @@ -343,68 +357,16 @@ impl Settings { } } -impl Hash for Settings { - fn hash(&self, state: &mut H) { - // Add base properties in alphabetical order. - for confusable in &self.allowed_confusables { - confusable.hash(state); - } - self.builtins.hash(state); - self.dummy_variable_rgx.as_str().hash(state); - for value in self.enabled.iter().sorted() { - value.hash(state); - } - for value in self.external.iter().sorted() { - value.hash(state); - } - for value in self.fixable.iter().sorted() { - value.hash(state); - } - self.ignore_init_module_imports.hash(state); - self.line_length.hash(state); - for (absolute, basename, codes) in &self.per_file_ignores { - absolute.glob().hash(state); - basename.glob().hash(state); - for value in codes.iter().sorted() { - value.hash(state); - } - } - self.show_source.hash(state); - self.src.hash(state); - self.target_version.hash(state); - self.task_tags.hash(state); - self.typing_modules.hash(state); - // Add plugin properties in alphabetical order. - self.flake8_annotations.hash(state); - self.flake8_bandit.hash(state); - self.flake8_bugbear.hash(state); - self.flake8_errmsg.hash(state); - self.flake8_import_conventions.hash(state); - self.flake8_pytest_style.hash(state); - self.flake8_quotes.hash(state); - self.flake8_tidy_imports.hash(state); - self.flake8_unused_arguments.hash(state); - self.isort.hash(state); - self.mccabe.hash(state); - self.pep8_naming.hash(state); - self.pydocstyle.hash(state); - self.pyupgrade.hash(state); - } -} - -/// Given a list of patterns, create a `GlobSet`. -pub fn resolve_globset(patterns: Vec) -> Result { - let mut builder = globset::GlobSetBuilder::new(); - for pattern in patterns { - pattern.add_to(&mut builder)?; - } - builder.build().map_err(Into::into) -} - /// Given a list of patterns, create a `GlobSet`. pub fn resolve_per_file_ignores( per_file_ignores: Vec, -) -> Result)>> { +) -> Result< + Vec<( + HashableGlobMatcher, + HashableGlobMatcher, + HashableHashSet, + )>, +> { per_file_ignores .into_iter() .map(|per_file_ignore| { @@ -415,7 +377,7 @@ pub fn resolve_per_file_ignores( // Construct basename matcher. let basename = Glob::new(&per_file_ignore.basename)?.compile_matcher(); - Ok((absolute, basename, per_file_ignore.codes)) + Ok((absolute.into(), basename.into(), per_file_ignore.codes)) }) .collect() } diff --git a/src/settings/types.rs b/src/settings/types.rs index 636c8ddddd..829b79fd69 100644 --- a/src/settings/types.rs +++ b/src/settings/types.rs @@ -10,6 +10,7 @@ use rustc_hash::FxHashSet; use schemars::JsonSchema; use serde::{de, Deserialize, Deserializer, Serialize}; +use super::hashable::HashableHashSet; use crate::fs; use crate::registry::{RuleCode, RuleCodePrefix}; @@ -54,7 +55,7 @@ impl FromStr for PythonVersion { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord)] pub enum FilePattern { Builtin(&'static str), User(String, PathBuf), @@ -94,16 +95,16 @@ impl FromStr for FilePattern { pub struct PerFileIgnore { pub basename: String, pub absolute: PathBuf, - pub codes: FxHashSet, + pub codes: HashableHashSet, } impl PerFileIgnore { pub fn new(basename: String, absolute: PathBuf, prefixes: &[RuleCodePrefix]) -> Self { - let codes = prefixes.iter().flat_map(RuleCodePrefix::codes).collect(); + let codes: FxHashSet<_> = prefixes.iter().flat_map(RuleCodePrefix::codes).collect(); Self { basename, absolute, - codes, + codes: codes.into(), } } } @@ -150,7 +151,9 @@ impl FromStr for PatternPrefixPair { } } -#[derive(Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema)] +#[derive( + Clone, Copy, ValueEnum, PartialEq, Eq, Serialize, Deserialize, Debug, JsonSchema, Hash, +)] #[serde(rename_all = "kebab-case")] pub enum SerializationFormat { Text, @@ -167,7 +170,7 @@ impl Default for SerializationFormat { } } -#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Hash)] #[serde(try_from = "String")] pub struct Version(String);