diff --git a/README.md b/README.md
index 20a5e997e629..21915e5c6fe0 100644
--- a/README.md
+++ b/README.md
@@ -237,37 +237,21 @@ define the `CLIPPY_DISABLE_DOCS_LINKS` environment variable.
### Specifying the minimum supported Rust version
Projects that intend to support old versions of Rust can disable lints pertaining to newer features by
-specifying the minimum supported Rust version (MSRV) in the Clippy configuration file.
-
-```toml
-msrv = "1.30.0"
-```
-
-Alternatively, the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field)
-in the `Cargo.toml` can be used.
+specifying the minimum supported Rust version (MSRV) in the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/rust-version.html)
+of `Cargo.toml`.
```toml
# Cargo.toml
rust-version = "1.30"
```
-The MSRV can also be specified as an attribute, like below.
-
-```rust,ignore
-#![feature(custom_inner_attributes)]
-#![clippy::msrv = "1.30.0"]
-
-fn main() {
- ...
-}
-```
-
-You can also omit the patch version when specifying the MSRV, so `msrv = 1.30`
-is equivalent to `msrv = 1.30.0`.
+Alternatively the [`msrv` field](https://doc.rust-lang.org/clippy/lint_configuration.html#msrv) can be specified in the
+Clippy configuration file.
-Note: `custom_inner_attributes` is an unstable feature, so it has to be enabled explicitly.
+Clippy will automatically adjust the MSRV for sections of code that uses `#[cfg(version)]`, alternatively the
+`#[clippy::msrv]` attribute can be used to specifiy the MSRV without any other effect.
-Lints that recognize this configuration option can be found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv)
+For more information see [Specifying the minimum supported Rust version](https://doc.rust-lang.org/clippy/configuration.html#specifying-the-minimum-supported-rust-version).
## Contributing
diff --git a/book/src/configuration.md b/book/src/configuration.md
index b13054431898..d47e9f282484 100644
--- a/book/src/configuration.md
+++ b/book/src/configuration.md
@@ -98,28 +98,45 @@ For more details and options, refer to the Cargo documentation.
### Specifying the minimum supported Rust version
-Projects that intend to support old versions of Rust can disable lints pertaining to newer features by specifying the
-minimum supported Rust version (MSRV) in the Clippy configuration file.
+Projects that intend to support old versions of Rust can disable lints pertaining to newer features by
+specifying the minimum supported Rust version (MSRV) in the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/rust-version.html)
+of `Cargo.toml`.
```toml
-msrv = "1.30.0"
+# Cargo.toml
+rust-version = "1.30"
```
-The MSRV can also be specified as an attribute, like below.
+Alternatively the [`msrv` field](https://doc.rust-lang.org/clippy/lint_configuration.html#msrv) can be specified in the
+Clippy configuration file.
-```rust,ignore
-#![feature(custom_inner_attributes)]
-#![clippy::msrv = "1.30.0"]
+```toml
+# clippy.toml
+msrv = "1.30"
+```
+
+Clippy will automatically adjust the MSRV for sections of code that use `#[cfg(version)]`:
-fn main() {
- ...
+```rust
+#[cfg(version("1.90"))]
+fn f() {
+ // The MSRV here is set to 1.90
}
```
-You can also omit the patch version when specifying the MSRV, so `msrv = 1.30`
-is equivalent to `msrv = 1.30.0`.
+> **Note:** `cfg(version)` is not yet [available on stable](https://github.com/rust-lang/rust/pull/141766)
+
+The `#[clippy::msrv]` can also be used to set the MSRV for a section of code with no other effect:
+
+```rust
+#[clippy::msrv = "1.30"]
+fn f() {
+ // The MSRV here is set to 1.30
+}
+```
-Note: `custom_inner_attributes` is an unstable feature, so it has to be enabled explicitly.
+If both `#[cfg(version)]` and `#[clippy::msrv]` attributes are applied to the same node then `#[clippy::msrv]` takes
+precedence.
Lints that recognize this configuration option can be
found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv)
diff --git a/clippy_utils/src/msrvs.rs b/clippy_utils/src/msrvs.rs
index a5e66ad463bb..64fb9656ae24 100644
--- a/clippy_utils/src/msrvs.rs
+++ b/clippy_utils/src/msrvs.rs
@@ -1,6 +1,6 @@
use crate::sym;
-use rustc_ast::Attribute;
use rustc_ast::attr::AttributeExt;
+use rustc_ast::{Attribute, LitKind, MetaItem, MetaItemInner};
use rustc_attr_data_structures::RustcVersion;
use rustc_attr_parsing::parse_version;
use rustc_lint::LateContext;
@@ -186,27 +186,67 @@ impl MsrvStack {
}
fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt]) -> Option<RustcVersion> {
- let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym::msrv]));
-
- if let Some(msrv_attr) = msrv_attrs.next() {
- if let Some(duplicate) = msrv_attrs.next_back() {
- sess.dcx()
- .struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
- .with_span_note(msrv_attr.span(), "first definition found here")
- .emit();
- }
-
- if let Some(msrv) = msrv_attr.value_str() {
- if let Some(version) = parse_version(msrv) {
- return Some(version);
+ let mut first_clippy_attr = None;
+ let mut clippy_msrv = None;
+ let mut cfg_version = None;
+ for attr in attrs {
+ if attr.path_matches(&[sym::clippy, sym::msrv]) {
+ match first_clippy_attr {
+ None => first_clippy_attr = Some(attr),
+ Some(first) => {
+ sess.dcx()
+ .struct_span_err(attr.span(), "`clippy::msrv` is defined multiple times")
+ .with_span_note(first.span(), "first definition found here")
+ .emit();
+ },
}
- sess.dcx()
- .span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
- } else {
- sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
+ if let Some(msrv) = attr.value_str() {
+ if let Some(version) = parse_version(msrv) {
+ clippy_msrv = Some(version);
+ } else {
+ sess.dcx()
+ .span_err(attr.span(), format!("`{msrv}` is not a valid Rust version"));
+ }
+ } else {
+ sess.dcx().span_err(attr.span(), "bad clippy attribute");
+ }
+ } else if matches!(attr.name(), Some(sym::cfg | sym::cfg_trace)) // cfg in early passes, cfg_trace in late
+ && let Some(list) = attr.meta_item_list()
+ && let [MetaItemInner::MetaItem(meta_item)] = list.as_slice()
+ {
+ parse_cfg_version(&mut cfg_version, meta_item, false);
}
}
- None
+ clippy_msrv.or(cfg_version)
+}
+
+fn parse_cfg_version(current: &mut Option<RustcVersion>, meta_item: &MetaItem, mut negated: bool) {
+ let Some(name) = meta_item.name() else { return };
+ match name {
+ sym::version => {
+ if !negated
+ && let Some([MetaItemInner::Lit(lit)]) = meta_item.meta_item_list()
+ && let LitKind::Str(s, _) = lit.kind
+ && let Some(version) = parse_version(s)
+ {
+ match current {
+ Some(current) => *current = version.min(*current),
+ None => *current = Some(version),
+ }
+ }
+ },
+ sym::any | sym::all | sym::not => {
+ if name == sym::not {
+ negated = !negated;
+ }
+ for inner in meta_item.meta_item_list().into_iter().flatten() {
+ if let Some(inner_meta_item) = inner.meta_item() {
+ parse_cfg_version(current, inner_meta_item, negated);
+ }
+ }
+ },
+ _ => {},
+ }
}
diff --git a/tests/ui/cfg_version_msrv.rs b/tests/ui/cfg_version_msrv.rs
new file mode 100644
index 000000000000..a9afca8cf190
--- /dev/null
+++ b/tests/ui/cfg_version_msrv.rs
@@ -0,0 +1,40 @@
+#![feature(cfg_version)]
+
+fn f(i: i32) {
+ #[cfg(version("1.50"))]
+ let _ = i.isqrt();
+ //~^ ERROR: is `1.50.0`
+
+ // When `any/all` are used pick the smallest version seen
+ #[cfg(any(version("1.49"), version("1.50")))]
+ let _ = i.isqrt();
+ //~^ ERROR: is `1.49.0`
+ #[cfg(all(version("1.60"), version("1.59")))]
+ let _ = i.isqrt();
+ //~^ ERROR: is `1.59.0`
+
+ // Ignore negated version requirements
+ #[cfg(not(version("1.50")))]
+ let _ = i.isqrt();
+ #[cfg(not(not(version("1.50"))))]
+ let _ = i.isqrt();
+ //~^ ERROR: is `1.50.0`
+ #[cfg(not(all(version("1.40"), not(version("1.50")))))]
+ let _ = i.isqrt();
+ //~^ ERROR: is `1.50.0`
+}
+
+/// If both are specified on the same node then `clippy::msrv` takes precedence
+#[clippy::msrv = "1.50"]
+#[cfg(version("1.40"))]
+fn both_attributes_cfg_lower(i: i32) {
+ let _ = i.isqrt();
+ //~^ ERROR: is `1.50.0`
+}
+
+#[clippy::msrv = "1.40"]
+#[cfg(version("1.50"))]
+fn both_attributes_cfg_higher(i: i32) {
+ let _ = i.isqrt();
+ //~^ ERROR: is `1.40.0`
+}
diff --git a/tests/ui/cfg_version_msrv.stderr b/tests/ui/cfg_version_msrv.stderr
new file mode 100644
index 000000000000..5610f7523799
--- /dev/null
+++ b/tests/ui/cfg_version_msrv.stderr
@@ -0,0 +1,47 @@
+error: current MSRV (Minimum Supported Rust Version) is `1.50.0` but this item is stable since `1.84.0`
+ --> tests/ui/cfg_version_msrv.rs:5:15
+ |
+LL | let _ = i.isqrt();
+ | ^^^^^^^
+ |
+ = note: `-D clippy::incompatible-msrv` implied by `-D warnings`
+ = help: to override `-D warnings` add `#[allow(clippy::incompatible_msrv)]`
+
+error: current MSRV (Minimum Supported Rust Version) is `1.49.0` but this item is stable since `1.84.0`
+ --> tests/ui/cfg_version_msrv.rs:10:15
+ |
+LL | let _ = i.isqrt();
+ | ^^^^^^^
+
+error: current MSRV (Minimum Supported Rust Version) is `1.59.0` but this item is stable since `1.84.0`
+ --> tests/ui/cfg_version_msrv.rs:13:15
+ |
+LL | let _ = i.isqrt();
+ | ^^^^^^^
+
+error: current MSRV (Minimum Supported Rust Version) is `1.50.0` but this item is stable since `1.84.0`
+ --> tests/ui/cfg_version_msrv.rs:20:15
+ |
+LL | let _ = i.isqrt();
+ | ^^^^^^^
+
+error: current MSRV (Minimum Supported Rust Version) is `1.50.0` but this item is stable since `1.84.0`
+ --> tests/ui/cfg_version_msrv.rs:23:15
+ |
+LL | let _ = i.isqrt();
+ | ^^^^^^^
+
+error: current MSRV (Minimum Supported Rust Version) is `1.50.0` but this item is stable since `1.84.0`
+ --> tests/ui/cfg_version_msrv.rs:31:15
+ |
+LL | let _ = i.isqrt();
+ | ^^^^^^^
+
+error: current MSRV (Minimum Supported Rust Version) is `1.40.0` but this item is stable since `1.84.0`
+ --> tests/ui/cfg_version_msrv.rs:38:15
+ |
+LL | let _ = i.isqrt();
+ | ^^^^^^^
+
+error: aborting due to 7 previous errors
+
diff --git a/tests/ui/min_rust_version_invalid_attr.rs b/tests/ui/min_rust_version_invalid_attr.rs
index c8409d78ed77..1d232adb6216 100644
--- a/tests/ui/min_rust_version_invalid_attr.rs
+++ b/tests/ui/min_rust_version_invalid_attr.rs
@@ -13,6 +13,8 @@ fn outer_attr() {}
mod multiple {
#![clippy::msrv = "1.40"]
#![clippy::msrv = "=1.35.0"]
+ //~^ ERROR: `clippy::msrv` is defined multiple times
+ //~| ERROR: `=1.35.0` is not a valid Rust version
#![clippy::msrv = "1.10.1"]
//~^ ERROR: `clippy::msrv` is defined multiple times
diff --git a/tests/ui/min_rust_version_invalid_attr.stderr b/tests/ui/min_rust_version_invalid_attr.stderr
index dbc276ed89df..dc05cc634f83 100644
--- a/tests/ui/min_rust_version_invalid_attr.stderr
+++ b/tests/ui/min_rust_version_invalid_attr.stderr
@@ -11,7 +11,25 @@ LL | #[clippy::msrv = "invalid.version"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: `clippy::msrv` is defined multiple times
- --> tests/ui/min_rust_version_invalid_attr.rs:16:5
+ --> tests/ui/min_rust_version_invalid_attr.rs:15:5
+ |
+LL | #![clippy::msrv = "=1.35.0"]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+note: first definition found here
+ --> tests/ui/min_rust_version_invalid_attr.rs:14:5
+ |
+LL | #![clippy::msrv = "1.40"]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: `=1.35.0` is not a valid Rust version
+ --> tests/ui/min_rust_version_invalid_attr.rs:15:5
+ |
+LL | #![clippy::msrv = "=1.35.0"]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: `clippy::msrv` is defined multiple times
+ --> tests/ui/min_rust_version_invalid_attr.rs:18:5
|
LL | #![clippy::msrv = "1.10.1"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -23,16 +41,16 @@ LL | #![clippy::msrv = "1.40"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^
error: `clippy::msrv` is defined multiple times
- --> tests/ui/min_rust_version_invalid_attr.rs:21:9
+ --> tests/ui/min_rust_version_invalid_attr.rs:23:9
|
LL | #![clippy::msrv = "1.0.0"]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: first definition found here
- --> tests/ui/min_rust_version_invalid_attr.rs:20:9
+ --> tests/ui/min_rust_version_invalid_attr.rs:22:9
|
LL | #![clippy::msrv = "1.0"]
| ^^^^^^^^^^^^^^^^^^^^^^^^
-error: aborting due to 4 previous errors
+error: aborting due to 6 previous errors