diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index 12dfb14c454d..e55edb1fcaa8 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -4,12 +4,12 @@ use clippy_utils::is_in_test; use clippy_utils::msrvs::Msrv; use rustc_attr_parsing::{RustcVersion, StabilityLevel, StableSince}; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::{Expr, ExprKind, HirId}; +use rustc_hir::{Expr, ExprKind, HirId, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; use rustc_span::def_id::DefId; -use rustc_span::{ExpnKind, Span}; +use rustc_span::{ExpnKind, Span, sym}; declare_clippy_lint! { /// ### What it does @@ -93,6 +93,21 @@ impl IncompatibleMsrv { // Intentionally not using `.from_expansion()`, since we do still care about macro expansions return; } + + // Functions coming from `core` while expanding a macro such as `assert*!()` get to cheat too: the + // macros may have existed prior to the checked MSRV, but their expansion with a recent compiler + // might use recent functions or methods. Compiling with an older compiler would not use those. + if span.from_expansion() + && cx.tcx.crate_name(def_id.krate) == sym::core + && span + .ctxt() + .outer_expn_data() + .macro_def_id + .is_some_and(|def_id| cx.tcx.crate_name(def_id.krate) == sym::core) + { + return; + } + 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) @@ -118,8 +133,11 @@ impl<'tcx> LateLintPass<'tcx> for IncompatibleMsrv { self.emit_lint_if_under_msrv(cx, method_did, expr.hir_id, span); } }, - ExprKind::Call(call, [_]) => { - if let ExprKind::Path(qpath) = call.kind + 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); diff --git a/tests/ui/incompatible_msrv.rs b/tests/ui/incompatible_msrv.rs index b4fea4cae5ed..99101b2bb8f2 100644 --- a/tests/ui/incompatible_msrv.rs +++ b/tests/ui/incompatible_msrv.rs @@ -1,5 +1,6 @@ #![warn(clippy::incompatible_msrv)] #![feature(custom_inner_attributes)] +#![feature(panic_internals)] #![clippy::msrv = "1.3.0"] use std::collections::HashMap; @@ -34,4 +35,42 @@ async fn issue12273(v: impl Future) { v.await; } +fn core_special_treatment(p: bool) { + // Do not lint code coming from `core` macros expanding into `core` function calls + if p { + panic!("foo"); // Do not lint + } + + // But still lint code calling `core` functions directly + if p { + core::panicking::panic("foo"); + //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0` + } + + // Lint code calling `core` from non-`core` macros + macro_rules! my_panic { + ($msg:expr) => { + core::panicking::panic($msg) + }; //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0` + } + my_panic!("foo"); + + // Lint even when the macro comes from `core` and calls `core` functions + assert!(core::panicking::panic("out of luck")); + //~^ ERROR: is `1.3.0` but this item is stable since `1.6.0` +} + +#[clippy::msrv = "1.26.0"] +fn lang_items() { + // Do not lint lang items. `..=` will expand into `RangeInclusive::new()`, which was introduced + // in Rust 1.27.0. + let _ = 1..=3; +} + +#[clippy::msrv = "1.80.0"] +fn issue14212() { + let _ = std::iter::repeat_n((), 5); + //~^ ERROR: is `1.80.0` but this item is stable since `1.82.0` +} + fn main() {} diff --git a/tests/ui/incompatible_msrv.stderr b/tests/ui/incompatible_msrv.stderr index 56c9eae5aafa..5ea2bb9cc58b 100644 --- a/tests/ui/incompatible_msrv.stderr +++ b/tests/ui/incompatible_msrv.stderr @@ -1,5 +1,5 @@ error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.10.0` - --> tests/ui/incompatible_msrv.rs:13:39 + --> tests/ui/incompatible_msrv.rs:14:39 | LL | assert_eq!(map.entry("poneyland").key(), &"poneyland"); | ^^^^^ @@ -8,16 +8,45 @@ LL | assert_eq!(map.entry("poneyland").key(), &"poneyland"); = 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` - --> tests/ui/incompatible_msrv.rs:17:11 + --> tests/ui/incompatible_msrv.rs:18: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` - --> tests/ui/incompatible_msrv.rs:21:5 + --> tests/ui/incompatible_msrv.rs:22:5 | LL | sleep(Duration::new(1, 0)); | ^^^^^ -error: aborting due to 3 previous errors +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0` + --> tests/ui/incompatible_msrv.rs:46:9 + | +LL | core::panicking::panic("foo"); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0` + --> tests/ui/incompatible_msrv.rs:53:13 + | +LL | core::panicking::panic($msg) + | ^^^^^^^^^^^^^^^^^^^^^^ +... +LL | my_panic!("foo"); + | ---------------- in this macro invocation + | + = note: this error originates in the macro `my_panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.6.0` + --> tests/ui/incompatible_msrv.rs:59:13 + | +LL | assert!(core::panicking::panic("out of luck")); + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: current MSRV (Minimum Supported Rust Version) is `1.80.0` but this item is stable since `1.82.0` + --> tests/ui/incompatible_msrv.rs:72:13 + | +LL | let _ = std::iter::repeat_n((), 5); + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 7 previous errors