Skip to content

Warn about const instability wrt MSRV #15297

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

Merged
merged 1 commit into from
Jul 18, 2025
Merged
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
48 changes: 34 additions & 14 deletions clippy_lints/src/incompatible_msrv.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use clippy_config::Conf;
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, Stability, StableSince};
use clippy_utils::{is_in_const_context, is_in_test};
use rustc_attr_data_structures::{RustcVersion, StabilityLevel, StableSince};
use rustc_data_structures::fx::FxHashMap;
use rustc_hir::def::DefKind;
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::TyCtxt;
Expand Down Expand Up @@ -80,7 +81,7 @@ enum Availability {

pub struct IncompatibleMsrv {
msrv: Msrv,
availability_cache: FxHashMap<DefId, Availability>,
availability_cache: FxHashMap<(DefId, bool), Availability>,
check_in_tests: bool,
}

Expand All @@ -96,29 +97,44 @@ impl IncompatibleMsrv {
}

/// 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) {
/// available since a given version (the default being Rust 1.0.0). `needs_const` requires
/// the `const`-stability to be looked up instead of the stability in non-`const` contexts.
fn get_def_id_availability(&mut self, tcx: TyCtxt<'_>, def_id: DefId, needs_const: bool) -> Availability {
if let Some(availability) = self.availability_cache.get(&(def_id, needs_const)) {
return *availability;
}
let stability = tcx.lookup_stability(def_id);
let version = if stability.is_some_and(|stability| tcx.features().enabled(stability.feature)) {
let (feature, stability_level) = if needs_const {
tcx.lookup_const_stability(def_id)
.map(|stability| (stability.feature, stability.level))
.unzip()
} else {
tcx.lookup_stability(def_id)
.map(|stability| (stability.feature, stability.level))
.unzip()
};
let version = if feature.is_some_and(|feature| tcx.features().enabled(feature)) {
Availability::FeatureEnabled
} else if let Some(StableSince::Version(version)) = stability.as_ref().and_then(Stability::stable_since) {
} else if let Some(StableSince::Version(version)) =
stability_level.as_ref().and_then(StabilityLevel::stable_since)
{
Availability::Since(version)
} else if needs_const {
// Fallback to regular stability
self.get_def_id_availability(tcx, def_id, false)
} else if let Some(parent_def_id) = tcx.opt_parent(def_id) {
self.get_def_id_availability(tcx, parent_def_id)
self.get_def_id_availability(tcx, parent_def_id, needs_const)
} else {
Availability::Since(RustcVersion {
major: 1,
minor: 0,
patch: 0,
})
};
self.availability_cache.insert(def_id, version);
self.availability_cache.insert((def_id, needs_const), version);
version
}

/// 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) {
if def_id.is_local() {
// We don't check local items since their MSRV is supposed to always be valid.
Expand All @@ -144,17 +160,22 @@ 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)
&& let Availability::Since(version) = self.get_def_id_availability(cx.tcx, def_id, needs_const)
&& version > current
{
span_lint_and_then(
cx,
INCOMPATIBLE_MSRV,
span,
format!(
"current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable since `{version}`"
"current MSRV (Minimum Supported Rust Version) is `{current}` but this item is stable{} since `{version}`",
if needs_const { " in a `const` context" } else { "" },
),
|diag| {
if is_under_cfg_attribute(cx, node) {
Expand All @@ -168,7 +189,6 @@ impl IncompatibleMsrv {

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
match expr.kind {
ExprKind::MethodCall(_, _, _, span) => {
if let Some(method_did) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
Expand Down
40 changes: 40 additions & 0 deletions tests/ui/incompatible_msrv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#![feature(strict_provenance)] // For use in test
#![clippy::msrv = "1.3.0"]

use std::cell::Cell;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::future::Future;
Expand Down Expand Up @@ -128,4 +129,43 @@ fn non_fn_items() {
//~^ incompatible_msrv
}

#[clippy::msrv = "1.87.0"]
fn msrv_non_ok_in_const() {
{
let c = Cell::new(42);
_ = c.get();
}
const {
let c = Cell::new(42);
_ = c.get();
//~^ incompatible_msrv
}
}

#[clippy::msrv = "1.88.0"]
fn msrv_ok_in_const() {
{
let c = Cell::new(42);
_ = c.get();
}
const {
let c = Cell::new(42);
_ = c.get();
}
}

#[clippy::msrv = "1.86.0"]
fn enum_variant_not_ok() {
let _ = std::io::ErrorKind::InvalidFilename;
//~^ incompatible_msrv
let _ = const { std::io::ErrorKind::InvalidFilename };
//~^ incompatible_msrv
}

#[clippy::msrv = "1.87.0"]
fn enum_variant_ok() {
let _ = std::io::ErrorKind::InvalidFilename;
let _ = const { std::io::ErrorKind::InvalidFilename };
}

fn main() {}
48 changes: 33 additions & 15 deletions tests/ui/incompatible_msrv.stderr
Original file line number Diff line number Diff line change
@@ -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:15:39
--> tests/ui/incompatible_msrv.rs:16:39
|
LL | assert_eq!(map.entry("poneyland").key(), &"poneyland");
| ^^^^^
Expand All @@ -8,37 +8,37 @@ 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:21:11
--> tests/ui/incompatible_msrv.rs:22: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:25:5
--> tests/ui/incompatible_msrv.rs:26:5
|
LL | sleep(Duration::new(1, 0));
| ^^^^^

error: current MSRV (Minimum Supported Rust Version) is `1.2.0` but this item is stable since `1.3.0`
--> tests/ui/incompatible_msrv.rs:30:33
--> tests/ui/incompatible_msrv.rs:31:33
|
LL | static NO_BODY_BAD_MSRV: Option<Duration> = None;
| ^^^^^^^^

error: current MSRV (Minimum Supported Rust Version) is `1.2.0` but this item is stable since `1.3.0`
--> tests/ui/incompatible_msrv.rs:37:19
--> tests/ui/incompatible_msrv.rs:38:19
|
LL | let _: Option<Duration> = None;
| ^^^^^^^^

error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0`
--> tests/ui/incompatible_msrv.rs:61:17
--> tests/ui/incompatible_msrv.rs:62:17
|
LL | let _ = core::iter::once_with(|| 0);
| ^^^^^^^^^^^^^^^^^^^^^

error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.43.0`
--> tests/ui/incompatible_msrv.rs:68:21
--> tests/ui/incompatible_msrv.rs:69:21
|
LL | let _ = core::iter::once_with(|| $msg);
| ^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -49,48 +49,66 @@ LL | my_panic!("foo");
= 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.43.0`
--> tests/ui/incompatible_msrv.rs:75:13
--> tests/ui/incompatible_msrv.rs:76:13
|
LL | assert!(core::iter::once_with(|| 0).next().is_some());
| ^^^^^^^^^^^^^^^^^^^^^

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:88:13
--> tests/ui/incompatible_msrv.rs:89:13
|
LL | let _ = std::iter::repeat_n((), 5);
| ^^^^^^^^^^^^^^^^^^^

error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0`
--> tests/ui/incompatible_msrv.rs:99:13
--> tests/ui/incompatible_msrv.rs:100:13
|
LL | let _ = std::iter::repeat_n((), 5);
| ^^^^^^^^^^^^^^^^^^^

error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0`
--> tests/ui/incompatible_msrv.rs:104:17
--> tests/ui/incompatible_msrv.rs:105:17
|
LL | let _ = std::iter::repeat_n((), 5);
| ^^^^^^^^^^^^^^^^^^^
|
= note: you may want to conditionally increase the MSRV considered by Clippy using the `clippy::msrv` attribute

error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.82.0`
--> tests/ui/incompatible_msrv.rs:109:17
--> tests/ui/incompatible_msrv.rs:110:17
|
LL | let _ = std::iter::repeat_n((), 5);
| ^^^^^^^^^^^^^^^^^^^

error: current MSRV (Minimum Supported Rust Version) is `1.78.0` but this item is stable since `1.84.0`
--> tests/ui/incompatible_msrv.rs:122:7
--> tests/ui/incompatible_msrv.rs:123:7
|
LL | r.isqrt()
| ^^^^^^^

error: current MSRV (Minimum Supported Rust Version) is `1.3.0` but this item is stable since `1.85.0`
--> tests/ui/incompatible_msrv.rs:127:13
--> tests/ui/incompatible_msrv.rs:128:13
|
LL | let _ = std::io::ErrorKind::CrossesDevices;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 14 previous errors
error: current MSRV (Minimum Supported Rust Version) is `1.87.0` but this item is stable in a `const` context since `1.88.0`
--> tests/ui/incompatible_msrv.rs:140:15
|
LL | _ = c.get();
| ^^^^^

error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item is stable since `1.87.0`
--> tests/ui/incompatible_msrv.rs:159:13
|
LL | let _ = std::io::ErrorKind::InvalidFilename;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: current MSRV (Minimum Supported Rust Version) is `1.86.0` but this item is stable since `1.87.0`
--> tests/ui/incompatible_msrv.rs:161:21
|
LL | let _ = const { std::io::ErrorKind::InvalidFilename };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to 17 previous errors