-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Warn if an item coming from more recent version than MSRV is used
- Loading branch information
1 parent
900a5aa
commit 14e1520
Showing
9 changed files
with
197 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
"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) { | ||
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( | ||
version.major as _, | ||
version.minor as _, | ||
version.patch as _, | ||
)), | ||
_ => 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); | ||
} | ||
}, | ||
_ => {}, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|