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 | |
| 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')
| -rw-r--r-- | compiler/rustc_const_eval/messages.ftl | 27 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/check_consts/check.rs | 222 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/check_consts/mod.rs | 60 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/check_consts/ops.rs | 98 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/const_eval/fn_queries.rs | 19 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/const_eval/machine.rs | 2 | ||||
| -rw-r--r-- | compiler/rustc_const_eval/src/errors.rs | 47 |
8 files changed, 319 insertions, 158 deletions
diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 24dbe688f36..3e4f83c8242 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -41,8 +41,6 @@ const_eval_const_context = {$kind -> *[other] {""} } -const_eval_const_stable = const-stable functions can only call other const-stable functions - const_eval_copy_nonoverlapping_overlapping = `copy_nonoverlapping` called on overlapping ranges @@ -259,6 +257,9 @@ const_eval_non_const_fn_call = const_eval_non_const_impl = impl defined here, but it is not `const` +const_eval_non_const_intrinsic = + cannot call non-const intrinsic `{$name}` in {const_eval_const_context}s + const_eval_not_enough_caller_args = calling a function with fewer arguments than it requires @@ -397,17 +398,29 @@ const_eval_uninhabited_enum_variant_read = read discriminant of an uninhabited enum variant const_eval_uninhabited_enum_variant_written = writing discriminant of an uninhabited enum variant + +const_eval_unmarked_const_fn_exposed = `{$def_path}` cannot be (indirectly) exposed to stable + .help = either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]` +const_eval_unmarked_intrinsic_exposed = intrinsic `{$def_path}` cannot be (indirectly) exposed to stable + .help = mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_const_stable_indirect]` (but this requires team approval) + const_eval_unreachable = entering unreachable code const_eval_unreachable_unwind = unwinding past a stack frame that does not allow unwinding const_eval_unsized_local = unsized locals are not supported const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn - -const_eval_unstable_in_stable = - const-stable function cannot use `#[feature({$gate})]` - .unstable_sugg = if the function is not (yet) meant to be stable, make this function unstably const - .bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (but requires team approval) +const_eval_unstable_in_stable_exposed = + const function that might be (indirectly) exposed to stable cannot use `#[feature({$gate})]` + .is_function_call = mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features + .unstable_sugg = if the {$is_function_call2 -> + [true] caller + *[false] function + } is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do) + .bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval) + +const_eval_unstable_intrinsic = `{$name}` is not yet stable as a const intrinsic + .help = add `#![feature({$feature})]` to the crate attributes to enable const_eval_unterminated_c_string = reading a null-terminated string starting at {$pointer} with no null found before end of allocation diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 288798bf0c2..ef01fe88979 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -5,6 +5,7 @@ use std::borrow::Cow; use std::mem; use std::ops::Deref; +use rustc_attr::{ConstStability, StabilityLevel}; use rustc_errors::{Diag, ErrorGuaranteed}; use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, LangItem}; @@ -28,8 +29,8 @@ use super::ops::{self, NonConstOp, Status}; use super::qualifs::{self, HasMutInterior, NeedsDrop, NeedsNonConstDrop}; use super::resolver::FlowSensitiveAnalysis; use super::{ConstCx, Qualif}; -use crate::const_eval::is_unstable_const_fn; -use crate::errors::UnstableInStable; +use crate::check_consts::is_safe_to_expose_on_stable_const_fn; +use crate::errors; type QualifResults<'mir, 'tcx, Q> = rustc_mir_dataflow::ResultsCursor<'mir, 'tcx, FlowSensitiveAnalysis<'mir, 'mir, 'tcx, Q>>; @@ -274,19 +275,22 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { /// context. pub fn check_op_spanned<O: NonConstOp<'tcx>>(&mut self, op: O, span: Span) { let gate = match op.status_in_item(self.ccx) { - Status::Allowed => return, - - Status::Unstable(gate) if self.tcx.features().enabled(gate) => { - let unstable_in_stable = self.ccx.is_const_stable_const_fn() - && !super::rustc_allow_const_fn_unstable(self.tcx, self.def_id(), gate); - if unstable_in_stable { - emit_unstable_in_stable_error(self.ccx, span, gate); + Status::Unstable { gate, safe_to_expose_on_stable, is_function_call } + if self.tcx.features().enabled(gate) => + { + // Generally this is allowed since the feature gate is enabled -- except + // if this function wants to be safe-to-expose-on-stable. + if !safe_to_expose_on_stable + && self.enforce_recursive_const_stability() + && !super::rustc_allow_const_fn_unstable(self.tcx, self.def_id(), gate) + { + emit_unstable_in_stable_exposed_error(self.ccx, span, gate, is_function_call); } return; } - Status::Unstable(gate) => Some(gate), + Status::Unstable { gate, .. } => Some(gate), Status::Forbidden => None, }; @@ -304,7 +308,13 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> { self.error_emitted = Some(reported); } - ops::DiagImportance::Secondary => self.secondary_errors.push(err), + ops::DiagImportance::Secondary => { + self.secondary_errors.push(err); + self.tcx.dcx().span_delayed_bug( + span, + "compilation must fail when there is a secondary const checker error", + ); + } } } @@ -569,6 +579,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { ty::FnPtr(..) => { self.check_op(ops::FnCallIndirect); + // We can get here without an error in miri-unleashed mode... might as well + // skip the rest of the checks as well then. return; } _ => { @@ -612,6 +624,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // checks. // FIXME(effects) we might consider moving const stability checks to typeck as well. if tcx.features().effects() { + // This skips the check below that ensures we only call `const fn`. is_trait = true; if let Ok(Some(instance)) = @@ -637,6 +650,8 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { sym::const_trait_impl }), }); + // If we allowed this, we're in miri-unleashed mode, so we might + // as well skip the remaining checks. return; } } @@ -650,28 +665,72 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { // const-eval of the `begin_panic` fn assumes the argument is `&str` if tcx.is_lang_item(callee, LangItem::BeginPanic) { match args[0].node.ty(&self.ccx.body.local_decls, tcx).kind() { - ty::Ref(_, ty, _) if ty.is_str() => return, + ty::Ref(_, ty, _) if ty.is_str() => {} _ => self.check_op(ops::PanicNonStr), } + // Allow this call, skip all the checks below. + return; } // const-eval of `#[rustc_const_panic_str]` functions assumes the argument is `&&str` if tcx.has_attr(callee, sym::rustc_const_panic_str) { match args[0].node.ty(&self.ccx.body.local_decls, tcx).kind() { ty::Ref(_, ty, _) if matches!(ty.kind(), ty::Ref(_, ty, _) if ty.is_str()) => - { - return; + {} + _ => { + self.check_op(ops::PanicNonStr); } - _ => self.check_op(ops::PanicNonStr), } + // Allow this call, skip all the checks below. + return; } // This can be called on stable via the `vec!` macro. if tcx.is_lang_item(callee, LangItem::ExchangeMalloc) { self.check_op(ops::HeapAllocation); + // Allow this call, skip all the checks below. return; } + // Intrinsics are language primitives, not regular calls, so treat them separately. + if let Some(intrinsic) = tcx.intrinsic(callee) { + match tcx.lookup_const_stability(callee) { + None => { + // Non-const intrinsic. + self.check_op(ops::IntrinsicNonConst { name: intrinsic.name }); + } + Some(ConstStability { feature: None, const_stable_indirect, .. }) => { + // Intrinsic does not need a separate feature gate (we rely on the + // regular stability checker). However, we have to worry about recursive + // const stability. + if !const_stable_indirect && self.enforce_recursive_const_stability() { + self.dcx().emit_err(errors::UnmarkedIntrinsicExposed { + span: self.span, + def_path: self.tcx.def_path_str(callee), + }); + } + } + Some(ConstStability { + feature: Some(feature), + level: StabilityLevel::Unstable { .. }, + const_stable_indirect, + .. + }) => { + self.check_op(ops::IntrinsicUnstable { + name: intrinsic.name, + feature, + const_stable_indirect, + }); + } + Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => { + // All good. + } + } + // This completes the checks for intrinsics. + return; + } + + // Trait functions are not `const fn` so we have to skip them here. if !tcx.is_const_fn_raw(callee) && !is_trait { self.check_op(ops::FnCallNonConst { caller, @@ -681,66 +740,68 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { call_source, feature: None, }); + // If we allowed this, we're in miri-unleashed mode, so we might + // as well skip the remaining checks. return; } - // If the `const fn` we are trying to call is not const-stable, ensure that we have - // the proper feature gate enabled. - if let Some((gate, implied_by)) = is_unstable_const_fn(tcx, callee) { - trace!(?gate, "calling unstable const fn"); - if self.span.allows_unstable(gate) { - return; + // Finally, stability for regular function calls -- this is the big one. + match tcx.lookup_const_stability(callee) { + Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => { + // All good. } - if let Some(implied_by_gate) = implied_by - && self.span.allows_unstable(implied_by_gate) - { - return; - } - - // Calling an unstable function *always* requires that the corresponding gate - // (or implied gate) be enabled, even if the function has - // `#[rustc_allow_const_fn_unstable(the_gate)]`. - let gate_enabled = |gate| tcx.features().enabled(gate); - let feature_gate_enabled = gate_enabled(gate); - let implied_gate_enabled = implied_by.is_some_and(gate_enabled); - if !feature_gate_enabled && !implied_gate_enabled { - self.check_op(ops::FnCallUnstable(callee, Some(gate))); - return; - } - - // If this crate is not using stability attributes, or the caller is not claiming to be a - // stable `const fn`, that is all that is required. - if !self.ccx.is_const_stable_const_fn() { - trace!("crate not using stability attributes or caller not stably const"); - return; - } - - // Otherwise, we are something const-stable calling a const-unstable fn. - if super::rustc_allow_const_fn_unstable(tcx, caller, gate) { - trace!("rustc_allow_const_fn_unstable gate enabled"); - return; + None | Some(ConstStability { feature: 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. + if self.enforce_recursive_const_stability() + && !is_safe_to_expose_on_stable_const_fn(tcx, callee) + { + self.dcx().emit_err(errors::UnmarkedConstFnExposed { + span: self.span, + def_path: self.tcx.def_path_str(callee), + }); + } } + Some(ConstStability { + feature: Some(feature), + level: StabilityLevel::Unstable { implied_by: implied_feature, .. }, + .. + }) => { + // An unstable const fn with a feature gate. + let callee_safe_to_expose_on_stable = + is_safe_to_expose_on_stable_const_fn(tcx, callee); + + // We only honor `span.allows_unstable` aka `#[allow_internal_unstable]` if + // the callee is safe to expose, to avoid bypassing recursive stability. + if (self.span.allows_unstable(feature) + || implied_feature.is_some_and(|f| self.span.allows_unstable(f))) + && callee_safe_to_expose_on_stable + { + return; + } - self.check_op(ops::FnCallUnstable(callee, Some(gate))); - return; - } - - // FIXME(ecstaticmorse); For compatibility, we consider `unstable` callees that - // have no `rustc_const_stable` attributes to be const-unstable as well. This - // should be fixed later. - let callee_is_unstable_unmarked = tcx.lookup_const_stability(callee).is_none() - && tcx.lookup_stability(callee).is_some_and(|s| s.is_unstable()); - if callee_is_unstable_unmarked { - trace!("callee_is_unstable_unmarked"); - // We do not use `const` modifiers for intrinsic "functions", as intrinsics are - // `extern` functions, and these have no way to get marked `const`. So instead we - // use `rustc_const_(un)stable` attributes to mean that the intrinsic is `const` - if self.ccx.is_const_stable_const_fn() || tcx.intrinsic(callee).is_some() { - self.check_op(ops::FnCallUnstable(callee, None)); - return; + // We can't use `check_op` to check whether the feature is enabled because + // the logic is a bit different than elsewhere: local functions don't need + // the feature gate, and there might be an "implied" gate that also suffices + // to allow this. + let feature_enabled = callee.is_local() + || tcx.features().enabled(feature) + || implied_feature.is_some_and(|f| tcx.features().enabled(f)); + // We do *not* honor this if we are in the "danger zone": we have to enforce + // recursive const-stability and the callee is not safe-to-expose. In that + // case we need `check_op` to do the check. + let danger_zone = !callee_safe_to_expose_on_stable + && self.enforce_recursive_const_stability(); + if danger_zone || !feature_enabled { + self.check_op(ops::FnCallUnstable { + def_id: callee, + feature, + safe_to_expose_on_stable: callee_safe_to_expose_on_stable, + }); + } } } - trace!("permitting call"); } // Forbid all `Drop` terminators unless the place being dropped is a local with no @@ -785,11 +846,13 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { TerminatorKind::InlineAsm { .. } => self.check_op(ops::InlineAsm), - TerminatorKind::Yield { .. } => self.check_op(ops::Coroutine( - self.tcx - .coroutine_kind(self.body.source.def_id()) - .expect("Only expected to have a yield in a coroutine"), - )), + TerminatorKind::Yield { .. } => { + self.check_op(ops::Coroutine( + self.tcx + .coroutine_kind(self.body.source.def_id()) + .expect("Only expected to have a yield in a coroutine"), + )); + } TerminatorKind::CoroutineDrop => { span_bug!( @@ -819,8 +882,19 @@ fn is_int_bool_float_or_char(ty: Ty<'_>) -> bool { ty.is_bool() || ty.is_integral() || ty.is_char() || ty.is_floating_point() } -fn emit_unstable_in_stable_error(ccx: &ConstCx<'_, '_>, span: Span, gate: Symbol) { +fn emit_unstable_in_stable_exposed_error( + ccx: &ConstCx<'_, '_>, + span: Span, + gate: Symbol, + is_function_call: bool, +) -> ErrorGuaranteed { let attr_span = ccx.tcx.def_span(ccx.def_id()).shrink_to_lo(); - ccx.dcx().emit_err(UnstableInStable { gate: gate.to_string(), span, attr_span }); + ccx.dcx().emit_err(errors::UnstableInStableExposed { + gate: gate.to_string(), + span, + attr_span, + is_function_call, + is_function_call2: is_function_call, + }) } 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()) } diff --git a/compiler/rustc_const_eval/src/check_consts/ops.rs b/compiler/rustc_const_eval/src/check_consts/ops.rs index 5c4a899f28a..0f151dcdb03 100644 --- a/compiler/rustc_const_eval/src/check_consts/ops.rs +++ b/compiler/rustc_const_eval/src/check_consts/ops.rs @@ -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 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) }); + 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 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 } diff --git a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs index d04d7b273f0..0173a528c22 100644 --- a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs +++ b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs @@ -15,7 +15,7 @@ use crate::check_consts::rustc_allow_const_fn_unstable; /// elaboration. pub fn checking_enabled(ccx: &ConstCx<'_, '_>) -> bool { // Const-stable functions must always use the stable live drop checker... - if ccx.is_const_stable_const_fn() { + if ccx.enforce_recursive_const_stability() { // ...except if they have the feature flag set via `rustc_allow_const_fn_unstable`. return rustc_allow_const_fn_unstable( ccx.tcx, diff --git a/compiler/rustc_const_eval/src/const_eval/fn_queries.rs b/compiler/rustc_const_eval/src/const_eval/fn_queries.rs index ca0993f0580..037fdcbcf9b 100644 --- a/compiler/rustc_const_eval/src/const_eval/fn_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/fn_queries.rs @@ -1,25 +1,8 @@ +use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_middle::query::Providers; use rustc_middle::ty::TyCtxt; -use rustc_span::symbol::Symbol; -use {rustc_attr as attr, rustc_hir as hir}; - -/// Whether the `def_id` is an unstable const fn and what feature gate(s) are necessary to enable -/// it. -pub fn is_unstable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> Option<(Symbol, Option<Symbol>)> { - if tcx.is_const_fn_raw(def_id) { - let const_stab = tcx.lookup_const_stability(def_id)?; - match const_stab.level { - attr::StabilityLevel::Unstable { implied_by, .. } => { - Some((const_stab.feature, implied_by)) - } - attr::StabilityLevel::Stable { .. } => None, - } - } else { - None - } -} pub fn is_parent_const_impl_raw(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { let parent_id = tcx.local_parent(def_id); diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 2db43a0f787..7793de9c933 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -219,7 +219,7 @@ impl<'tcx> CompileTimeInterpCx<'tcx> { } /// "Intercept" a function call, because we have something special to do for it. - /// All `#[rustc_do_not_const_check]` functions should be hooked here. + /// All `#[rustc_do_not_const_check]` functions MUST be hooked here. /// If this returns `Some` function, which may be `instance` or a different function with /// compatible arguments, then evaluation should continue with that function. /// If this returns `None`, the function call has been handled and the function has returned. diff --git a/compiler/rustc_const_eval/src/errors.rs b/compiler/rustc_const_eval/src/errors.rs index 211668cf055..38b87b72634 100644 --- a/compiler/rustc_const_eval/src/errors.rs +++ b/compiler/rustc_const_eval/src/errors.rs @@ -14,7 +14,7 @@ use rustc_middle::mir::interpret::{ UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo, }; use rustc_middle::ty::{self, Mutability, Ty}; -use rustc_span::Span; +use rustc_span::{Span, Symbol}; use rustc_target::abi::WrappingRange; use rustc_target::abi::call::AdjustForForeignAbiError; @@ -44,11 +44,15 @@ pub(crate) struct MutablePtrInFinal { } #[derive(Diagnostic)] -#[diag(const_eval_unstable_in_stable)] -pub(crate) struct UnstableInStable { +#[diag(const_eval_unstable_in_stable_exposed)] +pub(crate) struct UnstableInStableExposed { pub gate: String, #[primary_span] pub span: Span, + #[help(const_eval_is_function_call)] + pub is_function_call: bool, + /// Need to duplicate the field so that fluent also provides it as a variable... + pub is_function_call2: bool, #[suggestion( const_eval_unstable_sugg, code = "#[rustc_const_unstable(feature = \"...\", issue = \"...\")]\n", @@ -118,6 +122,34 @@ pub(crate) struct UnstableConstFn { } #[derive(Diagnostic)] +#[diag(const_eval_unstable_intrinsic)] +#[help] +pub(crate) struct UnstableIntrinsic { + #[primary_span] + pub span: Span, + pub name: Symbol, + pub feature: Symbol, +} + +#[derive(Diagnostic)] +#[diag(const_eval_unmarked_const_fn_exposed)] +#[help] +pub(crate) struct UnmarkedConstFnExposed { + #[primary_span] + pub span: Span, + pub def_path: String, +} + +#[derive(Diagnostic)] +#[diag(const_eval_unmarked_intrinsic_exposed)] +#[help] +pub(crate) struct UnmarkedIntrinsicExposed { + #[primary_span] + pub span: Span, + pub def_path: String, +} + +#[derive(Diagnostic)] #[diag(const_eval_mutable_ref_escaping, code = E0764)] pub(crate) struct MutableRefEscaping { #[primary_span] @@ -154,6 +186,15 @@ pub(crate) struct NonConstFnCall { } #[derive(Diagnostic)] +#[diag(const_eval_non_const_intrinsic)] +pub(crate) struct NonConstIntrinsic { + #[primary_span] + pub span: Span, + pub name: Symbol, + pub kind: ConstContext, +} + +#[derive(Diagnostic)] #[diag(const_eval_unallowed_op_in_const_context)] pub(crate) struct UnallowedOpInConstContext { #[primary_span] |
