Skip to content

Commit 8de9d8c

Browse files
committed
Auto merge of rust-lang#12160 - GuillaumeGomez:incompatible-msrv, r=blyxyas
Warn if an item coming from more recent version than MSRV is used Part of rust-lang/rust-clippy#6324. ~~Currently, the lint is not working for the simple reason that the `stable` attribute is not kept in dependencies. I'll send a PR to rustc to see if they'd be okay with keeping it.~~ EDIT: There was actually a `lookup_stability` function providing this information, so all good now! cc `@epage` changelog: create new [`incompatible_msrv`] lint
2 parents ed74c22 + 14e1520 commit 8de9d8c

File tree

9 files changed

+197
-2
lines changed

9 files changed

+197
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5198,6 +5198,7 @@ Released 2018-09-13
51985198
[`implied_bounds_in_impls`]: https://rust-lang.github.io/rust-clippy/master/index.html#implied_bounds_in_impls
51995199
[`impossible_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#impossible_comparisons
52005200
[`imprecise_flops`]: https://rust-lang.github.io/rust-clippy/master/index.html#imprecise_flops
5201+
[`incompatible_msrv`]: https://rust-lang.github.io/rust-clippy/master/index.html#incompatible_msrv
52015202
[`inconsistent_digit_grouping`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_digit_grouping
52025203
[`inconsistent_struct_constructor`]: https://rust-lang.github.io/rust-clippy/master/index.html#inconsistent_struct_constructor
52035204
[`incorrect_clone_impl_on_copy_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#incorrect_clone_impl_on_copy_type

clippy_config/src/msrvs.rs

+11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use rustc_semver::RustcVersion;
33
use rustc_session::Session;
44
use rustc_span::{sym, Symbol};
55
use serde::Deserialize;
6+
use std::fmt;
67

78
macro_rules! msrv_aliases {
89
($($major:literal,$minor:literal,$patch:literal {
@@ -58,6 +59,16 @@ pub struct Msrv {
5859
stack: Vec<RustcVersion>,
5960
}
6061

62+
impl fmt::Display for Msrv {
63+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64+
if let Some(msrv) = self.current() {
65+
write!(f, "{msrv}")
66+
} else {
67+
f.write_str("1.0.0")
68+
}
69+
}
70+
}
71+
6172
impl<'de> Deserialize<'de> for Msrv {
6273
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
6374
where

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
212212
crate::implicit_saturating_add::IMPLICIT_SATURATING_ADD_INFO,
213213
crate::implicit_saturating_sub::IMPLICIT_SATURATING_SUB_INFO,
214214
crate::implied_bounds_in_impls::IMPLIED_BOUNDS_IN_IMPLS_INFO,
215+
crate::incompatible_msrv::INCOMPATIBLE_MSRV_INFO,
215216
crate::inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR_INFO,
216217
crate::index_refutable_slice::INDEX_REFUTABLE_SLICE_INFO,
217218
crate::indexing_slicing::INDEXING_SLICING_INFO,

clippy_lints/src/incompatible_msrv.rs

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use clippy_config::msrvs::Msrv;
2+
use clippy_utils::diagnostics::span_lint;
3+
use rustc_attr::{StabilityLevel, StableSince};
4+
use rustc_data_structures::fx::FxHashMap;
5+
use rustc_hir::{Expr, ExprKind};
6+
use rustc_lint::{LateContext, LateLintPass};
7+
use rustc_middle::ty::TyCtxt;
8+
use rustc_semver::RustcVersion;
9+
use rustc_session::impl_lint_pass;
10+
use rustc_span::def_id::DefId;
11+
use rustc_span::Span;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
///
16+
/// This lint checks that no function newer than the defined MSRV (minimum
17+
/// supported rust version) is used in the crate.
18+
///
19+
/// ### Why is this bad?
20+
///
21+
/// It would prevent the crate to be actually used with the specified MSRV.
22+
///
23+
/// ### Example
24+
/// ```no_run
25+
/// // MSRV of 1.3.0
26+
/// use std::thread::sleep;
27+
/// use std::time::Duration;
28+
///
29+
/// // Sleep was defined in `1.4.0`.
30+
/// sleep(Duration::new(1, 0));
31+
/// ```
32+
///
33+
/// To fix this problem, either increase your MSRV or use another item
34+
/// available in your current MSRV.
35+
#[clippy::version = "1.77.0"]
36+
pub INCOMPATIBLE_MSRV,
37+
suspicious,
38+
"ensures that all items used in the crate are available for the current MSRV"
39+
}
40+
41+
pub struct IncompatibleMsrv {
42+
msrv: Msrv,
43+
is_above_msrv: FxHashMap<DefId, RustcVersion>,
44+
}
45+
46+
impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]);
47+
48+
impl IncompatibleMsrv {
49+
pub fn new(msrv: Msrv) -> Self {
50+
Self {
51+
msrv,
52+
is_above_msrv: FxHashMap::default(),
53+
}
54+
}
55+
56+
#[allow(clippy::cast_lossless)]
57+
fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion {
58+
if let Some(version) = self.is_above_msrv.get(&def_id) {
59+
return *version;
60+
}
61+
let version = if let Some(version) = tcx
62+
.lookup_stability(def_id)
63+
.and_then(|stability| match stability.level {
64+
StabilityLevel::Stable {
65+
since: StableSince::Version(version),
66+
..
67+
} => Some(RustcVersion::new(
68+
version.major as _,
69+
version.minor as _,
70+
version.patch as _,
71+
)),
72+
_ => None,
73+
}) {
74+
version
75+
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) {
76+
self.get_def_id_version(tcx, parent_def_id)
77+
} else {
78+
RustcVersion::new(1, 0, 0)
79+
};
80+
self.is_above_msrv.insert(def_id, version);
81+
version
82+
}
83+
84+
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, span: Span) {
85+
if def_id.is_local() {
86+
// We don't check local items since their MSRV is supposed to always be valid.
87+
return;
88+
}
89+
let version = self.get_def_id_version(cx.tcx, def_id);
90+
if self.msrv.meets(version) {
91+
return;
92+
}
93+
self.emit_lint_for(cx, span, version);
94+
}
95+
96+
fn emit_lint_for(&self, cx: &LateContext<'_>, span: Span, version: RustcVersion) {
97+
span_lint(
98+
cx,
99+
INCOMPATIBLE_MSRV,
100+
span,
101+
&format!(
102+
"current MSRV (Minimum Supported Rust Version) is `{}` but this item is stable since `{version}`",
103+
self.msrv
104+
),
105+
);
106+
}
107+
}
108+
109+
impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
110+
extract_msrv_attr!(LateContext);
111+
112+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
113+
if self.msrv.current().is_none() {
114+
// If there is no MSRV, then no need to check anything...
115+
return;
116+
}
117+
match expr.kind {
118+
ExprKind::MethodCall(_, _, _, span) => {
119+
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
120+
self.emit_lint_if_under_msrv(cx, method_did, span);
121+
}
122+
},
123+
ExprKind::Call(call, [_]) => {
124+
if let ExprKind::Path(qpath) = call.kind
125+
&& let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id()
126+
{
127+
self.emit_lint_if_under_msrv(cx, path_def_id, call.span);
128+
}
129+
},
130+
_ => {},
131+
}
132+
}
133+
}

clippy_lints/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern crate rustc_abi;
2626
extern crate rustc_arena;
2727
extern crate rustc_ast;
2828
extern crate rustc_ast_pretty;
29+
extern crate rustc_attr;
2930
extern crate rustc_data_structures;
3031
extern crate rustc_driver;
3132
extern crate rustc_errors;
@@ -153,6 +154,7 @@ mod implicit_return;
153154
mod implicit_saturating_add;
154155
mod implicit_saturating_sub;
155156
mod implied_bounds_in_impls;
157+
mod incompatible_msrv;
156158
mod inconsistent_struct_constructor;
157159
mod index_refutable_slice;
158160
mod indexing_slicing;
@@ -1094,6 +1096,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
10941096
store.register_late_pass(move |_| {
10951097
Box::new(thread_local_initializer_can_be_made_const::ThreadLocalInitializerCanBeMadeConst::new(msrv()))
10961098
});
1099+
store.register_late_pass(move |_| Box::new(incompatible_msrv::IncompatibleMsrv::new(msrv())));
10971100
// add lints here, do not remove this comment, it's used in `new_lint`
10981101
}
10991102

tests/ui-toml/min_rust_version/min_rust_version.fixed

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![allow(clippy::redundant_clone, clippy::unnecessary_operation)]
1+
#![allow(clippy::redundant_clone, clippy::unnecessary_operation, clippy::incompatible_msrv)]
22
#![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)]
33

44
use std::mem::{size_of, size_of_val};

tests/ui-toml/min_rust_version/min_rust_version.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![allow(clippy::redundant_clone, clippy::unnecessary_operation)]
1+
#![allow(clippy::redundant_clone, clippy::unnecessary_operation, clippy::incompatible_msrv)]
22
#![warn(clippy::manual_non_exhaustive, clippy::borrow_as_ptr, clippy::manual_bits)]
33

44
use std::mem::{size_of, size_of_val};

tests/ui/incompatible_msrv.rs

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#![warn(clippy::incompatible_msrv)]
2+
#![feature(custom_inner_attributes)]
3+
#![clippy::msrv = "1.3.0"]
4+
5+
use std::collections::hash_map::Entry;
6+
use std::collections::HashMap;
7+
use std::thread::sleep;
8+
use std::time::Duration;
9+
10+
fn foo() {
11+
let mut map: HashMap<&str, u32> = HashMap::new();
12+
assert_eq!(map.entry("poneyland").key(), &"poneyland");
13+
//~^ ERROR: is `1.3.0` but this item is stable since `1.10.0`
14+
if let Entry::Vacant(v) = map.entry("poneyland") {
15+
v.into_key();
16+
//~^ ERROR: is `1.3.0` but this item is stable since `1.12.0`
17+
}
18+
// Should warn for `sleep` but not for `Duration` (which was added in `1.3.0`).
19+
sleep(Duration::new(1, 0));
20+
//~^ ERROR: is `1.3.0` but this item is stable since `1.4.0`
21+
}
22+
23+
fn main() {}

tests/ui/incompatible_msrv.stderr

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.10.0`
2+
--> $DIR/incompatible_msrv.rs:12:39
3+
|
4+
LL | assert_eq!(map.entry("poneyland").key(), &"poneyland");
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.3.0` but this item is stable since `1.12.0`
11+
--> $DIR/incompatible_msrv.rs:15:11
12+
|
13+
LL | v.into_key();
14+
| ^^^^^^^^^^
15+
16+
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.4.0`
17+
--> $DIR/incompatible_msrv.rs:19:5
18+
|
19+
LL | sleep(Duration::new(1, 0));
20+
| ^^^^^
21+
22+
error: aborting due to 3 previous errors
23+

0 commit comments

Comments
 (0)