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 | |
| 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')
19 files changed, 519 insertions, 209 deletions
diff --git a/compiler/rustc_attr/messages.ftl b/compiler/rustc_attr/messages.ftl index adabf18ca85..235ab7572c4 100644 --- a/compiler/rustc_attr/messages.ftl +++ b/compiler/rustc_attr/messages.ftl @@ -91,6 +91,9 @@ attr_non_ident_feature = attr_rustc_allowed_unstable_pairing = `rustc_allowed_through_unstable_modules` attribute must be paired with a `stable` attribute +attr_rustc_const_stable_indirect_pairing = + `const_stable_indirect` attribute does not make sense on `rustc_const_stable` function, its behavior is already implied + attr_rustc_promotable_pairing = `rustc_promotable` attribute must be paired with either a `rustc_const_unstable` or a `rustc_const_stable` attribute diff --git a/compiler/rustc_attr/src/builtin.rs b/compiler/rustc_attr/src/builtin.rs index 537d8e04531..6af75bc94bb 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,7 +92,11 @@ impl Stability { #[derive(HashStable_Generic)] pub struct ConstStability { pub level: StabilityLevel, - pub feature: Symbol, + /// 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>, + /// This is true iff the `const_stable_indirect` attribute is present. + pub const_stable_indirect: bool, /// whether the function has a `#[rustc_promotable]` attribute pub promotable: bool, } @@ -268,17 +272,23 @@ 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`. It will always +/// be false for intrinsics in an `extern` block! 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; 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_unstable => { if const_stab.is_some() { sess.dcx() @@ -287,8 +297,15 @@ pub fn find_const_stability( } if let Some((feature, level)) = parse_unstability(sess, attr) { - const_stab = - Some((ConstStability { level, feature, promotable: false }, attr.span)); + const_stab = Some(( + ConstStability { + level, + feature: Some(feature), + const_stable_indirect: false, + promotable: false, + }, + attr.span, + )); } } sym::rustc_const_stable => { @@ -298,15 +315,22 @@ pub fn find_const_stability( break; } if let Some((feature, level)) = parse_stability(sess, attr) { - const_stab = - Some((ConstStability { level, feature, promotable: false }, attr.span)); + const_stab = Some(( + ConstStability { + level, + feature: Some(feature), + const_stable_indirect: false, + promotable: false, + }, + attr.span, + )); } } _ => {} } } - // Merge the const-unstable info into the stability info + // Merge promotable and not_exposed_on_stable into stability info if promotable { match &mut const_stab { Some((stab, _)) => stab.promotable = promotable, @@ -317,6 +341,46 @@ pub fn find_const_stability( } } } + if const_stable_indirect.is_some() { + match &mut const_stab { + Some((stab, _)) => { + if stab.is_const_unstable() { + stab.const_stable_indirect = true; + } else { + _ = sess.dcx().emit_err(session_diagnostics::RustcConstStableIndirectPairing { + span: item_sp, + }) + } + } + _ => { + // We ignore the `#[rustc_const_stable_indirect]` here, it should be picked up by + // the `default_const_unstable` logic. + } + } + } + // 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 } diff --git a/compiler/rustc_attr/src/session_diagnostics.rs b/compiler/rustc_attr/src/session_diagnostics.rs index 626840aa6a3..9d08a9f5754 100644 --- a/compiler/rustc_attr/src/session_diagnostics.rs +++ b/compiler/rustc_attr/src/session_diagnostics.rs @@ -319,6 +319,13 @@ pub(crate) struct RustcPromotablePairing { } #[derive(Diagnostic)] +#[diag(attr_rustc_const_stable_indirect_pairing)] +pub(crate) struct RustcConstStableIndirectPairing { + #[primary_span] + pub span: Span, +} + +#[derive(Diagnostic)] #[diag(attr_rustc_allowed_unstable_pairing, code = E0789)] pub(crate) struct RustcAllowedUnstablePairing { #[primary_span] diff --git a/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch b/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch index 646928893e9..3c81b04c0ea 100644 --- a/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch +++ b/compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch @@ -38,7 +38,7 @@ diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index d9de37e..8293fce 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs -@@ -2996,42 +2996,6 @@ atomic_int! { +@@ -2996,44 +2996,6 @@ atomic_int! { 8, u64 AtomicU64 } @@ -52,7 +52,8 @@ index d9de37e..8293fce 100644 - unstable(feature = "integer_atomics", issue = "99069"), - unstable(feature = "integer_atomics", issue = "99069"), - unstable(feature = "integer_atomics", issue = "99069"), -- rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), +- rustc_const_unstable(feature = "integer_atomics", issue = "99069"), +- rustc_const_unstable(feature = "integer_atomics", issue = "99069"), - cfg_attr(not(test), rustc_diagnostic_item = "AtomicI128"), - "i128", - "#![feature(integer_atomics)]\n\n", @@ -70,7 +71,8 @@ index d9de37e..8293fce 100644 - unstable(feature = "integer_atomics", issue = "99069"), - unstable(feature = "integer_atomics", issue = "99069"), - unstable(feature = "integer_atomics", issue = "99069"), -- rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"), +- rustc_const_unstable(feature = "integer_atomics", issue = "99069"), +- rustc_const_unstable(feature = "integer_atomics", issue = "99069"), - cfg_attr(not(test), rustc_diagnostic_item = "AtomicU128"), - "u128", - "#![feature(integer_atomics)]\n\n", 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] diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index bed500c3032..7e4bc508e5c 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -866,7 +866,9 @@ impl SyntaxExtension { }) .unwrap_or_else(|| (None, helper_attrs)); let stability = attr::find_stability(sess, attrs, span); - let const_stability = attr::find_const_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 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_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 5921fbc0fd7..0069b07ad62 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -618,11 +618,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ "allow_internal_unstable side-steps feature gating and stability checks", ), gated!( - rustc_allow_const_fn_unstable, Normal, - template!(Word, List: "feat1, feat2, ..."), DuplicatesOk, EncodeCrossCrate::No, - "rustc_allow_const_fn_unstable side-steps feature gating and stability checks" - ), - gated!( allow_internal_unsafe, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, "allow_internal_unsafe side-steps the unsafe_code lint", ), @@ -838,6 +833,15 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ rustc_const_panic_str, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes, INTERNAL_UNSTABLE ), + rustc_attr!( + rustc_const_stable_indirect, Normal, + template!(Word), WarnFollowing, EncodeCrossCrate::No, IMPL_DETAIL, + ), + gated!( + rustc_allow_const_fn_unstable, Normal, + template!(Word, List: "feat1, feat2, ..."), DuplicatesOk, EncodeCrossCrate::No, + "rustc_allow_const_fn_unstable side-steps feature gating and stability checks" + ), // ========================================================================== // Internal attributes, Layout related: diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index eab106a4403..1c6a5e9011f 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -3128,7 +3128,7 @@ impl<'tcx> TyCtxt<'tcx> { Some(stability) if stability.is_const_unstable() => { // has a `rustc_const_unstable` attribute, check whether the user enabled the // corresponding feature gate. - self.features().enabled(stability.feature) + stability.feature.is_some_and(|f| self.features().enabled(f)) } // functions without const stability are either stable user written // const fn or the user is using feature gates and we thus don't diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 3f98236595b..f8ef423a9b0 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -99,6 +99,10 @@ passes_collapse_debuginfo = passes_confusables = attribute should be applied to an inherent method .label = not an inherent method +passes_const_stable_not_stable = + attribute `#[rustc_const_stable]` can only be applied to functions that are declared `#[stable]` + .label = attribute specified here + passes_continue_labeled_block = `continue` pointing to a labeled block .label = labeled blocks cannot be `continue`'d @@ -465,10 +469,10 @@ passes_may_dangle = `#[may_dangle]` must be applied to a lifetime or type generic parameter in `Drop` impl passes_maybe_string_interpolation = you might have meant to use string interpolation in this string literal + passes_missing_const_err = - attributes `#[rustc_const_unstable]` and `#[rustc_const_stable]` require the function or method to be `const` + attributes `#[rustc_const_unstable]`, `#[rustc_const_stable]` and `#[rustc_const_stable_indirect]` require the function or method to be `const` .help = make the function or method const - .label = attribute specified here passes_missing_const_stab_attr = {$descr} has missing const stability attribute diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 042e50d890e..b5f1eac1cba 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1574,12 +1574,20 @@ pub(crate) struct DuplicateFeatureErr { pub span: Span, pub feature: Symbol, } + #[derive(Diagnostic)] #[diag(passes_missing_const_err)] pub(crate) struct MissingConstErr { #[primary_span] #[help] pub fn_sig_span: Span, +} + +#[derive(Diagnostic)] +#[diag(passes_const_stable_not_stable)] +pub(crate) struct ConstStableNotStable { + #[primary_span] + pub fn_sig_span: Span, #[label] pub const_span: Span, } diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index a176b2bb1ad..a3165410854 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::{FieldDef, Item, ItemKind, TraitRef, Ty, TyKind, Variant}; +use rustc_hir::{Constness, 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; @@ -27,7 +27,6 @@ use rustc_session::lint; use rustc_session::lint::builtin::{INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED}; use rustc_span::Span; use rustc_span::symbol::{Symbol, sym}; -use rustc_target::spec::abi::Abi; use tracing::{debug, info}; use crate::errors; @@ -107,6 +106,7 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { def_id: LocalDefId, item_sp: Span, fn_sig: Option<&'tcx hir::FnSig<'tcx>>, + is_foreign_item: bool, kind: AnnotationKind, inherit_deprecation: InheritDeprecation, inherit_const_stability: InheritConstStability, @@ -163,30 +163,62 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { } let stab = attr::find_stability(self.tcx.sess, attrs, item_sp); - let const_stab = attr::find_const_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); - let mut const_span = None; - let const_stab = const_stab.map(|(const_stab, const_span_node)| { - self.index.const_stab_map.insert(def_id, const_stab); - const_span = Some(const_span_node); - const_stab - }); - - // If the current node is a function, has const stability attributes and if it doesn not have an intrinsic ABI, - // check if the function/method is const or the parent impl block is const - if let (Some(const_span), Some(fn_sig)) = (const_span, fn_sig) - && fn_sig.header.abi != Abi::RustIntrinsic + // 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() + // We have to exclude foreign items as they might be intrinsics. Sadly we can't check + // their ABI; `fn_sig.abi` is *not* correct for foreign functions. + && !is_foreign_item + && const_stab.is_some() && (!self.in_trait_impl || !self.tcx.is_const_fn_raw(def_id.to_def_id())) { + 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::MissingConstErr { fn_sig_span: fn_sig.span, const_span }); + .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(effects): 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 { @@ -247,6 +279,8 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { } } + // Stable *language* features shouldn't be used as unstable library features. + // (Not doing this for stable library features is checked by tidy.) if let Stability { level: Unstable { .. }, feature } = stab { if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() { self.tcx @@ -260,21 +294,13 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> { self.index.implications.insert(implied_by, feature); } - if let Some(ConstStability { level: Unstable { .. }, feature, .. }) = const_stab { - if ACCEPTED_LANG_FEATURES.iter().find(|f| f.name == feature).is_some() { - self.tcx.dcx().emit_err(errors::UnstableAttrForAlreadyStableFeature { - span: const_span.unwrap(), // If const_stab contains Some(..), same is true for const_span - item_sp, - }); - } - } if let Some(ConstStability { level: Unstable { implied_by: Some(implied_by), .. }, feature, .. }) = const_stab { - self.index.implications.insert(implied_by, feature); + self.index.implications.insert(implied_by, feature.unwrap()); } self.index.stab_map.insert(def_id, stab); @@ -372,6 +398,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { ctor_def_id, i.span, None, + /* is_foreign_item */ false, AnnotationKind::Required, InheritDeprecation::Yes, InheritConstStability::No, @@ -390,6 +417,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { i.owner_id.def_id, i.span, fn_sig, + /* is_foreign_item */ false, kind, InheritDeprecation::Yes, const_stab_inherit, @@ -409,6 +437,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { ti.owner_id.def_id, ti.span, fn_sig, + /* is_foreign_item */ false, AnnotationKind::Required, InheritDeprecation::Yes, InheritConstStability::No, @@ -432,6 +461,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { ii.owner_id.def_id, ii.span, fn_sig, + /* is_foreign_item */ false, kind, InheritDeprecation::Yes, InheritConstStability::No, @@ -447,6 +477,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { var.def_id, var.span, None, + /* is_foreign_item */ false, AnnotationKind::Required, InheritDeprecation::Yes, InheritConstStability::No, @@ -457,6 +488,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { ctor_def_id, var.span, None, + /* is_foreign_item */ false, AnnotationKind::Required, InheritDeprecation::Yes, InheritConstStability::No, @@ -475,6 +507,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { s.def_id, s.span, None, + /* is_foreign_item */ false, AnnotationKind::Required, InheritDeprecation::Yes, InheritConstStability::No, @@ -486,10 +519,15 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { } fn visit_foreign_item(&mut self, i: &'tcx hir::ForeignItem<'tcx>) { + let fn_sig = match &i.kind { + rustc_hir::ForeignItemKind::Fn(fn_sig, ..) => Some(fn_sig), + _ => None, + }; self.annotate( i.owner_id.def_id, i.span, - None, + fn_sig, + /* is_foreign_item */ true, AnnotationKind::Required, InheritDeprecation::Yes, InheritConstStability::No, @@ -512,6 +550,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Annotator<'a, 'tcx> { p.def_id, p.span, None, + /* is_foreign_item */ false, kind, InheritDeprecation::No, InheritConstStability::No, @@ -540,7 +579,9 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> { } } - fn check_missing_const_stability(&self, def_id: LocalDefId, span: Span) { + 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; } @@ -553,11 +594,12 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> { return; } - let is_const = self.tcx.is_const_fn(def_id.to_def_id()) + let is_const = self.tcx.is_const_fn_raw(def_id.to_def_id()) || self.tcx.is_const_trait_impl_raw(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(); + 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 { let descr = self.tcx.def_descr(def_id.to_def_id()); @@ -587,7 +629,7 @@ impl<'tcx> Visitor<'tcx> for MissingStabilityAnnotations<'tcx> { } // Ensure stable `const fn` have a const stability attribute. - self.check_missing_const_stability(i.owner_id.def_id, i.span); + self.check_missing_or_wrong_const_stability(i.owner_id.def_id, i.span); intravisit::walk_item(self, i) } @@ -601,7 +643,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_const_stability(ii.owner_id.def_id, ii.span); + self.check_missing_or_wrong_const_stability(ii.owner_id.def_id, ii.span); } intravisit::walk_impl_item(self, ii); } @@ -670,6 +712,7 @@ fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index { CRATE_DEF_ID, tcx.hir().span(CRATE_HIR_ID), None, + /* is_foreign_item */ false, AnnotationKind::Required, InheritDeprecation::Yes, InheritConstStability::No, @@ -732,12 +775,23 @@ 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 { of_trait: Some(ref t), self_ty, items, .. }) => { + hir::ItemKind::Impl(hir::Impl { + constness, + 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); + let const_stab = attr::find_const_stability( + self.tcx.sess, + attrs, + item.span, + matches!(constness, Constness::Const), + ); // If this impl block has an #[unstable] attribute, give an // error if all involved types and traits are stable, because diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index bf5f948fe91..134a1a1db30 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1660,6 +1660,7 @@ symbols! { rustc_confusables, rustc_const_panic_str, rustc_const_stable, + rustc_const_stable_indirect, rustc_const_unstable, rustc_conversion_suggestion, rustc_deallocator, |
