-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Various improvements to the incompatible_msrv
lint
#14433
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
Changes from all commits
96ae10e
e236bfc
f2d4973
205e313
e3ec0d2
68ea461
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 |
---|---|---|
@@ -1,8 +1,8 @@ | ||
use clippy_config::Conf; | ||
use clippy_utils::diagnostics::span_lint; | ||
use clippy_utils::diagnostics::span_lint_and_then; | ||
use clippy_utils::is_in_test; | ||
use clippy_utils::msrvs::Msrv; | ||
use rustc_attr_data_structures::{RustcVersion, StabilityLevel, StableSince}; | ||
use rustc_attr_data_structures::{RustcVersion, Stability, StableSince}; | ||
use rustc_data_structures::fx::FxHashMap; | ||
use rustc_hir::{Expr, ExprKind, HirId, QPath}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
|
@@ -33,15 +33,54 @@ declare_clippy_lint! { | |
/// | ||
/// To fix this problem, either increase your MSRV or use another item | ||
/// available in your current MSRV. | ||
/// | ||
/// You can also locally change the MSRV that should be checked by Clippy, | ||
/// for example if a feature in your crate (e.g., `modern_compiler`) should | ||
/// allow you to use an item: | ||
/// | ||
/// ```no_run | ||
/// //! This crate has a MSRV of 1.3.0, but we also have an optional feature | ||
/// //! `sleep_well` which requires at least Rust 1.4.0. | ||
/// | ||
/// // When the `sleep_well` feature is set, do not warn for functions available | ||
/// // in Rust 1.4.0 and below. | ||
/// #![cfg_attr(feature = "sleep_well", clippy::msrv = "1.4.0")] | ||
/// | ||
/// use std::time::Duration; | ||
/// | ||
/// #[cfg(feature = "sleep_well")] | ||
/// fn sleep_for_some_time() { | ||
/// std::thread::sleep(Duration::new(1, 0)); // Will not trigger the lint | ||
/// } | ||
/// ``` | ||
/// | ||
/// You can also increase the MSRV in tests, by using: | ||
/// | ||
/// ```no_run | ||
/// // Use a much higher MSRV for tests while keeping the main one low | ||
/// #![cfg_attr(test, clippy::msrv = "1.85.0")] | ||
/// | ||
/// #[test] | ||
/// fn my_test() { | ||
/// // The tests can use items introduced in Rust 1.85.0 and lower | ||
/// // without triggering the `incompatible_msrv` lint. | ||
/// } | ||
/// ``` | ||
#[clippy::version = "1.78.0"] | ||
pub INCOMPATIBLE_MSRV, | ||
suspicious, | ||
"ensures that all items used in the crate are available for the current MSRV" | ||
} | ||
|
||
#[derive(Clone, Copy)] | ||
enum Availability { | ||
FeatureEnabled, | ||
Since(RustcVersion), | ||
} | ||
|
||
pub struct IncompatibleMsrv { | ||
msrv: Msrv, | ||
is_above_msrv: FxHashMap<DefId, RustcVersion>, | ||
availability_cache: FxHashMap<DefId, Availability>, | ||
check_in_tests: bool, | ||
} | ||
|
||
|
@@ -51,35 +90,32 @@ impl IncompatibleMsrv { | |
pub fn new(conf: &'static Conf) -> Self { | ||
Self { | ||
msrv: conf.msrv, | ||
is_above_msrv: FxHashMap::default(), | ||
availability_cache: FxHashMap::default(), | ||
check_in_tests: conf.check_incompatible_msrv_in_tests, | ||
} | ||
} | ||
|
||
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; | ||
/// Returns the availability of `def_id`, whether it is enabled through a feature or | ||
/// available since a given version (the default being Rust 1.0.0). | ||
fn get_def_id_availability(&mut self, tcx: TyCtxt<'_>, def_id: DefId) -> Availability { | ||
if let Some(availability) = self.availability_cache.get(&def_id) { | ||
return *availability; | ||
} | ||
let version = if let Some(version) = tcx | ||
.lookup_stability(def_id) | ||
.and_then(|stability| match stability.level { | ||
StabilityLevel::Stable { | ||
since: StableSince::Version(version), | ||
.. | ||
} => Some(version), | ||
_ => None, | ||
}) { | ||
version | ||
let stability = tcx.lookup_stability(def_id); | ||
let version = if stability.is_some_and(|stability| tcx.features().enabled(stability.feature)) { | ||
Availability::FeatureEnabled | ||
} else if let Some(StableSince::Version(version)) = stability.as_ref().and_then(Stability::stable_since) { | ||
Availability::Since(version) | ||
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) { | ||
self.get_def_id_version(tcx, parent_def_id) | ||
self.get_def_id_availability(tcx, parent_def_id) | ||
} else { | ||
RustcVersion { | ||
Availability::Since(RustcVersion { | ||
major: 1, | ||
minor: 0, | ||
patch: 0, | ||
} | ||
}) | ||
}; | ||
self.is_above_msrv.insert(def_id, version); | ||
self.availability_cache.insert(def_id, version); | ||
version | ||
} | ||
|
||
|
@@ -110,40 +146,57 @@ impl IncompatibleMsrv { | |
|
||
if (self.check_in_tests || !is_in_test(cx.tcx, node)) | ||
&& let Some(current) = self.msrv.current(cx) | ||
&& let version = self.get_def_id_version(cx.tcx, def_id) | ||
&& let Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id) | ||
&& version > current | ||
{ | ||
span_lint( | ||
span_lint_and_then( | ||
cx, | ||
INCOMPATIBLE_MSRV, | ||
span, | ||
format!( | ||
"current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable since `{version}`" | ||
), | ||
|diag| { | ||
if is_under_cfg_attribute(cx, node) { | ||
diag.note_once("you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute"); | ||
} | ||
}, | ||
); | ||
} | ||
} | ||
} | ||
|
||
impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { | ||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { | ||
// TODO: check for const stability when in const context | ||
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. Is this limitation documented? Might be good to file an issue. 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've opened #15297 about it, thanks for reminding me of it. |
||
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, expr.hir_id, span); | ||
} | ||
}, | ||
ExprKind::Call(call, _) => { | ||
// Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should | ||
// not be linted as they will not be generated in older compilers if the function is not available, | ||
// and the compiler is allowed to call unstable functions. | ||
if let ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = 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, expr.hir_id, call.span); | ||
// Desugaring into function calls by the compiler will use `QPath::LangItem` variants. Those should | ||
// not be linted as they will not be generated in older compilers if the function is not available, | ||
// and the compiler is allowed to call unstable functions. | ||
ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) => { | ||
if let Some(path_def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() { | ||
self.emit_lint_if_under_msrv(cx, path_def_id, expr.hir_id, expr.span); | ||
} | ||
}, | ||
_ => {}, | ||
} | ||
} | ||
} | ||
|
||
/// Heuristic checking if the node `hir_id` is under a `#[cfg()]` or `#[cfg_attr()]` | ||
/// attribute. | ||
fn is_under_cfg_attribute(cx: &LateContext<'_>, hir_id: HirId) -> bool { | ||
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. Should this only add the note under 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. My rationale was that the lint only triggers when a MSRV is set on the current crate. However, when features are used, the author might want to express the fact that, in some configurations, they can go beyond the default MSRV. If the used entity is not behind a crate feature or a For example, |
||
cx.tcx.hir_parent_id_iter(hir_id).any(|id| { | ||
cx.tcx.hir_attrs(id).iter().any(|attr| { | ||
matches!( | ||
attr.ident().map(|ident| ident.name), | ||
Some(sym::cfg_trace | sym::cfg_attr_trace) | ||
) | ||
}) | ||
}) | ||
} |
Uh oh!
There was an error while loading. Please reload this page.