diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 2c958ccbbc24..3d654f21de40 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -100,7 +100,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat ## `msrv` The minimum rust version that the project supports -**Default Value:** `None` (`Option`) +**Default Value:** `Msrv { stack: [] }` (`crate::Msrv`) --- **Affected lints:** diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 2d02ce8b4f0a..c3be203329bd 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -50,9 +50,6 @@ extern crate clippy_utils; #[macro_use] extern crate declare_clippy_lint; -use std::io; -use std::path::PathBuf; - use clippy_utils::msrvs::Msrv; use rustc_data_structures::fx::FxHashSet; use rustc_lint::{Lint, LintId}; @@ -362,7 +359,6 @@ mod zero_sized_map_values; // end lints modules, do not remove this comment, it’s used in `update_lints` use crate::utils::conf::metadata::get_configuration_metadata; -use crate::utils::conf::TryConf; pub use crate::utils::conf::{lookup_conf_file, Conf}; use crate::utils::FindAll; @@ -374,65 +370,13 @@ use crate::utils::FindAll; /// level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. /// /// Used in `./src/driver.rs`. -pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { +pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { // NOTE: Do not add any more pre-expansion passes. These should be removed eventually. - let msrv = Msrv::read(&conf.msrv, sess); - let msrv = move || msrv.clone(); + let msrv = || conf.msrv.clone(); store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes { msrv: msrv() })); } -#[doc(hidden)] -pub fn read_conf(sess: &Session, path: &io::Result<(Option, Vec)>) -> Conf { - if let Ok((_, warnings)) = path { - for warning in warnings { - sess.warn(warning.clone()); - } - } - let file_name = match path { - Ok((Some(path), _)) => path, - Ok((None, _)) => return Conf::default(), - Err(error) => { - sess.err(format!("error finding Clippy's configuration file: {error}")); - return Conf::default(); - }, - }; - - let TryConf { conf, errors, warnings } = utils::conf::read(sess, file_name); - // all conf errors are non-fatal, we just use the default conf in case of error - for error in errors { - if let Some(span) = error.span { - sess.span_err( - span, - format!("error reading Clippy's configuration file: {}", error.message), - ); - } else { - sess.err(format!( - "error reading Clippy's configuration file `{}`: {}", - file_name.display(), - error.message - )); - } - } - - for warning in warnings { - if let Some(span) = warning.span { - sess.span_warn( - span, - format!("error reading Clippy's configuration file: {}", warning.message), - ); - } else { - sess.warn(format!( - "error reading Clippy's configuration file `{}`: {}", - file_name.display(), - warning.message - )); - } - } - - conf -} - #[derive(Default)] struct RegistrationGroups { all: Vec, @@ -558,7 +502,7 @@ fn register_categories(store: &mut rustc_lint::LintStore) { /// /// Used in `./src/driver.rs`. #[expect(clippy::too_many_lines)] -pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &Conf) { +pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: &'static Conf) { register_removed_non_tool_lints(store); register_categories(store); @@ -660,8 +604,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)); store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); - let msrv = Msrv::read(&conf.msrv, sess); - let msrv = move || msrv.clone(); + let msrv = || conf.msrv.clone(); let avoid_breaking_exported_api = conf.avoid_breaking_exported_api; let allow_expect_in_tests = conf.allow_expect_in_tests; let allow_unwrap_in_tests = conf.allow_unwrap_in_tests; diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index a23000e5fe15..b5ab94b3a2f8 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -967,7 +967,6 @@ declare_clippy_lint! { "checks for unnecessary guards in match expressions" } -#[derive(Default)] pub struct Matches { msrv: Msrv, infallible_destructuring_match_linted: bool, @@ -978,7 +977,7 @@ impl Matches { pub fn new(msrv: Msrv) -> Self { Self { msrv, - ..Matches::default() + infallible_destructuring_match_linted: false, } } } diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index 50231d930d0e..f10ed4b3d419 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -54,7 +54,6 @@ declare_clippy_lint! { "unnecessary structure name repetition whereas `Self` is applicable" } -#[derive(Default)] pub struct UseSelf { msrv: Msrv, stack: Vec, @@ -65,7 +64,7 @@ impl UseSelf { pub fn new(msrv: Msrv) -> Self { Self { msrv, - ..Self::default() + stack: Vec::new(), } } } diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 23da1de77303..0075f611eb75 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -8,8 +8,9 @@ use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor}; use serde::Deserialize; use std::fmt::{Debug, Display, Formatter}; use std::ops::Range; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::str::FromStr; +use std::sync::OnceLock; use std::{cmp, env, fmt, fs, io}; #[rustfmt::skip] @@ -78,62 +79,35 @@ pub struct TryConf { impl TryConf { fn from_toml_error(file: &SourceFile, error: &toml::de::Error) -> Self { - ConfError::from_toml(file, error).into() - } -} - -impl From for TryConf { - fn from(value: ConfError) -> Self { Self { conf: Conf::default(), - errors: vec![value], + errors: vec![ConfError::from_toml(file, error)], warnings: vec![], } } } -impl From for TryConf { - fn from(value: io::Error) -> Self { - ConfError::from(value).into() - } -} - #[derive(Debug)] pub struct ConfError { pub message: String, - pub span: Option, + pub span: Span, } impl ConfError { fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self { - if let Some(span) = error.span() { - Self::spanned(file, error.message(), span) - } else { - Self { - message: error.message().to_string(), - span: None, - } - } + let span = error.span().unwrap_or(0..file.source_len.0 as usize); + Self::spanned(file, error.message(), span) } fn spanned(file: &SourceFile, message: impl Into, span: Range) -> Self { Self { message: message.into(), - span: Some(Span::new( + span: Span::new( file.start_pos + BytePos::from_usize(span.start), file.start_pos + BytePos::from_usize(span.end), SyntaxContext::root(), None, - )), - } - } -} - -impl From for ConfError { - fn from(value: io::Error) -> Self { - Self { - message: value.to_string(), - span: None, + ), } } } @@ -297,7 +271,7 @@ define_Conf! { /// Lint: MANUAL_SPLIT_ONCE, MANUAL_STR_REPEAT, CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, OPTION_MAP_UNWRAP_OR, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE, APPROX_CONSTANT, DEPRECATED_CFG_ATTR, INDEX_REFUTABLE_SLICE, MAP_CLONE, BORROW_AS_PTR, MANUAL_BITS, ERR_EXPECT, CAST_ABS_TO_UNSIGNED, UNINLINED_FORMAT_ARGS, MANUAL_CLAMP, MANUAL_LET_ELSE, UNCHECKED_DURATION_SUBTRACTION, COLLAPSIBLE_STR_REPLACE, SEEK_FROM_CURRENT, SEEK_REWIND, UNNECESSARY_LAZY_EVALUATIONS, TRANSMUTE_PTR_TO_REF, ALMOST_COMPLETE_RANGE, NEEDLESS_BORROW, DERIVABLE_IMPLS, MANUAL_IS_ASCII_CHECK, MANUAL_REM_EUCLID, MANUAL_RETAIN, TYPE_REPETITION_IN_BOUNDS, TUPLE_ARRAY_CONVERSIONS, MANUAL_TRY_FOLD, MANUAL_HASH_ONE. /// /// The minimum rust version that the project supports - (msrv: Option = None), + (msrv: crate::Msrv = crate::Msrv::empty()), /// DEPRECATED LINT: BLACKLISTED_NAME. /// /// Use the Disallowed Names lint instead @@ -641,15 +615,8 @@ pub fn lookup_conf_file() -> io::Result<(Option, Vec)> { } } -/// Read the `toml` configuration file. -/// -/// In case of error, the function tries to continue as much as possible. -pub fn read(sess: &Session, path: &Path) -> TryConf { - let file = match sess.source_map().load_file(path) { - Err(e) => return e.into(), - Ok(file) => file, - }; - match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(&file)) { +fn deserialize(file: &SourceFile) -> TryConf { + match toml::de::Deserializer::new(file.src.as_ref().unwrap()).deserialize_map(ConfVisitor(file)) { Ok(mut conf) => { extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS); extend_vec_if_indicator_present(&mut conf.conf.disallowed_names, DEFAULT_DISALLOWED_NAMES); @@ -662,7 +629,7 @@ pub fn read(sess: &Session, path: &Path) -> TryConf { conf }, - Err(e) => TryConf::from_toml_error(&file, &e), + Err(e) => TryConf::from_toml_error(file, &e), } } @@ -672,6 +639,60 @@ fn extend_vec_if_indicator_present(vec: &mut Vec, default: &[&str]) { } } +impl Conf { + pub fn read(sess: &Session, path: &io::Result<(Option, Vec)>) -> &'static Conf { + static CONF: OnceLock = OnceLock::new(); + CONF.get_or_init(|| Conf::read_inner(sess, path)) + } + + fn read_inner(sess: &Session, path: &io::Result<(Option, Vec)>) -> Conf { + match path { + Ok((_, warnings)) => { + for warning in warnings { + sess.warn(warning.clone()); + } + }, + Err(error) => { + sess.err(format!("error finding Clippy's configuration file: {error}")); + }, + } + + let TryConf { + mut conf, + errors, + warnings, + } = match path { + Ok((Some(path), _)) => match sess.source_map().load_file(path) { + Ok(file) => deserialize(&file), + Err(error) => { + sess.err(format!("failed to read `{}`: {error}", path.display())); + TryConf::default() + }, + }, + _ => TryConf::default(), + }; + + conf.msrv.read_cargo(sess); + + // all conf errors are non-fatal, we just use the default conf in case of error + for error in errors { + sess.span_err( + error.span, + format!("error reading Clippy's configuration file: {}", error.message), + ); + } + + for warning in warnings { + sess.span_warn( + warning.span, + format!("error reading Clippy's configuration file: {}", warning.message), + ); + } + + conf + } +} + const SEPARATOR_WIDTH: usize = 4; #[derive(Debug)] diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index 8522493f67b3..90091ca927ae 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -9,6 +9,7 @@ arrayvec = { version = "0.7", default-features = false } if_chain = "1.0" itertools = "0.10.1" rustc-semver = "1.1" +serde = { version = "1.0" } [features] deny-warnings = [] diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs index df839c2106f1..c6a48874e09b 100644 --- a/clippy_utils/src/msrvs.rs +++ b/clippy_utils/src/msrvs.rs @@ -1,9 +1,7 @@ -use std::sync::OnceLock; - use rustc_ast::Attribute; use rustc_semver::RustcVersion; use rustc_session::Session; -use rustc_span::Span; +use serde::Deserialize; use crate::attrs::get_unique_attr; @@ -53,65 +51,45 @@ msrv_aliases! { 1,15,0 { MAYBE_BOUND_IN_WHERE } } -fn parse_msrv(msrv: &str, sess: Option<&Session>, span: Option) -> Option { - if let Ok(version) = RustcVersion::parse(msrv) { - return Some(version); - } else if let Some(sess) = sess { - if let Some(span) = span { - sess.span_err(span, format!("`{msrv}` is not a valid Rust version")); - } - } - None -} - /// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]` -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct Msrv { stack: Vec, } +impl<'de> Deserialize<'de> for Msrv { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let v = String::deserialize(deserializer)?; + RustcVersion::parse(&v) + .map(|v| Msrv { stack: vec![v] }) + .map_err(|_| serde::de::Error::custom("not a valid Rust version")) + } +} + impl Msrv { - fn new(initial: Option) -> Self { - Self { - stack: Vec::from_iter(initial), - } + pub fn empty() -> Msrv { + Msrv { stack: Vec::new() } } - fn read_inner(conf_msrv: &Option, sess: &Session) -> Self { + pub fn read_cargo(&mut self, sess: &Session) { let cargo_msrv = std::env::var("CARGO_PKG_RUST_VERSION") .ok() - .and_then(|v| parse_msrv(&v, None, None)); - let clippy_msrv = conf_msrv.as_ref().and_then(|s| { - parse_msrv(s, None, None).or_else(|| { - sess.err(format!( - "error reading Clippy's configuration file. `{s}` is not a valid Rust version" - )); - None - }) - }); - - // if both files have an msrv, let's compare them and emit a warning if they differ - if let Some(cargo_msrv) = cargo_msrv - && let Some(clippy_msrv) = clippy_msrv - && clippy_msrv != cargo_msrv - { - sess.warn(format!( - "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`" - )); + .and_then(|v| RustcVersion::parse(&v).ok()); + + match (self.current(), cargo_msrv) { + (None, Some(cargo_msrv)) => self.stack = vec![cargo_msrv], + (Some(clippy_msrv), Some(cargo_msrv)) => { + if clippy_msrv != cargo_msrv { + sess.warn(format!( + "the MSRV in `clippy.toml` and `Cargo.toml` differ; using `{clippy_msrv}` from `clippy.toml`" + )); + } + }, + _ => {}, } - - Self::new(clippy_msrv.or(cargo_msrv)) - } - - /// Set the initial MSRV from the Clippy config file or from Cargo due to the `rust-version` - /// field in `Cargo.toml` - /// - /// Returns a `&'static Msrv` as `Copy` types are more easily passed to the - /// `register_{late,early}_pass` callbacks - pub fn read(conf_msrv: &Option, sess: &Session) -> &'static Self { - static PARSED: OnceLock = OnceLock::new(); - - PARSED.get_or_init(|| Self::read_inner(conf_msrv, sess)) } pub fn current(&self) -> Option { @@ -125,10 +103,14 @@ impl Msrv { fn parse_attr(sess: &Session, attrs: &[Attribute]) -> Option { if let Some(msrv_attr) = get_unique_attr(sess, attrs, "msrv") { if let Some(msrv) = msrv_attr.value_str() { - return parse_msrv(&msrv.to_string(), Some(sess), Some(msrv_attr.span)); - } + if let Ok(version) = RustcVersion::parse(msrv.as_str()) { + return Some(version); + } - sess.span_err(msrv_attr.span, "bad clippy attribute"); + sess.span_err(msrv_attr.span, format!("`{msrv}` is not a valid Rust version")); + } else { + sess.span_err(msrv_attr.span, "bad clippy attribute"); + } } None diff --git a/src/driver.rs b/src/driver.rs index d47767faed9e..0e81419f1fa3 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -147,9 +147,9 @@ impl rustc_driver::Callbacks for ClippyCallbacks { (previous)(sess, lint_store); } - let conf = clippy_lints::read_conf(sess, &conf_path); - clippy_lints::register_plugins(lint_store, sess, &conf); - clippy_lints::register_pre_expansion_lints(lint_store, sess, &conf); + let conf = clippy_lints::Conf::read(sess, &conf_path); + clippy_lints::register_plugins(lint_store, sess, conf); + clippy_lints::register_pre_expansion_lints(lint_store, conf); clippy_lints::register_renamed(lint_store); })); diff --git a/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.rs b/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.rs index 03fa719975b6..85e2fb8c797f 100644 --- a/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.rs +++ b/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.rs @@ -1,4 +1,4 @@ -//@error-in-other-file: `invalid.version` is not a valid Rust version +//@error-in-other-file: not a valid Rust version #![allow(clippy::redundant_clone)] diff --git a/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.stderr b/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.stderr index e9d8fd2e0f52..f127c2408f90 100644 --- a/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.stderr +++ b/tests/ui-toml/invalid_min_rust_version/invalid_min_rust_version.stderr @@ -1,4 +1,8 @@ -error: error reading Clippy's configuration file. `invalid.version` is not a valid Rust version +error: error reading Clippy's configuration file: not a valid Rust version + --> $DIR/$DIR/clippy.toml:1:8 + | +LL | msrv = "invalid.version" + | ^^^^^^^^^^^^^^^^^ error: aborting due to previous error