Deserialize Msrv directly in Conf
This commit is contained in:
parent
2cf708d04f
commit
1528c1db47
@ -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<String>`)
|
||||
**Default Value:** `Msrv { stack: [] }` (`crate::Msrv`)
|
||||
|
||||
---
|
||||
**Affected lints:**
|
||||
|
@ -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<PathBuf>, Vec<String>)>) -> 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<LintId>,
|
||||
@ -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;
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +54,6 @@ declare_clippy_lint! {
|
||||
"unnecessary structure name repetition whereas `Self` is applicable"
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UseSelf {
|
||||
msrv: Msrv,
|
||||
stack: Vec<StackItem>,
|
||||
@ -65,7 +64,7 @@ impl UseSelf {
|
||||
pub fn new(msrv: Msrv) -> Self {
|
||||
Self {
|
||||
msrv,
|
||||
..Self::default()
|
||||
stack: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<ConfError> 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<io::Error> for TryConf {
|
||||
fn from(value: io::Error) -> Self {
|
||||
ConfError::from(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ConfError {
|
||||
pub message: String,
|
||||
pub span: Option<Span>,
|
||||
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<String>, span: Range<usize>) -> 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<io::Error> 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<String> = 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<PathBuf>, Vec<String>)> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<String>, default: &[&str]) {
|
||||
}
|
||||
}
|
||||
|
||||
impl Conf {
|
||||
pub fn read(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> &'static Conf {
|
||||
static CONF: OnceLock<Conf> = OnceLock::new();
|
||||
CONF.get_or_init(|| Conf::read_inner(sess, path))
|
||||
}
|
||||
|
||||
fn read_inner(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> 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)]
|
||||
|
@ -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 = []
|
||||
|
@ -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<Span>) -> Option<RustcVersion> {
|
||||
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<RustcVersion>,
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Msrv {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
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<RustcVersion>) -> Self {
|
||||
Self {
|
||||
stack: Vec::from_iter(initial),
|
||||
}
|
||||
pub fn empty() -> Msrv {
|
||||
Msrv { stack: Vec::new() }
|
||||
}
|
||||
|
||||
fn read_inner(conf_msrv: &Option<String>, 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
|
||||
})
|
||||
});
|
||||
.and_then(|v| RustcVersion::parse(&v).ok());
|
||||
|
||||
// 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`"
|
||||
));
|
||||
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<String>, sess: &Session) -> &'static Self {
|
||||
static PARSED: OnceLock<Msrv> = OnceLock::new();
|
||||
|
||||
PARSED.get_or_init(|| Self::read_inner(conf_msrv, sess))
|
||||
}
|
||||
|
||||
pub fn current(&self) -> Option<RustcVersion> {
|
||||
@ -125,10 +103,14 @@ impl Msrv {
|
||||
fn parse_attr(sess: &Session, attrs: &[Attribute]) -> Option<RustcVersion> {
|
||||
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
|
||||
|
@ -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);
|
||||
}));
|
||||
|
||||
|
@ -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)]
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user