about summary refs log tree commit diff
path: root/compiler/rustc_const_eval/src/check_consts/mod.rs
diff options
context:
space:
mode:
authorRalf Jung <post@ralfj.de>2024-10-06 19:59:19 +0200
committerRalf Jung <post@ralfj.de>2024-10-25 20:31:40 +0200
commita0215d8e46aab41219dea0bb1cbaaf97dafe2f89 (patch)
treecb98c6fb900deceea7aa9f2d08455de383c45d02 /compiler/rustc_const_eval/src/check_consts/mod.rs
parent45089ec19ebebec88bace6ec237244ff0eaa7ad3 (diff)
downloadrust-a0215d8e46aab41219dea0bb1cbaaf97dafe2f89.tar.gz
rust-a0215d8e46aab41219dea0bb1cbaaf97dafe2f89.zip
Re-do recursive const stability checks
Fundamentally, we have *three* disjoint categories of functions:
1. const-stable functions
2. private/unstable functions that are meant to be callable from const-stable functions
3. functions that can make use of unstable const features

This PR implements the following system:
- `#[rustc_const_stable]` puts functions in the first category. It may only be applied to `#[stable]` functions.
- `#[rustc_const_unstable]` by default puts functions in the third category. The new attribute `#[rustc_const_stable_indirect]` can be added to such a function to move it into the second category.
- `const fn` without a const stability marker are in the second category if they are still unstable. They automatically inherit the feature gate for regular calls, it can now also be used for const-calls.

Also, several holes in recursive const stability checking are being closed.
There's still one potential hole that is hard to avoid, which is when MIR
building automatically inserts calls to a particular function in stable
functions -- which happens in the panic machinery. Those need to *not* be
`rustc_const_unstable` (or manually get a `rustc_const_stable_indirect`) to be
sure they follow recursive const stability. But that's a fairly rare and special
case so IMO it's fine.

The net effect of this is that a `#[unstable]` or unmarked function can be
constified simply by marking it as `const fn`, and it will then be
const-callable from stable `const fn` and subject to recursive const stability
requirements. If it is publicly reachable (which implies it cannot be unmarked),
it will be const-unstable under the same feature gate. Only if the function ever
becomes `#[stable]` does it need a `#[rustc_const_unstable]` or
`#[rustc_const_stable]` marker to decide if this should also imply
const-stability.

Adding `#[rustc_const_unstable]` is only needed for (a) functions that need to
use unstable const lang features (including intrinsics), or (b) `#[stable]`
functions that are not yet intended to be const-stable. Adding
`#[rustc_const_stable]` is only needed for functions that are actually meant to
be directly callable from stable const code. `#[rustc_const_stable_indirect]` is
used to mark intrinsics as const-callable and for `#[rustc_const_unstable]`
functions that are actually called from other, exposed-on-stable `const fn`. No
other attributes are required.
Diffstat (limited to 'compiler/rustc_const_eval/src/check_consts/mod.rs')
-rw-r--r--compiler/rustc_const_eval/src/check_consts/mod.rs60
1 files changed, 25 insertions, 35 deletions
diff --git a/compiler/rustc_const_eval/src/check_consts/mod.rs b/compiler/rustc_const_eval/src/check_consts/mod.rs
index 3720418d4f0..146b559f2d7 100644
--- a/compiler/rustc_const_eval/src/check_consts/mod.rs
+++ b/compiler/rustc_const_eval/src/check_consts/mod.rs
@@ -59,10 +59,12 @@ impl<'mir, 'tcx> ConstCx<'mir, 'tcx> {
         self.const_kind.expect("`const_kind` must not be called on a non-const fn")
     }
 
-    pub fn is_const_stable_const_fn(&self) -> bool {
+    pub fn enforce_recursive_const_stability(&self) -> bool {
+        // We can skip this if `staged_api` is not enabled, since in such crates
+        // `lookup_const_stability` will always be `None`.
         self.const_kind == Some(hir::ConstContext::ConstFn)
             && self.tcx.features().staged_api()
-            && is_const_stable_const_fn(self.tcx, self.def_id().to_def_id())
+            && is_safe_to_expose_on_stable_const_fn(self.tcx, self.def_id().to_def_id())
     }
 
     fn is_async(&self) -> bool {
@@ -90,50 +92,38 @@ pub fn rustc_allow_const_fn_unstable(
     attr::rustc_allow_const_fn_unstable(tcx.sess, attrs).any(|name| name == feature_gate)
 }
 
-/// Returns `true` if the given `const fn` is "const-stable".
+/// Returns `true` if the given `const fn` is "safe to expose on stable".
 ///
 /// Panics if the given `DefId` does not refer to a `const fn`.
 ///
-/// Const-stability is only relevant for `const fn` within a `staged_api` crate. Only "const-stable"
-/// functions can be called in a const-context by users of the stable compiler. "const-stable"
-/// functions are subject to more stringent restrictions than "const-unstable" functions: They
-/// cannot use unstable features and can only call other "const-stable" functions.
-pub fn is_const_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
-    // A default body in a `#[const_trait]` is not const-stable because const
-    // trait fns currently cannot be const-stable. We shouldn't
-    // restrict default bodies to only call const-stable functions.
+/// This is relevant within a `staged_api` crate. Unlike with normal features, the use of unstable
+/// const features *recursively* taints the functions that use them. This is to avoid accidentally
+/// exposing e.g. the implementation of an unstable const intrinsic on stable. So we partition the
+/// world into two functions: those that are safe to expose on stable (and hence may not use
+/// unstable features, not even recursively), and those that are not.
+pub fn is_safe_to_expose_on_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
+    // A default body in a `#[const_trait]` is not const-stable because const trait fns currently
+    // cannot be const-stable. These functions can't be called from anything stable, so we shouldn't
+    // restrict them to only call const-stable functions.
     if tcx.is_const_default_method(def_id) {
+        // FIXME(const_trait_impl): we have to eventually allow some of these if these things can ever be stable.
+        // They should probably behave like regular `const fn` for that...
         return false;
     }
 
     // Const-stability is only relevant for `const fn`.
     assert!(tcx.is_const_fn_raw(def_id));
 
-    // A function is only const-stable if it has `#[rustc_const_stable]` or it the trait it belongs
-    // to is const-stable.
     match tcx.lookup_const_stability(def_id) {
-        Some(stab) => stab.is_const_stable(),
-        None if is_parent_const_stable_trait(tcx, def_id) => {
-            // Remove this when `#![feature(const_trait_impl)]` is stabilized,
-            // returning `true` unconditionally.
-            tcx.dcx().span_delayed_bug(
-                tcx.def_span(def_id),
-                "trait implementations cannot be const stable yet",
-            );
-            true
+        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
+        }
+        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
         }
-        None => false, // By default, items are not const stable.
-    }
-}
-
-fn is_parent_const_stable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
-    let local_def_id = def_id.expect_local();
-    let hir_id = tcx.local_def_id_to_hir_id(local_def_id);
-
-    let parent_owner_id = tcx.parent_hir_id(hir_id).owner;
-    if !tcx.is_const_trait_impl_raw(parent_owner_id.to_def_id()) {
-        return false;
     }
-
-    tcx.lookup_const_stability(parent_owner_id).is_some_and(|stab| stab.is_const_stable())
 }