Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions clippy_lints/src/incompatible_msrv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::Msrv;
use clippy_utils::{is_in_const_context, is_in_test};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::DefKind;
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, QPath, RustcVersion, StabilityLevel, StableSince};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
use rustc_middle::ty::{self, TyCtxt};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::{CrateNum, DefId};
use rustc_span::{ExpnKind, Span, sym};
Expand Down Expand Up @@ -83,6 +82,10 @@ pub struct IncompatibleMsrv {
availability_cache: FxHashMap<(DefId, bool), Availability>,
check_in_tests: bool,
core_crate: Option<CrateNum>,

// The most recently called path. Used to skip checking the path after it's
// been checked when visiting the call expression.
called_path: Option<HirId>,
}

impl_lint_pass!(IncompatibleMsrv => [INCOMPATIBLE_MSRV]);
Expand All @@ -98,6 +101,7 @@ impl IncompatibleMsrv {
.iter()
.find(|krate| tcx.crate_name(**krate) == sym::core)
.copied(),
called_path: None,
}
}

Expand Down Expand Up @@ -140,7 +144,14 @@ impl IncompatibleMsrv {
}

/// Emit lint if `def_id`, associated with `node` and `span`, is below the current MSRV.
fn emit_lint_if_under_msrv(&mut self, cx: &LateContext<'_>, def_id: DefId, node: HirId, span: Span) {
fn emit_lint_if_under_msrv(
&mut self,
cx: &LateContext<'_>,
needs_const: bool,
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;
Expand All @@ -158,10 +169,6 @@ impl IncompatibleMsrv {
return;
}

let needs_const = cx.enclosing_body.is_some()
&& is_in_const_context(cx)
&& matches!(cx.tcx.def_kind(def_id), DefKind::AssocFn | DefKind::Fn);

if (self.check_in_tests || !is_in_test(cx.tcx, node))
&& let Some(current) = self.msrv.current(cx)
&& let Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id, needs_const)
Expand Down Expand Up @@ -190,16 +197,35 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
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);
self.emit_lint_if_under_msrv(cx, is_in_const_context(cx), method_did, expr.hir_id, span);
}
},
ExprKind::Call(callee, _)
if let ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..))) = callee.kind =>
{
self.called_path = Some(callee.hir_id);
let needs_const = is_in_const_context(cx);
let def_id = if let Some(def_id) = cx.qpath_res(&qpath, callee.hir_id).opt_def_id() {
def_id
} else if needs_const && let ty::FnDef(def_id, _) = *cx.typeck_results().expr_ty(callee).kind() {
// Edge case where a function is first assigned then called.
// We previously would have warned for the non-const MSRV, when
// checking the path, but now that it's called the const MSRV
// must also be met.
def_id
} else {
return;
};
self.emit_lint_if_under_msrv(cx, needs_const, def_id, expr.hir_id, callee.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);
}
ExprKind::Path(qpath @ (QPath::Resolved(..) | QPath::TypeRelative(..)))
if let Some(path_def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id()
&& self.called_path != Some(expr.hir_id) =>
{
self.emit_lint_if_under_msrv(cx, false, path_def_id, expr.hir_id, expr.span);
},
_ => {},
}
Expand All @@ -211,7 +237,7 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv {
// `CStr` and `CString` have been moved around but have been available since Rust 1.0.0
&& !matches!(cx.tcx.get_diagnostic_name(ty_def_id), Some(sym::cstr_type | sym::cstring_type))
{
self.emit_lint_if_under_msrv(cx, ty_def_id, hir_ty.hir_id, hir_ty.span);
self.emit_lint_if_under_msrv(cx, false, ty_def_id, hir_ty.hir_id, hir_ty.span);
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions tests/ui/incompatible_msrv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,14 @@ fn enum_variant_ok() {
let _ = const { std::io::ErrorKind::InvalidFilename };
}

#[clippy::msrv = "1.38.0"]
const fn uncalled_len() {
let _ = Vec::<usize>::len;
let x = str::len;
let _ = x("");
//~^ incompatible_msrv
let _ = "".len();
//~^ incompatible_msrv
}

fn main() {}
14 changes: 13 additions & 1 deletion tests/ui/incompatible_msrv.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,17 @@ error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item i
LL | let _ = const { std::io::ErrorKind::InvalidFilename };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 17 previous errors
error: current MSRV (Minimum Supported Rust Version) is `1.38.0` but this item is stable in a `const` context since `1.39.0`
--> tests/ui/incompatible_msrv.rs:175:13
|
LL | let _ = x("");
| ^

error: current MSRV (Minimum Supported Rust Version) is `1.38.0` but this item is stable in a `const` context since `1.39.0`
--> tests/ui/incompatible_msrv.rs:177:16
|
LL | let _ = "".len();
| ^^^^^

error: aborting due to 19 previous errors