about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSamuel Tardieu <sam@rfc1149.net>2025-07-16 21:13:43 +0200
committerSamuel Tardieu <sam@rfc1149.net>2025-07-17 14:44:56 +0200
commit167ac052df6dd5371798fe5ef944f4fd0d1dbe77 (patch)
treee80e33ab76f940b9c32d041f61935945d8d8af37
parente62e27bf5bbae5d0ba596ae43356a7c9c988a067 (diff)
downloadrust-167ac052df6dd5371798fe5ef944f4fd0d1dbe77.tar.gz
rust-167ac052df6dd5371798fe5ef944f4fd0d1dbe77.zip
Warn about `const` instability wrt MSRV
This makes `const` contexts use the `const`-stability version information
instead of the regular stability one, as `const`-stability may happen
much later than stability in non-`const` contexts.
-rw-r--r--clippy_lints/src/incompatible_msrv.rs48
-rw-r--r--tests/ui/incompatible_msrv.rs40
-rw-r--r--tests/ui/incompatible_msrv.stderr48
3 files changed, 107 insertions, 29 deletions
diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs
index addb1e7d755..116d63c3bb1 100644
--- a/clippy_lints/src/incompatible_msrv.rs
+++ b/clippy_lints/src/incompatible_msrv.rs
@@ -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;
@@ -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,
 }
 
@@ -96,18 +97,32 @@ 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,
@@ -115,10 +130,11 @@ impl IncompatibleMsrv {
                 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.
@@ -144,9 +160,13 @@ 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(
@@ -154,7 +174,8 @@ impl IncompatibleMsrv {
                 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) {
@@ -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) {
diff --git a/tests/ui/incompatible_msrv.rs b/tests/ui/incompatible_msrv.rs
index 1f9069c7c1c..f7f21e1850d 100644
--- a/tests/ui/incompatible_msrv.rs
+++ b/tests/ui/incompatible_msrv.rs
@@ -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;
@@ -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() {}
diff --git a/tests/ui/incompatible_msrv.stderr b/tests/ui/incompatible_msrv.stderr
index fff1b956561..e42360d296f 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:15:39
+  --> tests/ui/incompatible_msrv.rs:16:39
    |
 LL |     assert_eq!(map.entry("poneyland").key(), &"poneyland");
    |                                       ^^^^^
@@ -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);
    |                     ^^^^^^^^^^^^^^^^^^^^^
@@ -49,25 +49,25 @@ 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);
    |                 ^^^^^^^^^^^^^^^^^^^
@@ -75,22 +75,40 @@ 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