1
Fork 0

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.
This commit is contained in:
Ralf Jung 2024-10-06 19:59:19 +02:00
parent 45089ec19e
commit a0215d8e46
102 changed files with 1520 additions and 663 deletions

View file

@ -26,8 +26,16 @@ use crate::{errors, fluent_generated};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Status {
Allowed,
Unstable(Symbol),
Unstable {
/// The feature that must be enabled to use this operation.
gate: Symbol,
/// Whether it is allowed to use this operation from stable `const fn`.
/// This will usually be `false`.
safe_to_expose_on_stable: bool,
/// We indicate whether this is a function call, since we can use targeted
/// diagnostics for "callee is not safe to expose om stable".
is_function_call: bool,
},
Forbidden,
}
@ -40,9 +48,9 @@ pub enum DiagImportance {
Secondary,
}
/// An operation that is not *always* allowed in a const context.
/// An operation that is *not allowed* in a const context.
pub trait NonConstOp<'tcx>: std::fmt::Debug {
/// Returns an enum indicating whether this operation is allowed within the given item.
/// Returns an enum indicating whether this operation can be enabled with a feature gate.
fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
Status::Forbidden
}
@ -298,30 +306,78 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
///
/// Contains the name of the feature that would allow the use of this function.
#[derive(Debug)]
pub(crate) struct FnCallUnstable(pub DefId, pub Option<Symbol>);
pub(crate) struct FnCallUnstable {
pub def_id: DefId,
pub feature: Symbol,
pub safe_to_expose_on_stable: bool,
}
impl<'tcx> NonConstOp<'tcx> for FnCallUnstable {
fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
Status::Unstable {
gate: self.feature,
safe_to_expose_on_stable: self.safe_to_expose_on_stable,
is_function_call: true,
}
}
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
let FnCallUnstable(def_id, feature) = *self;
let mut err = ccx
.dcx()
.create_err(errors::UnstableConstFn { span, def_path: ccx.tcx.def_path_str(def_id) });
let mut err = ccx.dcx().create_err(errors::UnstableConstFn {
span,
def_path: ccx.tcx.def_path_str(self.def_id),
});
// FIXME: make this translatable
#[allow(rustc::untranslatable_diagnostic)]
if ccx.is_const_stable_const_fn() {
err.help(fluent_generated::const_eval_const_stable);
} else if ccx.tcx.sess.is_nightly_build() {
if let Some(feature) = feature {
err.help(format!("add `#![feature({feature})]` to the crate attributes to enable"));
}
}
err.help(format!("add `#![feature({})]` to the crate attributes to enable", self.feature));
err
}
}
/// A call to an intrinsic that is just not const-callable at all.
#[derive(Debug)]
pub(crate) struct IntrinsicNonConst {
pub name: Symbol,
}
impl<'tcx> NonConstOp<'tcx> for IntrinsicNonConst {
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.dcx().create_err(errors::NonConstIntrinsic {
span,
name: self.name,
kind: ccx.const_kind(),
})
}
}
/// A call to an intrinsic that is just not const-callable at all.
#[derive(Debug)]
pub(crate) struct IntrinsicUnstable {
pub name: Symbol,
pub feature: Symbol,
pub const_stable_indirect: bool,
}
impl<'tcx> NonConstOp<'tcx> for IntrinsicUnstable {
fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
Status::Unstable {
gate: self.feature,
safe_to_expose_on_stable: self.const_stable_indirect,
// We do *not* want to suggest to mark the intrinsic as `const_stable_indirect`,
// that's not a trivial change!
is_function_call: false,
}
}
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
ccx.dcx().create_err(errors::UnstableIntrinsic {
span,
name: self.name,
feature: self.feature,
})
}
}
#[derive(Debug)]
pub(crate) struct Coroutine(pub hir::CoroutineKind);
impl<'tcx> NonConstOp<'tcx> for Coroutine {
@ -331,7 +387,11 @@ impl<'tcx> NonConstOp<'tcx> for Coroutine {
hir::CoroutineSource::Block,
) = self.0
{
Status::Unstable(sym::const_async_blocks)
Status::Unstable {
gate: sym::const_async_blocks,
safe_to_expose_on_stable: false,
is_function_call: false,
}
} else {
Status::Forbidden
}