diff options
| author | Ralf Jung <post@ralfj.de> | 2024-10-06 19:59:19 +0200 |
|---|---|---|
| committer | Ralf Jung <post@ralfj.de> | 2024-10-25 20:31:40 +0200 |
| commit | a0215d8e46aab41219dea0bb1cbaaf97dafe2f89 (patch) | |
| tree | cb98c6fb900deceea7aa9f2d08455de383c45d02 /compiler/rustc_const_eval/src/check_consts/mod.rs | |
| parent | 45089ec19ebebec88bace6ec237244ff0eaa7ad3 (diff) | |
| download | rust-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.rs | 60 |
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()) } |
