about summary refs log tree commit diff
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2024-11-02 21:19:21 +0100
committerRalf Jung <post@ralfj.de>2024-11-10 10:16:26 +0100
commite96808162ad7ff5906d7b58d32a25abe139e998c (patch)
tree039f785ca0af8ef506a8b07eac1e1e822ada4917
parent686eeb83e9c6d7f70848cdf84f490f5c1aa3edd3 (diff)
downloadrust-e96808162ad7ff5906d7b58d32a25abe139e998c.tar.gz
rust-e96808162ad7ff5906d7b58d32a25abe139e998c.zip
ensure that all publicly reachable const fn have const stability info
-rw-r--r--compiler/rustc_attr/src/builtin.rs50
-rw-r--r--compiler/rustc_const_eval/src/check_consts/check.rs24
-rw-r--r--compiler/rustc_const_eval/src/check_consts/mod.rs13
-rw-r--r--compiler/rustc_expand/src/base.rs4
-rw-r--r--compiler/rustc_passes/src/stability.rs194
-rw-r--r--src/librustdoc/html/render/mod.rs4
-rw-r--r--src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs8
-rw-r--r--tests/ui/consts/const-unstable-intrinsic.rs6
-rw-r--r--tests/ui/consts/const-unstable-intrinsic.stderr30
-rw-r--r--tests/ui/consts/rustc-const-stability-require-const.rs6
-rw-r--r--tests/ui/consts/rustc-const-stability-require-const.stderr14
-rw-r--r--tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr22
12 files changed, 174 insertions, 201 deletions
diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs
index 3a7ea36f601..94f9727eb7f 100644
--- a/compiler/rustc_attr/src/builtin.rs
+++ b/compiler/rustc_attr/src/builtin.rs
@@ -16,9 +16,9 @@ use rustc_session::lint::BuiltinLintDiag;
 use rustc_session::lint::builtin::UNEXPECTED_CFGS;
 use rustc_session::parse::feature_err;
 use rustc_session::{RustcVersion, Session};
+use rustc_span::Span;
 use rustc_span::hygiene::Transparency;
 use rustc_span::symbol::{Symbol, kw, sym};
-use rustc_span::{DUMMY_SP, Span};
 
 use crate::fluent_generated;
 use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause};
@@ -92,9 +92,7 @@ impl Stability {
 #[derive(HashStable_Generic)]
 pub struct ConstStability {
     pub level: StabilityLevel,
-    /// This can be `None` for functions that do not have an explicit const feature.
-    /// We still track them for recursive const stability checks.
-    pub feature: Option<Symbol>,
+    pub feature: Symbol,
     /// This is true iff the `const_stable_indirect` attribute is present.
     pub const_stable_indirect: bool,
     /// whether the function has a `#[rustc_promotable]` attribute
@@ -272,22 +270,19 @@ pub fn find_stability(
 
 /// Collects stability info from `rustc_const_stable`/`rustc_const_unstable`/`rustc_promotable`
 /// attributes in `attrs`. Returns `None` if no stability attributes are found.
-///
-/// `is_const_fn` indicates whether this is a function marked as `const`.
 pub fn find_const_stability(
     sess: &Session,
     attrs: &[Attribute],
     item_sp: Span,
-    is_const_fn: bool,
 ) -> Option<(ConstStability, Span)> {
     let mut const_stab: Option<(ConstStability, Span)> = None;
     let mut promotable = false;
-    let mut const_stable_indirect = None;
+    let mut const_stable_indirect = false;
 
     for attr in attrs {
         match attr.name_or_empty() {
             sym::rustc_promotable => promotable = true,
-            sym::rustc_const_stable_indirect => const_stable_indirect = Some(attr.span),
+            sym::rustc_const_stable_indirect => const_stable_indirect = true,
             sym::rustc_const_unstable => {
                 if const_stab.is_some() {
                     sess.dcx()
@@ -299,7 +294,7 @@ pub fn find_const_stability(
                     const_stab = Some((
                         ConstStability {
                             level,
-                            feature: Some(feature),
+                            feature,
                             const_stable_indirect: false,
                             promotable: false,
                         },
@@ -317,7 +312,7 @@ pub fn find_const_stability(
                     const_stab = Some((
                         ConstStability {
                             level,
-                            feature: Some(feature),
+                            feature,
                             const_stable_indirect: false,
                             promotable: false,
                         },
@@ -340,7 +335,7 @@ pub fn find_const_stability(
             }
         }
     }
-    if const_stable_indirect.is_some() {
+    if const_stable_indirect {
         match &mut const_stab {
             Some((stab, _)) => {
                 if stab.is_const_unstable() {
@@ -351,32 +346,13 @@ pub fn find_const_stability(
                     })
                 }
             }
-            _ => {}
+            _ => {
+                // This function has no const stability attribute, but has `const_stable_indirect`.
+                // We ignore that; unmarked functions are subject to recursive const stability
+                // checks by default so we do carry out the user's intent.
+            }
         }
     }
-    // Make sure if `const_stable_indirect` is present, that is recorded. Also make sure all `const
-    // fn` get *some* marker, since we are a staged_api crate and therefore will do recursive const
-    // stability checks for them. We need to do this because the default for whether an unmarked
-    // function enforces recursive stability differs between staged-api crates and force-unmarked
-    // crates: in force-unmarked crates, only functions *explicitly* marked `const_stable_indirect`
-    // enforce recursive stability. Therefore when `lookup_const_stability` is `None`, we have to
-    // assume the function does not have recursive stability. All functions that *do* have recursive
-    // stability must explicitly record this, and so that's what we do for all `const fn` in a
-    // staged_api crate.
-    if (is_const_fn || const_stable_indirect.is_some()) && const_stab.is_none() {
-        let c = ConstStability {
-            feature: None,
-            const_stable_indirect: const_stable_indirect.is_some(),
-            promotable: false,
-            level: StabilityLevel::Unstable {
-                reason: UnstableReason::Default,
-                issue: None,
-                is_soft: false,
-                implied_by: None,
-            },
-        };
-        const_stab = Some((c, const_stable_indirect.unwrap_or(DUMMY_SP)));
-    }
 
     const_stab
 }
@@ -394,7 +370,7 @@ pub fn unmarked_crate_const_stab(
     let const_stable_indirect =
         attrs.iter().any(|a| a.name_or_empty() == sym::rustc_const_stable_indirect);
     ConstStability {
-        feature: Some(regular_stab.feature),
+        feature: regular_stab.feature,
         const_stable_indirect,
         promotable: false,
         level: regular_stab.level,
diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs
index 8cd0ecb3e4e..886ebf1a5a8 100644
--- a/compiler/rustc_const_eval/src/check_consts/check.rs
+++ b/compiler/rustc_const_eval/src/check_consts/check.rs
@@ -709,6 +709,13 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
 
                 // Intrinsics are language primitives, not regular calls, so treat them separately.
                 if let Some(intrinsic) = tcx.intrinsic(callee) {
+                    if !tcx.is_const_fn(callee) {
+                        // Non-const intrinsic.
+                        self.check_op(ops::IntrinsicNonConst { name: intrinsic.name });
+                        // If we allowed this, we're in miri-unleashed mode, so we might
+                        // as well skip the remaining checks.
+                        return;
+                    }
                     // We use `intrinsic.const_stable` to determine if this can be safely exposed to
                     // stable code, rather than `const_stable_indirect`. This is to make
                     // `#[rustc_const_stable_indirect]` an attribute that is always safe to add.
@@ -716,17 +723,12 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                     // fallback body is safe to expose on stable.
                     let is_const_stable = intrinsic.const_stable
                         || (!intrinsic.must_be_overridden
-                            && tcx.is_const_fn(callee)
                             && is_safe_to_expose_on_stable_const_fn(tcx, callee));
                     match tcx.lookup_const_stability(callee) {
                         None => {
-                            // Non-const intrinsic.
-                            self.check_op(ops::IntrinsicNonConst { name: intrinsic.name });
-                        }
-                        Some(ConstStability { feature: None, .. }) => {
-                            // Intrinsic does not need a separate feature gate (we rely on the
-                            // regular stability checker). However, we have to worry about recursive
-                            // const stability.
+                            // This doesn't need a separate const-stability check -- const-stability equals
+                            // regular stability, and regular stability is checked separately.
+                            // However, we *do* have to worry about *recursive* const stability.
                             if !is_const_stable && self.enforce_recursive_const_stability() {
                                 self.dcx().emit_err(errors::UnmarkedIntrinsicExposed {
                                     span: self.span,
@@ -735,8 +737,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                             }
                         }
                         Some(ConstStability {
-                            feature: Some(feature),
                             level: StabilityLevel::Unstable { .. },
+                            feature,
                             ..
                         }) => {
                             self.check_op(ops::IntrinsicUnstable {
@@ -773,7 +775,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                     Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => {
                         // All good.
                     }
-                    None | Some(ConstStability { feature: None, .. }) => {
+                    None => {
                         // This doesn't need a separate const-stability check -- const-stability equals
                         // regular stability, and regular stability is checked separately.
                         // However, we *do* have to worry about *recursive* const stability.
@@ -787,8 +789,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
                         }
                     }
                     Some(ConstStability {
-                        feature: Some(feature),
                         level: StabilityLevel::Unstable { implied_by: implied_feature, .. },
+                        feature,
                         ..
                     }) => {
                         // An unstable const fn with a feature gate.
diff --git a/compiler/rustc_const_eval/src/check_consts/mod.rs b/compiler/rustc_const_eval/src/check_consts/mod.rs
index 3b2a79793ae..d49d59c2a08 100644
--- a/compiler/rustc_const_eval/src/check_consts/mod.rs
+++ b/compiler/rustc_const_eval/src/check_consts/mod.rs
@@ -110,14 +110,15 @@ pub fn is_safe_to_expose_on_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> b
 
     match tcx.lookup_const_stability(def_id) {
         None => {
-            // Only marked functions can be trusted. Note that this may be a function in a
-            // non-staged-API crate where no recursive checks were done!
-            false
+            // In a `staged_api` crate, we do enforce recursive const stability for all unmarked
+            // functions, so we can trust local functions. But in another crate we don't know which
+            // rules were applied, so we can't trust that.
+            def_id.is_local() && tcx.features().staged_api()
         }
         Some(stab) => {
-            // We consider things safe-to-expose if they are stable, if they don't have any explicit
-            // const stability attribute, or if they are marked as `const_stable_indirect`.
-            stab.is_const_stable() || stab.feature.is_none() || stab.const_stable_indirect
+            // We consider things safe-to-expose if they are stable or if they are marked as
+            // `const_stable_indirect`.
+            stab.is_const_stable() || stab.const_stable_indirect
         }
     }
 }
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 7e4bc508e5c..bed500c3032 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -866,9 +866,7 @@ impl SyntaxExtension {
             })
             .unwrap_or_else(|| (None, helper_attrs));
         let stability = attr::find_stability(sess, attrs, span);
-        // We set `is_const_fn` false to avoid getting any implicit const stability.
-        let const_stability =
-            attr::find_const_stability(sess, attrs, span, /* is_const_fn */ false);
+        let const_stability = attr::find_const_stability(sess, attrs, span);
         let body_stability = attr::find_body_stability(sess, attrs);
         if let Some((_, sp)) = const_stability {
             sess.dcx().emit_err(errors::MacroConstStability {
diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs
index 264dd364b5b..4a793f1875e 100644
--- a/compiler/rustc_passes/src/stability.rs
+++ b/compiler/rustc_passes/src/stability.rs
@@ -16,7 +16,7 @@ use rustc_hir::def::{DefKind, Res};
 use rustc_hir::def_id::{CRATE_DEF_ID, LOCAL_CRATE, LocalDefId, LocalModDefId};
 use rustc_hir::hir_id::CRATE_HIR_ID;
 use rustc_hir::intravisit::{self, Visitor};
-use rustc_hir::{Constness, FieldDef, Item, ItemKind, TraitRef, Ty, TyKind, Variant};
+use rustc_hir::{FieldDef, Item, ItemKind, TraitRef, Ty, TyKind, Variant};
 use rustc_middle::hir::nested_filter;
 use rustc_middle::middle::lib_features::{FeatureStability, LibFeatures};
 use rustc_middle::middle::privacy::EffectiveVisibilities;
@@ -166,68 +166,11 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
             return;
         }
 
+        // # Regular and body stability
+
         let stab = attr::find_stability(self.tcx.sess, attrs, item_sp);
-        let const_stab = attr::find_const_stability(
-            self.tcx.sess,
-            attrs,
-            item_sp,
-            fn_sig.is_some_and(|s| s.header.is_const()),
-        );
         let body_stab = attr::find_body_stability(self.tcx.sess, attrs);
 
-        // If the current node is a function with const stability attributes (directly given or
-        // implied), check if the function/method is const or the parent impl block is const.
-        if let Some(fn_sig) = fn_sig
-            && !fn_sig.header.is_const()
-            && const_stab.is_some()
-        {
-            self.tcx.dcx().emit_err(errors::MissingConstErr { fn_sig_span: fn_sig.span });
-        }
-
-        // If this is marked const *stable*, it must also be regular-stable.
-        if let Some((const_stab, const_span)) = const_stab
-            && let Some(fn_sig) = fn_sig
-            && const_stab.is_const_stable()
-            && !stab.is_some_and(|(s, _)| s.is_stable())
-        {
-            self.tcx
-                .dcx()
-                .emit_err(errors::ConstStableNotStable { fn_sig_span: fn_sig.span, const_span });
-        }
-
-        // Stable *language* features shouldn't be used as unstable library features.
-        // (Not doing this for stable library features is checked by tidy.)
-        if let Some((
-            ConstStability { level: Unstable { .. }, feature: Some(feature), .. },
-            const_span,
-        )) = const_stab
-        {
-            if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() {
-                self.tcx.dcx().emit_err(errors::UnstableAttrForAlreadyStableFeature {
-                    span: const_span,
-                    item_sp,
-                });
-            }
-        }
-
-        let const_stab = const_stab.map(|(const_stab, _span)| {
-            self.index.const_stab_map.insert(def_id, const_stab);
-            const_stab
-        });
-
-        // `impl const Trait for Type` items forward their const stability to their
-        // immediate children.
-        // FIXME(const_trait_impl): how is this supposed to interact with `#[rustc_const_stable_indirect]`?
-        // Currently, once that is set, we do not inherit anything from the parent any more.
-        if const_stab.is_none() {
-            debug!("annotate: const_stab not found, parent = {:?}", self.parent_const_stab);
-            if let Some(parent) = self.parent_const_stab {
-                if parent.is_const_unstable() {
-                    self.index.const_stab_map.insert(def_id, parent);
-                }
-            }
-        }
-
         if let Some((depr, span)) = &depr
             && depr.is_since_rustc_version()
             && stab.is_none()
@@ -294,15 +237,6 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
                 self.index.implications.insert(implied_by, feature);
             }
 
-            if let Some(ConstStability {
-                level: Unstable { implied_by: Some(implied_by), .. },
-                feature,
-                ..
-            }) = const_stab
-            {
-                self.index.implications.insert(implied_by, feature.unwrap());
-            }
-
             self.index.stab_map.insert(def_id, stab);
             stab
         });
@@ -316,6 +250,91 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
             }
         }
 
+        let final_stab = self.index.stab_map.get(&def_id);
+
+        // # Const stability
+
+        let const_stab = attr::find_const_stability(self.tcx.sess, attrs, item_sp);
+
+        // If the current node is a function with const stability attributes (directly given or
+        // implied), check if the function/method is const.
+        if let Some(fn_sig) = fn_sig
+            && !fn_sig.header.is_const()
+            && const_stab.is_some()
+        {
+            self.tcx.dcx().emit_err(errors::MissingConstErr { fn_sig_span: fn_sig.span });
+        }
+
+        // If this is marked const *stable*, it must also be regular-stable.
+        if let Some((const_stab, const_span)) = const_stab
+            && let Some(fn_sig) = fn_sig
+            && const_stab.is_const_stable()
+            && !stab.is_some_and(|s| s.is_stable())
+        {
+            self.tcx
+                .dcx()
+                .emit_err(errors::ConstStableNotStable { fn_sig_span: fn_sig.span, const_span });
+        }
+
+        // Stable *language* features shouldn't be used as unstable library features.
+        // (Not doing this for stable library features is checked by tidy.)
+        if let Some((ConstStability { level: Unstable { .. }, feature, .. }, const_span)) =
+            const_stab
+        {
+            if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() {
+                self.tcx.dcx().emit_err(errors::UnstableAttrForAlreadyStableFeature {
+                    span: const_span,
+                    item_sp,
+                });
+            }
+        }
+
+        // After checking the immediate attributes, get rid of the span and compute implied
+        // const stability: inherit feature gate from regular stability.
+        let mut const_stab = const_stab.map(|(stab, _span)| stab);
+
+        // If this is a const fn but not annotated with stability markers, see if we can inherit regular stability.
+        if fn_sig.is_some_and(|s| s.header.is_const())  && const_stab.is_none() &&
+            // We only ever inherit unstable features.
+            let Some(inherit_regular_stab) =
+                final_stab.filter(|s| s.is_unstable())
+        {
+            const_stab = Some(ConstStability {
+                // We subject these implicitly-const functions to recursive const stability.
+                const_stable_indirect: true,
+                promotable: false,
+                level: inherit_regular_stab.level,
+                feature: inherit_regular_stab.feature,
+            });
+        }
+
+        // Now that everything is computed, insert it into the table.
+        const_stab.inspect(|const_stab| {
+            self.index.const_stab_map.insert(def_id, *const_stab);
+        });
+
+        if let Some(ConstStability {
+            level: Unstable { implied_by: Some(implied_by), .. },
+            feature,
+            ..
+        }) = const_stab
+        {
+            self.index.implications.insert(implied_by, feature);
+        }
+
+        // `impl const Trait for Type` items forward their const stability to their
+        // immediate children.
+        // FIXME(const_trait_impl): how is this supposed to interact with `#[rustc_const_stable_indirect]`?
+        // Currently, once that is set, we do not inherit anything from the parent any more.
+        if const_stab.is_none() {
+            debug!("annotate: const_stab not found, parent = {:?}", self.parent_const_stab);
+            if let Some(parent) = self.parent_const_stab {
+                if parent.is_const_unstable() {
+                    self.index.const_stab_map.insert(def_id, parent);
+                }
+            }
+        }
+
         self.recurse_with_stability_attrs(
             depr.map(|(d, _)| DeprecationEntry::local(d, def_id)),
             stab,
@@ -570,13 +589,7 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> {
         }
     }
 
-    fn check_missing_or_wrong_const_stability(&self, def_id: LocalDefId, span: Span) {
-        // The visitor runs for "unstable-if-unmarked" crates, but we don't yet support
-        // that on the const side.
-        if !self.tcx.features().staged_api() {
-            return;
-        }
-
+    fn check_missing_const_stability(&self, def_id: LocalDefId, span: Span) {
         // if the const impl is derived using the `derive_const` attribute,
         // then it would be "stable" at least for the impl.
         // We gate usages of it using `feature(const_trait_impl)` anyways
@@ -587,12 +600,12 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> {
 
         let is_const = self.tcx.is_const_fn(def_id.to_def_id())
             || self.tcx.is_const_trait_impl(def_id.to_def_id());
-        let is_stable =
-            self.tcx.lookup_stability(def_id).is_some_and(|stability| stability.level.is_stable());
-        let missing_const_stability_attribute =
-            self.tcx.lookup_const_stability(def_id).is_none_or(|s| s.feature.is_none());
 
-        if is_const && is_stable && missing_const_stability_attribute {
+        // Reachable const fn must have a stability attribute.
+        if is_const
+            && self.effective_visibilities.is_reachable(def_id)
+            && self.tcx.lookup_const_stability(def_id).is_none()
+        {
             let descr = self.tcx.def_descr(def_id.to_def_id());
             self.tcx.dcx().emit_err(errors::MissingConstStabAttr { span, descr });
         }
@@ -620,7 +633,7 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
         }
 
         // Ensure stable `const fn` have a const stability attribute.
-        self.check_missing_or_wrong_const_stability(i.owner_id.def_id, i.span);
+        self.check_missing_const_stability(i.owner_id.def_id, i.span);
 
         intravisit::walk_item(self, i)
     }
@@ -634,7 +647,7 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> {
         let impl_def_id = self.tcx.hir().get_parent_item(ii.hir_id());
         if self.tcx.impl_trait_ref(impl_def_id).is_none() {
             self.check_missing_stability(ii.owner_id.def_id, ii.span);
-            self.check_missing_or_wrong_const_stability(ii.owner_id.def_id, ii.span);
+            self.check_missing_const_stability(ii.owner_id.def_id, ii.span);
         }
         intravisit::walk_impl_item(self, ii);
     }
@@ -765,23 +778,12 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
             // For implementations of traits, check the stability of each item
             // individually as it's possible to have a stable trait with unstable
             // items.
-            hir::ItemKind::Impl(hir::Impl {
-                constness,
-                of_trait: Some(ref t),
-                self_ty,
-                items,
-                ..
-            }) => {
+            hir::ItemKind::Impl(hir::Impl { of_trait: Some(ref t), self_ty, items, .. }) => {
                 let features = self.tcx.features();
                 if features.staged_api() {
                     let attrs = self.tcx.hir().attrs(item.hir_id());
                     let stab = attr::find_stability(self.tcx.sess, attrs, item.span);
-                    let const_stab = attr::find_const_stability(
-                        self.tcx.sess,
-                        attrs,
-                        item.span,
-                        matches!(constness, Constness::Const),
-                    );
+                    let const_stab = attr::find_const_stability(self.tcx.sess, attrs, item.span);
 
                     // If this impl block has an #[unstable] attribute, give an
                     // error if all involved types and traits are stable, because
diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs
index 8446235fb18..ce96d1d0a95 100644
--- a/src/librustdoc/html/render/mod.rs
+++ b/src/librustdoc/html/render/mod.rs
@@ -1010,9 +1010,7 @@ fn render_stability_since_raw_with_extra(
                 // don't display const unstable if entirely unstable
                 None
             } else {
-                let unstable = if let Some(n) = issue
-                    && let Some(feature) = feature
-                {
+                let unstable = if let Some(n) = issue {
                     format!(
                         "<a \
                         href=\"https://github.com/rust-lang/rust/issues/{n}\" \
diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
index 666ec8df930..971f8eeb1b3 100644
--- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
+++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs
@@ -393,12 +393,8 @@ fn is_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId, msrv: &Msrv) -> bool {
 
                 msrv.meets(const_stab_rust_version)
             } else {
-                // Unstable const fn, check if the feature is enabled. We need both the regular stability
-                // feature and (if set) the const stability feature to const-call this function.
-                let stab = tcx.lookup_stability(def_id);
-                let is_enabled = stab.is_some_and(|s| s.is_stable() || tcx.features().enabled(s.feature))
-                    && const_stab.feature.is_none_or(|f| tcx.features().enabled(f));
-                is_enabled && msrv.current().is_none()
+                // Unstable const fn, check if the feature is enabled.
+                tcx.features().enabled(const_stab.feature) && msrv.current().is_none()
             }
         })
 }
diff --git a/tests/ui/consts/const-unstable-intrinsic.rs b/tests/ui/consts/const-unstable-intrinsic.rs
index 56b552b6a3f..8b38067e46e 100644
--- a/tests/ui/consts/const-unstable-intrinsic.rs
+++ b/tests/ui/consts/const-unstable-intrinsic.rs
@@ -16,13 +16,13 @@ const fn const_main() {
     unsafe {
         unstable_intrinsic::size_of_val(&x);
         //~^ERROR: unstable library feature `unstable`
-        //~|ERROR: cannot be (indirectly) exposed to stable
+        //~|ERROR: not yet stable as a const intrinsic
         unstable_intrinsic::min_align_of_val(&x);
         //~^ERROR: unstable library feature `unstable`
         //~|ERROR: not yet stable as a const intrinsic
 
         size_of_val(&x);
-        //~^ERROR: cannot be (indirectly) exposed to stable
+        //~^ERROR: cannot use `#[feature(local)]`
         min_align_of_val(&x);
         //~^ERROR: cannot use `#[feature(local)]`
     }
@@ -59,6 +59,6 @@ mod fallback {
     #[rustc_intrinsic]
     const unsafe fn copy<T>(src: *const T, _dst: *mut T, _count: usize) {
         super::size_of_val(src);
-        //~^ ERROR cannot be (indirectly) exposed to stable
+        //~^ ERROR cannot use `#[feature(local)]`
     }
 }
diff --git a/tests/ui/consts/const-unstable-intrinsic.stderr b/tests/ui/consts/const-unstable-intrinsic.stderr
index 3e605f3d003..8b61b0904a9 100644
--- a/tests/ui/consts/const-unstable-intrinsic.stderr
+++ b/tests/ui/consts/const-unstable-intrinsic.stderr
@@ -18,13 +18,13 @@ LL |         unstable_intrinsic::min_align_of_val(&x);
    = help: add `#![feature(unstable)]` to the crate attributes to enable
    = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
 
-error: intrinsic `unstable_intrinsic::size_of_val` cannot be (indirectly) exposed to stable
+error: `size_of_val` is not yet stable as a const intrinsic
   --> $DIR/const-unstable-intrinsic.rs:17:9
    |
 LL |         unstable_intrinsic::size_of_val(&x);
    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_const_stable_intrinsic]` (but this requires team approval)
+   = help: add `#![feature(unstable)]` to the crate attributes to enable
 
 error: `min_align_of_val` is not yet stable as a const intrinsic
   --> $DIR/const-unstable-intrinsic.rs:20:9
@@ -34,13 +34,22 @@ LL |         unstable_intrinsic::min_align_of_val(&x);
    |
    = help: add `#![feature(unstable)]` to the crate attributes to enable
 
-error: intrinsic `size_of_val` cannot be (indirectly) exposed to stable
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local)]`
   --> $DIR/const-unstable-intrinsic.rs:24:9
    |
 LL |         size_of_val(&x);
    |         ^^^^^^^^^^^^^^^
    |
-   = help: mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_const_stable_intrinsic]` (but this requires team approval)
+help: if the function is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL + #[rustc_const_unstable(feature = "...", issue = "...")]
+LL | const fn const_main() {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL + #[rustc_allow_const_fn_unstable(local)]
+LL | const fn const_main() {
+   |
 
 error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local)]`
   --> $DIR/const-unstable-intrinsic.rs:26:9
@@ -67,13 +76,22 @@ LL |     unsafe { copy(src, dst, count) }
    |
    = help: mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_const_stable_intrinsic]` (but this requires team approval)
 
-error: intrinsic `size_of_val` cannot be (indirectly) exposed to stable
+error: const function that might be (indirectly) exposed to stable cannot use `#[feature(local)]`
   --> $DIR/const-unstable-intrinsic.rs:61:9
    |
 LL |         super::size_of_val(src);
    |         ^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_const_stable_intrinsic]` (but this requires team approval)
+help: if the function is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
+   |
+LL +     #[rustc_const_unstable(feature = "...", issue = "...")]
+LL |     const unsafe fn copy<T>(src: *const T, _dst: *mut T, _count: usize) {
+   |
+help: otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
+   |
+LL +     #[rustc_allow_const_fn_unstable(local)]
+LL |     const unsafe fn copy<T>(src: *const T, _dst: *mut T, _count: usize) {
+   |
 
 error: aborting due to 8 previous errors
 
diff --git a/tests/ui/consts/rustc-const-stability-require-const.rs b/tests/ui/consts/rustc-const-stability-require-const.rs
index 6cc3f0f0da1..ad27fcf6cb9 100644
--- a/tests/ui/consts/rustc-const-stability-require-const.rs
+++ b/tests/ui/consts/rustc-const-stability-require-const.rs
@@ -56,9 +56,3 @@ const fn barfoo_unmarked() {}
 #[rustc_const_stable(feature = "barfoo_const", since = "1.0.0")]
 pub const fn barfoo_unstable() {}
 //~^ ERROR can only be applied to functions that are declared `#[stable]`
-
-// `#[rustc_const_stable_indirect]` also requires a const fn
-#[rustc_const_stable_indirect]
-#[unstable(feature = "unstable", issue = "none")]
-pub fn not_a_const_fn() {}
-//~^ ERROR require the function or method to be `const`
diff --git a/tests/ui/consts/rustc-const-stability-require-const.stderr b/tests/ui/consts/rustc-const-stability-require-const.stderr
index d9a7d37cbcd..4b13826584d 100644
--- a/tests/ui/consts/rustc-const-stability-require-const.stderr
+++ b/tests/ui/consts/rustc-const-stability-require-const.stderr
@@ -86,17 +86,5 @@ LL | #[rustc_const_stable(feature = "barfoo_const", since = "1.0.0")]
 LL | pub const fn barfoo_unstable() {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const`
-  --> $DIR/rustc-const-stability-require-const.rs:63:1
-   |
-LL | pub fn not_a_const_fn() {}
-   | ^^^^^^^^^^^^^^^^^^^^^^^
-   |
-help: make the function or method const
-  --> $DIR/rustc-const-stability-require-const.rs:63:1
-   |
-LL | pub fn not_a_const_fn() {}
-   | ^^^^^^^^^^^^^^^^^^^^^^^
-
-error: aborting due to 9 previous errors
+error: aborting due to 8 previous errors
 
diff --git a/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr b/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr
index d599523c727..319056a9c88 100644
--- a/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr
+++ b/tests/ui/feature-gates/unstable-attribute-rejects-already-stable-features.stderr
@@ -1,31 +1,31 @@
 error: can't mark as unstable using an already stable feature
-  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1
+  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:6:1
    |
+LL | #[unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this feature is already stable
 LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this feature is already stable
 LL | const fn my_fun() {}
    | -------------------- the stability attribute annotates this item
    |
 help: consider removing the attribute
-  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1
+  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:6:1
    |
-LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: can't mark as unstable using an already stable feature
-  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:6:1
+  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1
    |
-LL | #[unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this feature is already stable
 LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this feature is already stable
 LL | const fn my_fun() {}
    | -------------------- the stability attribute annotates this item
    |
 help: consider removing the attribute
-  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:6:1
+  --> $DIR/unstable-attribute-rejects-already-stable-features.rs:7:1
    |
-LL | #[unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+LL | #[rustc_const_unstable(feature = "arbitrary_enum_discriminant", issue = "42")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: aborting due to 2 previous errors