blob: 0ef5b803a89d6db3bd2bd611d02dede544ac5c2c [file] [log] [blame]
use clippy_config::msrvs::Msrv;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_in_test;
use rustc_attr::{StabilityLevel, StableSince};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::{Expr, ExprKind, HirId};
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::{ExpnKind, 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.78.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(conf: &'static Conf) -> Self {
Self {
msrv: conf.msrv.clone(),
is_above_msrv: FxHashMap::default(),
}
}
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.into(),
version.minor.into(),
version.patch.into(),
)),
_ => 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, node: HirId, 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) || is_in_test(cx.tcx, node) {
return;
}
if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = span.ctxt().outer_expn_data().kind {
// Desugared expressions get to cheat and stability is ignored.
// Intentionally not using `.from_expansion()`, since we do still care about macro expansions
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, expr.hir_id, 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, expr.hir_id, call.span);
}
},
_ => {},
}
}
}