Skip to content

Commit f134f83

Browse files
committed
Automatically set MSRV for code under #[cfg(version)]
1 parent 10ec6fc commit f134f83

File tree

7 files changed

+193
-58
lines changed

7 files changed

+193
-58
lines changed

README.md

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -237,37 +237,21 @@ define the `CLIPPY_DISABLE_DOCS_LINKS` environment variable.
237237
### Specifying the minimum supported Rust version
238238

239239
Projects that intend to support old versions of Rust can disable lints pertaining to newer features by
240-
specifying the minimum supported Rust version (MSRV) in the Clippy configuration file.
241-
242-
```toml
243-
msrv = "1.30.0"
244-
```
245-
246-
Alternatively, the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/manifest.html#the-rust-version-field)
247-
in the `Cargo.toml` can be used.
240+
specifying the minimum supported Rust version (MSRV) in the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/rust-version.html)
241+
of `Cargo.toml`.
248242

249243
```toml
250244
# Cargo.toml
251245
rust-version = "1.30"
252246
```
253247

254-
The MSRV can also be specified as an attribute, like below.
255-
256-
```rust,ignore
257-
#![feature(custom_inner_attributes)]
258-
#![clippy::msrv = "1.30.0"]
259-
260-
fn main() {
261-
...
262-
}
263-
```
264-
265-
You can also omit the patch version when specifying the MSRV, so `msrv = 1.30`
266-
is equivalent to `msrv = 1.30.0`.
248+
Alternatively the [`msrv` field](https://doc.rust-lang.org/clippy/lint_configuration.html#msrv) can be specified in the
249+
Clippy configuration file.
267250

268-
Note: `custom_inner_attributes` is an unstable feature, so it has to be enabled explicitly.
251+
Clippy will automatically adjust the MSRV for sections of code that uses `#[cfg(version)]`, alternatively the
252+
`#[clippy::msrv]` attribute can be used to specifiy the MSRV without any other effect.
269253

270-
Lints that recognize this configuration option can be found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv)
254+
For more information see [Specifying the minimum supported Rust version](https://doc.rust-lang.org/clippy/configuration.html#specifying-the-minimum-supported-rust-version).
271255

272256
## Contributing
273257

book/src/configuration.md

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,28 +98,45 @@ For more details and options, refer to the Cargo documentation.
9898

9999
### Specifying the minimum supported Rust version
100100

101-
Projects that intend to support old versions of Rust can disable lints pertaining to newer features by specifying the
102-
minimum supported Rust version (MSRV) in the Clippy configuration file.
101+
Projects that intend to support old versions of Rust can disable lints pertaining to newer features by
102+
specifying the minimum supported Rust version (MSRV) in the [`rust-version` field](https://doc.rust-lang.org/cargo/reference/rust-version.html)
103+
of `Cargo.toml`.
103104

104105
```toml
105-
msrv = "1.30.0"
106+
# Cargo.toml
107+
rust-version = "1.30"
106108
```
107109

108-
The MSRV can also be specified as an attribute, like below.
110+
Alternatively the [`msrv` field](https://doc.rust-lang.org/clippy/lint_configuration.html#msrv) can be specified in the
111+
Clippy configuration file.
109112

110-
```rust,ignore
111-
#![feature(custom_inner_attributes)]
112-
#![clippy::msrv = "1.30.0"]
113+
```toml
114+
# clippy.toml
115+
msrv = "1.30"
116+
```
117+
118+
Clippy will automatically adjust the MSRV for sections of code that use `#[cfg(version)]`:
113119

114-
fn main() {
115-
...
120+
```rust
121+
#[cfg(version("1.90"))]
122+
fn f() {
123+
// The MSRV here is set to 1.90
116124
}
117125
```
118126

119-
You can also omit the patch version when specifying the MSRV, so `msrv = 1.30`
120-
is equivalent to `msrv = 1.30.0`.
127+
> **Note:** `cfg(version)` is not yet [available on stable](https://github.com/rust-lang/rust/pull/141766)
128+
129+
The `#[clippy::msrv]` can also be used to set the MSRV for a section of code with no other effect:
130+
131+
```rust
132+
#[clippy::msrv = "1.30"]
133+
fn f() {
134+
// The MSRV here is set to 1.30
135+
}
136+
```
121137

122-
Note: `custom_inner_attributes` is an unstable feature, so it has to be enabled explicitly.
138+
If both `#[cfg(version)]` and `#[clippy::msrv]` attributes are applied to the same node then `#[clippy::msrv]` takes
139+
precedence.
123140

124141
Lints that recognize this configuration option can be
125142
found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv)

clippy_utils/src/msrvs.rs

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::sym;
2-
use rustc_ast::Attribute;
32
use rustc_ast::attr::AttributeExt;
3+
use rustc_ast::{Attribute, LitKind, MetaItem, MetaItemInner};
44
use rustc_attr_data_structures::RustcVersion;
55
use rustc_attr_parsing::parse_version;
66
use rustc_lint::LateContext;
@@ -186,27 +186,67 @@ impl MsrvStack {
186186
}
187187

188188
fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt]) -> Option<RustcVersion> {
189-
let mut msrv_attrs = attrs.iter().filter(|attr| attr.path_matches(&[sym::clippy, sym::msrv]));
190-
191-
if let Some(msrv_attr) = msrv_attrs.next() {
192-
if let Some(duplicate) = msrv_attrs.next_back() {
193-
sess.dcx()
194-
.struct_span_err(duplicate.span(), "`clippy::msrv` is defined multiple times")
195-
.with_span_note(msrv_attr.span(), "first definition found here")
196-
.emit();
197-
}
198-
199-
if let Some(msrv) = msrv_attr.value_str() {
200-
if let Some(version) = parse_version(msrv) {
201-
return Some(version);
189+
let mut first_clippy_attr = None;
190+
let mut clippy_msrv = None;
191+
let mut cfg_version: Option<RustcVersion> = None;
192+
for attr in attrs {
193+
if attr.path_matches(&[sym::clippy, sym::msrv]) {
194+
match first_clippy_attr {
195+
None => first_clippy_attr = Some(attr),
196+
Some(first) => {
197+
sess.dcx()
198+
.struct_span_err(attr.span(), "`clippy::msrv` is defined multiple times")
199+
.with_span_note(first.span(), "first definition found here")
200+
.emit();
201+
},
202202
}
203203

204-
sess.dcx()
205-
.span_err(msrv_attr.span(), format!("`{msrv}` is not a valid Rust version"));
206-
} else {
207-
sess.dcx().span_err(msrv_attr.span(), "bad clippy attribute");
204+
if let Some(msrv) = attr.value_str() {
205+
if let Some(version) = parse_version(msrv) {
206+
clippy_msrv = Some(version);
207+
} else {
208+
sess.dcx()
209+
.span_err(attr.span(), format!("`{msrv}` is not a valid Rust version"));
210+
}
211+
} else {
212+
sess.dcx().span_err(attr.span(), "bad clippy attribute");
213+
}
214+
} else if matches!(attr.name(), Some(sym::cfg | sym::cfg_trace)) // cfg in early passes, cfg_trace in late
215+
&& let Some(list) = attr.meta_item_list()
216+
&& let [MetaItemInner::MetaItem(meta_item)] = list.as_slice()
217+
{
218+
parse_cfg_version(&mut cfg_version, meta_item, false);
208219
}
209220
}
210221

211-
None
222+
clippy_msrv.or(cfg_version)
223+
}
224+
225+
fn parse_cfg_version(current: &mut Option<RustcVersion>, meta_item: &MetaItem, mut negated: bool) {
226+
let Some(name) = meta_item.name() else { return };
227+
match name {
228+
sym::version => {
229+
if !negated
230+
&& let Some([MetaItemInner::Lit(lit)]) = meta_item.meta_item_list()
231+
&& let LitKind::Str(s, _) = lit.kind
232+
&& let Some(version) = parse_version(s)
233+
{
234+
match current {
235+
Some(current) => *current = version.min(*current),
236+
None => *current = Some(version),
237+
}
238+
}
239+
},
240+
sym::any | sym::all | sym::not => {
241+
if name == sym::not {
242+
negated = !negated;
243+
}
244+
for inner in meta_item.meta_item_list().into_iter().flatten() {
245+
if let Some(inner_meta_item) = inner.meta_item() {
246+
parse_cfg_version(current, inner_meta_item, negated);
247+
}
248+
}
249+
},
250+
_ => {},
251+
}
212252
}

tests/ui/cfg_version_msrv.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#![feature(cfg_version)]
2+
3+
fn f(i: i32) {
4+
#[cfg(version("1.50"))]
5+
let _ = i.isqrt();
6+
//~^ ERROR: is `1.50.0`
7+
8+
// When `any/all` are used pick the smallest version seen
9+
#[cfg(any(version("1.49"), version("1.50")))]
10+
let _ = i.isqrt();
11+
//~^ ERROR: is `1.49.0`
12+
#[cfg(all(version("1.60"), version("1.59")))]
13+
let _ = i.isqrt();
14+
//~^ ERROR: is `1.59.0`
15+
16+
// Ignore negated version requirements
17+
#[cfg(not(version("1.50")))]
18+
let _ = i.isqrt();
19+
#[cfg(not(not(version("1.50"))))]
20+
let _ = i.isqrt();
21+
//~^ ERROR: is `1.50.0`
22+
#[cfg(not(all(version("1.40"), not(version("1.50")))))]
23+
let _ = i.isqrt();
24+
//~^ ERROR: is `1.50.0`
25+
}
26+
27+
#[clippy::msrv = "1.40"]
28+
#[cfg(version("1.80"))]
29+
fn both_attributes(i: i32) {
30+
// if both are specified on the same node then `clippy::msrv` takes precedence
31+
let _ = i.isqrt();
32+
//~^ ERROR: is `1.40.0`
33+
}

tests/ui/cfg_version_msrv.stderr

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
error: current MSRV (Minimum Supported Rust Version) is `1.50.0` but this item is stable since `1.84.0`
2+
--> tests/ui/cfg_version_msrv.rs:5:15
3+
|
4+
LL | let _ = i.isqrt();
5+
| ^^^^^^^
6+
|
7+
= note: `-D clippy::incompatible-msrv` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::incompatible_msrv)]`
9+
10+
error: current MSRV (Minimum Supported Rust Version) is `1.49.0` but this item is stable since `1.84.0`
11+
--> tests/ui/cfg_version_msrv.rs:10:15
12+
|
13+
LL | let _ = i.isqrt();
14+
| ^^^^^^^
15+
16+
error: current MSRV (Minimum Supported Rust Version) is `1.59.0` but this item is stable since `1.84.0`
17+
--> tests/ui/cfg_version_msrv.rs:13:15
18+
|
19+
LL | let _ = i.isqrt();
20+
| ^^^^^^^
21+
22+
error: current MSRV (Minimum Supported Rust Version) is `1.50.0` but this item is stable since `1.84.0`
23+
--> tests/ui/cfg_version_msrv.rs:20:15
24+
|
25+
LL | let _ = i.isqrt();
26+
| ^^^^^^^
27+
28+
error: current MSRV (Minimum Supported Rust Version) is `1.50.0` but this item is stable since `1.84.0`
29+
--> tests/ui/cfg_version_msrv.rs:23:15
30+
|
31+
LL | let _ = i.isqrt();
32+
| ^^^^^^^
33+
34+
error: current MSRV (Minimum Supported Rust Version) is `1.40.0` but this item is stable since `1.84.0`
35+
--> tests/ui/cfg_version_msrv.rs:31:15
36+
|
37+
LL | let _ = i.isqrt();
38+
| ^^^^^^^
39+
40+
error: aborting due to 6 previous errors
41+

tests/ui/min_rust_version_invalid_attr.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ fn outer_attr() {}
1313
mod multiple {
1414
#![clippy::msrv = "1.40"]
1515
#![clippy::msrv = "=1.35.0"]
16+
//~^ ERROR: `clippy::msrv` is defined multiple times
17+
//~| ERROR: `=1.35.0` is not a valid Rust version
1618
#![clippy::msrv = "1.10.1"]
1719
//~^ ERROR: `clippy::msrv` is defined multiple times
1820

tests/ui/min_rust_version_invalid_attr.stderr

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,25 @@ LL | #[clippy::msrv = "invalid.version"]
1111
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1212

1313
error: `clippy::msrv` is defined multiple times
14-
--> tests/ui/min_rust_version_invalid_attr.rs:16:5
14+
--> tests/ui/min_rust_version_invalid_attr.rs:15:5
15+
|
16+
LL | #![clippy::msrv = "=1.35.0"]
17+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18+
|
19+
note: first definition found here
20+
--> tests/ui/min_rust_version_invalid_attr.rs:14:5
21+
|
22+
LL | #![clippy::msrv = "1.40"]
23+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
24+
25+
error: `=1.35.0` is not a valid Rust version
26+
--> tests/ui/min_rust_version_invalid_attr.rs:15:5
27+
|
28+
LL | #![clippy::msrv = "=1.35.0"]
29+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30+
31+
error: `clippy::msrv` is defined multiple times
32+
--> tests/ui/min_rust_version_invalid_attr.rs:18:5
1533
|
1634
LL | #![clippy::msrv = "1.10.1"]
1735
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -23,16 +41,16 @@ LL | #![clippy::msrv = "1.40"]
2341
| ^^^^^^^^^^^^^^^^^^^^^^^^^
2442

2543
error: `clippy::msrv` is defined multiple times
26-
--> tests/ui/min_rust_version_invalid_attr.rs:21:9
44+
--> tests/ui/min_rust_version_invalid_attr.rs:23:9
2745
|
2846
LL | #![clippy::msrv = "1.0.0"]
2947
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
3048
|
3149
note: first definition found here
32-
--> tests/ui/min_rust_version_invalid_attr.rs:20:9
50+
--> tests/ui/min_rust_version_invalid_attr.rs:22:9
3351
|
3452
LL | #![clippy::msrv = "1.0"]
3553
| ^^^^^^^^^^^^^^^^^^^^^^^^
3654

37-
error: aborting due to 4 previous errors
55+
error: aborting due to 6 previous errors
3856

0 commit comments

Comments
 (0)