Auto merge of #131349 - RalfJung:const-stability-checks, r=compiler-errors
Const stability checks v2 The const stability system has served us well ever since `const fn` were first stabilized. It's main feature is that it enforces *recursive* validity -- a stable const fn cannot internally make use of unstable const features without an explicit marker in the form of `#[rustc_allow_const_fn_unstable]`. This is done to make sure that we don't accidentally expose unstable const features on stable in a way that would be hard to take back. As part of this, it is enforced that a `#[rustc_const_stable]` can only call `#[rustc_const_stable]` functions. However, some problems have been coming up with increased usage: - It is baffling that we have to mark private or even unstable functions as `#[rustc_const_stable]` when they are used as helpers in regular stable `const fn`, and often people will rather add `#[rustc_allow_const_fn_unstable]` instead which was not our intention. - The system has several gaping holes: a private `const fn` without stability attributes whose inherited stability (walking up parent modules) is `#[stable]` is allowed to call *arbitrary* unstable const operations, but can itself be called from stable `const fn`. Similarly, `#[allow_internal_unstable]` on a macro completely bypasses the recursive nature of the check. Fundamentally, the problem is that we have *three* disjoint categories of functions, and not enough attributes to distinguish them: 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 Functions in the first two categories cannot use unstable const features and they can only call functions from the first two categories. 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, all the holes mentioned above have been 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 be manually marked `#[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. Also see the updated dev-guide at https://github.com/rust-lang/rustc-dev-guide/pull/2098. I think in the future we may want to tweak this further, so that in the hopefully common case where a public function's const-stability just exactly mirrors its regular stability, we never have to add any attribute. But right now, once the function is stable this requires `#[rustc_const_stable]`. ### Open question There is one point I could see we might want to do differently, and that is putting `#[rustc_const_unstable]` functions (but not intrinsics) in category 2 by default, and requiring an extra attribute for `#[rustc_const_not_exposed_on_stable]` or so. This would require a bunch of extra annotations, but would have the advantage that turning a `#[rustc_const_unstable]` into `#[rustc_const_stable]` will never change the way the function is const-checked. Currently, we often discover in the const stabilization PR that a function needs some other unstable const things, and then we rush to quickly deal with that. In this alternative universe, we'd work towards getting rid of the `rustc_const_not_exposed_on_stable` before stabilization, and once that is done stabilization becomes a trivial matter. `#[rustc_const_stable_indirect]` would then only be used for intrinsics. I think I like this idea, but might want to do it in a follow-up PR, as it will need a whole bunch of annotations in the standard library. Also, we probably want to convert all const intrinsics to the "new" form (`#[rustc_intrinsic]` instead of an `extern` block) before doing this to avoid having to deal with two different ways of declaring intrinsics. Cc `@rust-lang/wg-const-eval` `@rust-lang/libs-api` Part of https://github.com/rust-lang/rust/issues/129815 (but not finished since this is not yet sufficient to safely let us expose `const fn` from hashbrown) Fixes https://github.com/rust-lang/rust/issues/131073 by making it so that const-stable functions are always stable try-job: test-various
This commit is contained in:
commit
54761cb3e8
118 changed files with 1605 additions and 760 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -318,6 +318,13 @@ pub(crate) struct RustcPromotablePairing {
|
|||
pub span: Span,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
|
|
@ -313,14 +313,23 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
|
|||
match m {
|
||||
ProcMacro::Derive(cd) => {
|
||||
cx.resolver.declare_proc_macro(cd.id);
|
||||
cx.expr_call(span, proc_macro_ty_method_path(cx, custom_derive), thin_vec![
|
||||
cx.expr_str(span, cd.trait_name),
|
||||
cx.expr_array_ref(
|
||||
span,
|
||||
cd.attrs.iter().map(|&s| cx.expr_str(span, s)).collect::<ThinVec<_>>(),
|
||||
),
|
||||
local_path(cx, cd.function_name),
|
||||
])
|
||||
// The call needs to use `harness_span` so that the const stability checker
|
||||
// accepts it.
|
||||
cx.expr_call(
|
||||
harness_span,
|
||||
proc_macro_ty_method_path(cx, custom_derive),
|
||||
thin_vec![
|
||||
cx.expr_str(span, cd.trait_name),
|
||||
cx.expr_array_ref(
|
||||
span,
|
||||
cd.attrs
|
||||
.iter()
|
||||
.map(|&s| cx.expr_str(span, s))
|
||||
.collect::<ThinVec<_>>(),
|
||||
),
|
||||
local_path(cx, cd.function_name),
|
||||
],
|
||||
)
|
||||
}
|
||||
ProcMacro::Attr(ca) | ProcMacro::Bang(ca) => {
|
||||
cx.resolver.declare_proc_macro(ca.id);
|
||||
|
@ -330,7 +339,9 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
|
|||
ProcMacro::Derive(_) => unreachable!(),
|
||||
};
|
||||
|
||||
cx.expr_call(span, proc_macro_ty_method_path(cx, ident), thin_vec![
|
||||
// The call needs to use `harness_span` so that the const stability checker
|
||||
// accepts it.
|
||||
cx.expr_call(harness_span, proc_macro_ty_method_path(cx, ident), thin_vec![
|
||||
cx.expr_str(span, ca.function_name.name),
|
||||
local_path(cx, ca.function_name),
|
||||
])
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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_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_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_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
|
||||
|
|
|
@ -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,29 +665,73 @@ 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;
|
||||
}
|
||||
|
||||
if !tcx.is_const_fn_raw(callee) && !is_trait {
|
||||
// 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(callee) && !is_trait {
|
||||
self.check_op(ops::FnCallNonConst {
|
||||
caller,
|
||||
callee,
|
||||
|
@ -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;
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
assert!(tcx.is_const_fn(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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -114,7 +122,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
|
|||
|
||||
if let Ok(Some(ImplSource::UserDefined(data))) = implsrc {
|
||||
// FIXME(effects) revisit this
|
||||
if !tcx.is_const_trait_impl_raw(data.impl_def_id) {
|
||||
if !tcx.is_const_trait_impl(data.impl_def_id) {
|
||||
let span = tcx.def_span(data.impl_def_id);
|
||||
err.subdiagnostic(errors::NonConstImplNote { span });
|
||||
}
|
||||
|
@ -166,7 +174,7 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
|
|||
let note = match self_ty.kind() {
|
||||
FnDef(def_id, ..) => {
|
||||
let span = tcx.def_span(*def_id);
|
||||
if ccx.tcx.is_const_fn_raw(*def_id) {
|
||||
if ccx.tcx.is_const_fn(*def_id) {
|
||||
span_bug!(span, "calling const FnDef errored when it shouldn't");
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
@ -431,8 +431,8 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
|
|||
// sensitive check here. But we can at least rule out functions that are not const at
|
||||
// all. That said, we have to allow calling functions inside a trait marked with
|
||||
// #[const_trait]. These *are* const-checked!
|
||||
// FIXME: why does `is_const_fn_raw` not classify them as const?
|
||||
if (!ecx.tcx.is_const_fn_raw(def) && !ecx.tcx.is_const_default_method(def))
|
||||
// FIXME(effects): why does `is_const_fn` not classify them as const?
|
||||
if (!ecx.tcx.is_const_fn(def) && !ecx.tcx.is_const_default_method(def))
|
||||
|| ecx.tcx.has_attr(def, sym::rustc_do_not_const_check)
|
||||
{
|
||||
// We certainly do *not* want to actually call the fn
|
||||
|
|
|
@ -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",
|
||||
|
@ -117,6 +121,34 @@ pub(crate) struct UnstableConstFn {
|
|||
pub def_path: String,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
@ -153,6 +185,15 @@ pub(crate) struct NonConstFnCall {
|
|||
pub kind: ConstContext,
|
||||
}
|
||||
|
||||
#[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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -617,11 +617,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
|
|||
DuplicatesOk, EncodeCrossCrate::Yes,
|
||||
"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:
|
||||
|
|
|
@ -1597,7 +1597,7 @@ fn impl_trait_header(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<ty::ImplTrai
|
|||
impl_.of_trait.as_ref().map(|ast_trait_ref| {
|
||||
let selfty = tcx.type_of(def_id).instantiate_identity();
|
||||
|
||||
check_impl_constness(tcx, tcx.is_const_trait_impl_raw(def_id.to_def_id()), ast_trait_ref);
|
||||
check_impl_constness(tcx, tcx.is_const_trait_impl(def_id.to_def_id()), ast_trait_ref);
|
||||
|
||||
let trait_ref = icx.lowerer().lower_impl_trait_ref(ast_trait_ref, selfty);
|
||||
|
||||
|
|
|
@ -537,7 +537,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
//
|
||||
// This check is here because there is currently no way to express a trait bound for `FnDef` types only.
|
||||
if let ty::FnDef(def_id, _args) = *arg_ty.kind() {
|
||||
if idx == 0 && !self.tcx.is_const_fn_raw(def_id) {
|
||||
if idx == 0 && !self.tcx.is_const_fn(def_id) {
|
||||
self.dcx().emit_err(errors::ConstSelectMustBeConst { span });
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1751,7 +1751,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
|||
// to tell them that in the diagnostic. Does not affect typeck.
|
||||
let is_constable = match element.kind {
|
||||
hir::ExprKind::Call(func, _args) => match *self.node_ty(func.hir_id).kind() {
|
||||
ty::FnDef(def_id, _) if tcx.is_const_fn(def_id) => traits::IsConstable::Fn,
|
||||
ty::FnDef(def_id, _) if tcx.is_stable_const_fn(def_id) => traits::IsConstable::Fn,
|
||||
_ => traits::IsConstable::No,
|
||||
},
|
||||
hir::ExprKind::Path(qpath) => {
|
||||
|
|
|
@ -1081,7 +1081,7 @@ fn should_encode_mir(
|
|||
&& (generics.requires_monomorphization(tcx)
|
||||
|| tcx.cross_crate_inlinable(def_id)));
|
||||
// The function has a `const` modifier or is in a `#[const_trait]`.
|
||||
let is_const_fn = tcx.is_const_fn_raw(def_id.to_def_id())
|
||||
let is_const_fn = tcx.is_const_fn(def_id.to_def_id())
|
||||
|| tcx.is_const_default_method(def_id.to_def_id());
|
||||
(is_const_fn, opt)
|
||||
}
|
||||
|
|
|
@ -329,7 +329,7 @@ impl<'hir> Map<'hir> {
|
|||
BodyOwnerKind::Static(mutability) => ConstContext::Static(mutability),
|
||||
|
||||
BodyOwnerKind::Fn if self.tcx.is_constructor(def_id) => return None,
|
||||
BodyOwnerKind::Fn | BodyOwnerKind::Closure if self.tcx.is_const_fn_raw(def_id) => {
|
||||
BodyOwnerKind::Fn | BodyOwnerKind::Closure if self.tcx.is_const_fn(def_id) => {
|
||||
ConstContext::ConstFn
|
||||
}
|
||||
BodyOwnerKind::Fn if self.tcx.is_const_default_method(def_id) => ConstContext::ConstFn,
|
||||
|
|
|
@ -17,7 +17,7 @@ where
|
|||
let mirs = def_ids
|
||||
.iter()
|
||||
.flat_map(|def_id| {
|
||||
if tcx.is_const_fn_raw(*def_id) {
|
||||
if tcx.is_const_fn(*def_id) {
|
||||
vec![tcx.optimized_mir(*def_id), tcx.mir_for_ctfe(*def_id)]
|
||||
} else {
|
||||
vec![tcx.instance_mir(ty::InstanceKind::Item(*def_id))]
|
||||
|
|
|
@ -317,7 +317,7 @@ pub fn write_mir_pretty<'tcx>(
|
|||
};
|
||||
|
||||
// For `const fn` we want to render both the optimized MIR and the MIR for ctfe.
|
||||
if tcx.is_const_fn_raw(def_id) {
|
||||
if tcx.is_const_fn(def_id) {
|
||||
render_body(w, tcx.optimized_mir(def_id))?;
|
||||
writeln!(w)?;
|
||||
writeln!(w, "// MIR FOR CTFE")?;
|
||||
|
|
|
@ -741,12 +741,11 @@ rustc_queries! {
|
|||
desc { |tcx| "computing drop-check constraints for `{}`", tcx.def_path_str(key) }
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a const fn, use the `is_const_fn` to know whether your crate
|
||||
/// actually sees it as const fn (e.g., the const-fn-ness might be unstable and you might
|
||||
/// not have the feature gate active).
|
||||
/// Returns `true` if this is a const fn / const impl.
|
||||
///
|
||||
/// **Do not call this function manually.** It is only meant to cache the base data for the
|
||||
/// `is_const_fn` function. Consider using `is_const_fn` or `is_const_fn_raw` instead.
|
||||
/// higher-level functions. Consider using `is_const_fn` or `is_const_trait_impl` instead.
|
||||
/// Also note that neither of them takes into account feature gates and stability.
|
||||
query constness(key: DefId) -> hir::Constness {
|
||||
desc { |tcx| "checking if item is const: `{}`", tcx.def_path_str(key) }
|
||||
separate_provide_extern
|
||||
|
|
|
@ -3120,39 +3120,24 @@ impl<'tcx> TyCtxt<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Whether the `def_id` counts as const fn in the current crate, considering all active
|
||||
/// feature gates
|
||||
pub fn is_const_fn(self, def_id: DefId) -> bool {
|
||||
if self.is_const_fn_raw(def_id) {
|
||||
match self.lookup_const_stability(def_id) {
|
||||
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)
|
||||
}
|
||||
// functions without const stability are either stable user written
|
||||
// const fn or the user is using feature gates and we thus don't
|
||||
// care what they do
|
||||
_ => true,
|
||||
/// Whether `def_id` is a stable const fn (i.e., doesn't need any feature gates to be called).
|
||||
///
|
||||
/// When this is `false`, the function may still be callable as a `const fn` due to features
|
||||
/// being enabled!
|
||||
pub fn is_stable_const_fn(self, def_id: DefId) -> bool {
|
||||
self.is_const_fn(def_id)
|
||||
&& match self.lookup_const_stability(def_id) {
|
||||
None => true, // a fn in a non-staged_api crate
|
||||
Some(stability) if stability.is_const_stable() => true,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME(effects): Please remove this. It's a footgun.
|
||||
/// Whether the trait impl is marked const. This does not consider stability or feature gates.
|
||||
pub fn is_const_trait_impl_raw(self, def_id: DefId) -> bool {
|
||||
let Some(local_def_id) = def_id.as_local() else { return false };
|
||||
let node = self.hir_node_by_def_id(local_def_id);
|
||||
|
||||
matches!(
|
||||
node,
|
||||
hir::Node::Item(hir::Item {
|
||||
kind: hir::ItemKind::Impl(hir::Impl { constness, .. }),
|
||||
..
|
||||
}) if matches!(constness, hir::Constness::Const)
|
||||
)
|
||||
pub fn is_const_trait_impl(self, def_id: DefId) -> bool {
|
||||
self.def_kind(def_id) == DefKind::Impl { of_trait: true }
|
||||
&& self.constness(def_id) == hir::Constness::Const
|
||||
}
|
||||
|
||||
pub fn intrinsic(self, def_id: impl IntoQueryParam<DefId> + Copy) -> Option<ty::IntrinsicDef> {
|
||||
|
|
|
@ -1995,8 +1995,11 @@ impl<'tcx> TyCtxt<'tcx> {
|
|||
(ident, scope)
|
||||
}
|
||||
|
||||
/// Checks whether this is a `const fn`. Returns `false` for non-functions.
|
||||
///
|
||||
/// Even if this returns `true`, constness may still be unstable!
|
||||
#[inline]
|
||||
pub fn is_const_fn_raw(self, def_id: DefId) -> bool {
|
||||
pub fn is_const_fn(self, def_id: DefId) -> bool {
|
||||
matches!(
|
||||
self.def_kind(def_id),
|
||||
DefKind::Fn | DefKind::AssocFn | DefKind::Ctor(_, CtorKind::Fn) | DefKind::Closure
|
||||
|
|
|
@ -673,7 +673,7 @@ impl<'tcx> Validator<'_, 'tcx> {
|
|||
}
|
||||
// Make sure the callee is a `const fn`.
|
||||
let is_const_fn = match *fn_ty.kind() {
|
||||
ty::FnDef(def_id, _) => self.tcx.is_const_fn_raw(def_id),
|
||||
ty::FnDef(def_id, _) => self.tcx.is_const_fn(def_id),
|
||||
_ => false,
|
||||
};
|
||||
if !is_const_fn {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1997,7 +1997,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
|
|||
) {
|
||||
match target {
|
||||
Target::Fn | Target::Method(_)
|
||||
if self.tcx.is_const_fn_raw(hir_id.expect_owner().to_def_id()) => {}
|
||||
if self.tcx.is_const_fn(hir_id.expect_owner().to_def_id()) => {}
|
||||
// FIXME(#80564): We permit struct fields and match arms to have an
|
||||
// `#[allow_internal_unstable]` attribute with just a lint, because we previously
|
||||
// erroneously allowed it and some crates used it accidentally, to be compatible
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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,65 @@ 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()
|
||||
&& (!self.in_trait_impl || !self.tcx.is_const_fn_raw(def_id.to_def_id()))
|
||||
// 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(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())
|
||||
// FIXME: we skip this check targets until
|
||||
// <https://github.com/rust-lang/stdarch/pull/1654> propagates.
|
||||
&& false
|
||||
{
|
||||
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 +282,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 +297,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 +401,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 +420,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 +440,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 +464,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 +480,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 +491,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 +510,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 +522,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 +553,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 +582,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;
|
||||
}
|
||||
|
@ -554,10 +598,11 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> {
|
|||
}
|
||||
|
||||
let is_const = self.tcx.is_const_fn(def_id.to_def_id())
|
||||
|| self.tcx.is_const_trait_impl_raw(def_id.to_def_id());
|
||||
|| self.tcx.is_const_trait_impl(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 +632,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 +646,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 +715,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 +778,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
|
||||
|
@ -763,7 +820,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
|
|||
// `#![feature(const_trait_impl)]` is unstable, so any impl declared stable
|
||||
// needs to have an error emitted.
|
||||
if features.const_trait_impl()
|
||||
&& self.tcx.is_const_trait_impl_raw(item.owner_id.to_def_id())
|
||||
&& self.tcx.is_const_trait_impl(item.owner_id.to_def_id())
|
||||
&& const_stab.is_some_and(|(stab, _)| stab.is_const_stable())
|
||||
{
|
||||
self.tcx.dcx().emit_err(errors::TraitImplConstStable { span: item.span });
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -393,7 +393,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
let self_ty = obligation.self_ty().skip_binder();
|
||||
match *self_ty.kind() {
|
||||
ty::Closure(def_id, _) => {
|
||||
let is_const = self.tcx().is_const_fn_raw(def_id);
|
||||
let is_const = self.tcx().is_const_fn(def_id);
|
||||
debug!(?kind, ?obligation, "assemble_unboxed_candidates");
|
||||
match self.infcx.closure_kind(self_ty) {
|
||||
Some(closure_kind) => {
|
||||
|
@ -413,7 +413,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
}
|
||||
ty::CoroutineClosure(def_id, args) => {
|
||||
let args = args.as_coroutine_closure();
|
||||
let is_const = self.tcx().is_const_fn_raw(def_id);
|
||||
let is_const = self.tcx().is_const_fn(def_id);
|
||||
if let Some(closure_kind) = self.infcx.closure_kind(self_ty)
|
||||
// Ambiguity if upvars haven't been constrained yet
|
||||
&& !args.tupled_upvars_ty().is_ty_var()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue