-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Warn if an item coming from more recent version than MSRV is used #12160
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,133 @@ | ||||||||||||||
use clippy_config::msrvs::Msrv; | ||||||||||||||
use clippy_utils::diagnostics::span_lint; | ||||||||||||||
use rustc_attr::{StabilityLevel, StableSince}; | ||||||||||||||
use rustc_data_structures::fx::FxHashMap; | ||||||||||||||
use rustc_hir::{Expr, ExprKind}; | ||||||||||||||
use rustc_lint::{LateContext, LateLintPass}; | ||||||||||||||
use rustc_middle::ty::TyCtxt; | ||||||||||||||
use rustc_semver::RustcVersion; | ||||||||||||||
use rustc_session::impl_lint_pass; | ||||||||||||||
use rustc_span::def_id::DefId; | ||||||||||||||
use rustc_span::Span; | ||||||||||||||
|
||||||||||||||
declare_clippy_lint! { | ||||||||||||||
/// ### What it does | ||||||||||||||
/// | ||||||||||||||
/// This lint checks that no function newer than the defined MSRV (minimum | ||||||||||||||
/// supported rust version) is used in the crate. | ||||||||||||||
/// | ||||||||||||||
/// ### Why is this bad? | ||||||||||||||
/// | ||||||||||||||
/// It would prevent the crate to be actually used with the specified MSRV. | ||||||||||||||
/// | ||||||||||||||
/// ### Example | ||||||||||||||
/// ```no_run | ||||||||||||||
/// // MSRV of 1.3.0 | ||||||||||||||
/// use std::thread::sleep; | ||||||||||||||
/// use std::time::Duration; | ||||||||||||||
/// | ||||||||||||||
/// // Sleep was defined in `1.4.0`. | ||||||||||||||
/// sleep(Duration::new(1, 0)); | ||||||||||||||
/// ``` | ||||||||||||||
/// | ||||||||||||||
/// To fix this problem, either increase your MSRV or use another item | ||||||||||||||
/// available in your current MSRV. | ||||||||||||||
#[clippy::version = "1.77.0"] | ||||||||||||||
pub INCOMPATIBLE_MSRV, | ||||||||||||||
suspicious, | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So... What do you think about putting this lint in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's pretty low risk (ie, warning will very likely always be right) to keep it into |
||||||||||||||
"ensures that all items used in the crate are available for the current MSRV" | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
pub struct IncompatibleMsrv { | ||||||||||||||
msrv: Msrv, | ||||||||||||||
is_above_msrv: FxHashMap<DefId, RustcVersion>, | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]); | ||||||||||||||
|
||||||||||||||
impl IncompatibleMsrv { | ||||||||||||||
pub fn new(msrv: Msrv) -> Self { | ||||||||||||||
Self { | ||||||||||||||
msrv, | ||||||||||||||
is_above_msrv: FxHashMap::default(), | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
#[allow(clippy::cast_lossless)] | ||||||||||||||
fn get_def_id_version(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> RustcVersion { | ||||||||||||||
if let Some(version) = self.is_above_msrv.get(&def_id) { | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for this light caching, it does indeed work! yeehaw 🤠 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 😃 |
||||||||||||||
return *version; | ||||||||||||||
} | ||||||||||||||
let version = if let Some(version) = tcx | ||||||||||||||
.lookup_stability(def_id) | ||||||||||||||
.and_then(|stability| match stability.level { | ||||||||||||||
StabilityLevel::Stable { | ||||||||||||||
since: StableSince::Version(version), | ||||||||||||||
.. | ||||||||||||||
} => Some(RustcVersion::new( | ||||||||||||||
Comment on lines
+61
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In case There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (and yes, when its stabilized, I would love to tell clippy or rustc the minimum version for every dependency and have a lint to catch crate dependencies and not just std/alloc/core) |
||||||||||||||
version.major as _, | ||||||||||||||
version.minor as _, | ||||||||||||||
version.patch as _, | ||||||||||||||
)), | ||||||||||||||
Comment on lines
+67
to
+71
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Meow (does this work?)
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Doesn't work unfortunately:
|
||||||||||||||
_ => None, | ||||||||||||||
}) { | ||||||||||||||
version | ||||||||||||||
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) { | ||||||||||||||
self.get_def_id_version(tcx, parent_def_id) | ||||||||||||||
} else { | ||||||||||||||
RustcVersion::new(1, 0, 0) | ||||||||||||||
}; | ||||||||||||||
self.is_above_msrv.insert(def_id, version); | ||||||||||||||
version | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, span: Span) { | ||||||||||||||
if def_id.is_local() { | ||||||||||||||
// We don't check local items since their MSRV is supposed to always be valid. | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
let version = self.get_def_id_version(cx.tcx, def_id); | ||||||||||||||
if self.msrv.meets(version) { | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
self.emit_lint_for(cx, span, version); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
fn emit_lint_for(&self, cx: &LateContext<'_>, span: Span, version: RustcVersion) { | ||||||||||||||
span_lint( | ||||||||||||||
cx, | ||||||||||||||
INCOMPATIBLE_MSRV, | ||||||||||||||
span, | ||||||||||||||
&format!( | ||||||||||||||
"current MSRV (Minimum Supported Rust Version) is `{}` but this item is stable since `{version}`", | ||||||||||||||
self.msrv | ||||||||||||||
), | ||||||||||||||
); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { | ||||||||||||||
extract_msrv_attr!(LateContext); | ||||||||||||||
|
||||||||||||||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { | ||||||||||||||
if self.msrv.current().is_none() { | ||||||||||||||
// If there is no MSRV, then no need to check anything... | ||||||||||||||
return; | ||||||||||||||
} | ||||||||||||||
match expr.kind { | ||||||||||||||
ExprKind::MethodCall(_, _, _, span) => { | ||||||||||||||
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) { | ||||||||||||||
self.emit_lint_if_under_msrv(cx, method_did, span); | ||||||||||||||
} | ||||||||||||||
}, | ||||||||||||||
ExprKind::Call(call, [_]) => { | ||||||||||||||
if let ExprKind::Path(qpath) = call.kind | ||||||||||||||
&& let Some(path_def_id) = cx.qpath_res(&qpath, call.hir_id).opt_def_id() | ||||||||||||||
{ | ||||||||||||||
self.emit_lint_if_under_msrv(cx, path_def_id, call.span); | ||||||||||||||
} | ||||||||||||||
}, | ||||||||||||||
_ => {}, | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#![warn(clippy::incompatible_msrv)] | ||
#![feature(custom_inner_attributes)] | ||
#![clippy::msrv = "1.3.0"] | ||
|
||
use std::collections::hash_map::Entry; | ||
use std::collections::HashMap; | ||
use std::thread::sleep; | ||
use std::time::Duration; | ||
|
||
fn foo() { | ||
let mut map: HashMap<&str, u32> = HashMap::new(); | ||
assert_eq!(map.entry("poneyland").key(), &"poneyland"); | ||
//~^ ERROR: is `1.3.0` but this item is stable since `1.10.0` | ||
if let Entry::Vacant(v) = map.entry("poneyland") { | ||
v.into_key(); | ||
//~^ ERROR: is `1.3.0` but this item is stable since `1.12.0` | ||
} | ||
// Should warn for `sleep` but not for `Duration` (which was added in `1.3.0`). | ||
sleep(Duration::new(1, 0)); | ||
//~^ ERROR: is `1.3.0` but this item is stable since `1.4.0` | ||
} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.10.0` | ||
--> $DIR/incompatible_msrv.rs:12:39 | ||
| | ||
LL | assert_eq!(map.entry("poneyland").key(), &"poneyland"); | ||
| ^^^^^ | ||
| | ||
= 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.3.0` but this item is stable since `1.12.0` | ||
--> $DIR/incompatible_msrv.rs:15:11 | ||
| | ||
LL | v.into_key(); | ||
| ^^^^^^^^^^ | ||
|
||
error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.4.0` | ||
--> $DIR/incompatible_msrv.rs:19:5 | ||
| | ||
LL | sleep(Duration::new(1, 0)); | ||
| ^^^^^ | ||
|
||
error: aborting due to 3 previous errors | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit hesitant about using "msrv" because (1) it is an acronym and (2) not everyone knows what it means.
Would "incompatible_rust_version` work for a lint name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hum... What do you think about
incompatible_minimum_rust_version
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No meaningful preference either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll go for the more explicit one then.