Skip to content

Commit 54761cb

Browse files
committed
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 rust-lang/rustc-dev-guide#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 #129815 (but not finished since this is not yet sufficient to safely let us expose `const fn` from hashbrown) Fixes #131073 by making it so that const-stable functions are always stable try-job: test-various
2 parents c1db4dc + 8849ac6 commit 54761cb

File tree

118 files changed

+1596
-751
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

118 files changed

+1596
-751
lines changed

Diff for: compiler/rustc_attr/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ attr_non_ident_feature =
9191
attr_rustc_allowed_unstable_pairing =
9292
`rustc_allowed_through_unstable_modules` attribute must be paired with a `stable` attribute
9393
94+
attr_rustc_const_stable_indirect_pairing =
95+
`const_stable_indirect` attribute does not make sense on `rustc_const_stable` function, its behavior is already implied
96+
9497
attr_rustc_promotable_pairing =
9598
`rustc_promotable` attribute must be paired with either a `rustc_const_unstable` or a `rustc_const_stable` attribute
9699

Diff for: compiler/rustc_attr/src/builtin.rs

+71-7
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ use rustc_session::lint::BuiltinLintDiag;
1616
use rustc_session::lint::builtin::UNEXPECTED_CFGS;
1717
use rustc_session::parse::feature_err;
1818
use rustc_session::{RustcVersion, Session};
19-
use rustc_span::Span;
2019
use rustc_span::hygiene::Transparency;
2120
use rustc_span::symbol::{Symbol, kw, sym};
21+
use rustc_span::{DUMMY_SP, Span};
2222

2323
use crate::fluent_generated;
2424
use crate::session_diagnostics::{self, IncorrectReprFormatGenericCause};
@@ -92,7 +92,11 @@ impl Stability {
9292
#[derive(HashStable_Generic)]
9393
pub struct ConstStability {
9494
pub level: StabilityLevel,
95-
pub feature: Symbol,
95+
/// This can be `None` for functions that do not have an explicit const feature.
96+
/// We still track them for recursive const stability checks.
97+
pub feature: Option<Symbol>,
98+
/// This is true iff the `const_stable_indirect` attribute is present.
99+
pub const_stable_indirect: bool,
96100
/// whether the function has a `#[rustc_promotable]` attribute
97101
pub promotable: bool,
98102
}
@@ -268,17 +272,23 @@ pub fn find_stability(
268272

269273
/// Collects stability info from `rustc_const_stable`/`rustc_const_unstable`/`rustc_promotable`
270274
/// attributes in `attrs`. Returns `None` if no stability attributes are found.
275+
///
276+
/// `is_const_fn` indicates whether this is a function marked as `const`. It will always
277+
/// be false for intrinsics in an `extern` block!
271278
pub fn find_const_stability(
272279
sess: &Session,
273280
attrs: &[Attribute],
274281
item_sp: Span,
282+
is_const_fn: bool,
275283
) -> Option<(ConstStability, Span)> {
276284
let mut const_stab: Option<(ConstStability, Span)> = None;
277285
let mut promotable = false;
286+
let mut const_stable_indirect = None;
278287

279288
for attr in attrs {
280289
match attr.name_or_empty() {
281290
sym::rustc_promotable => promotable = true,
291+
sym::rustc_const_stable_indirect => const_stable_indirect = Some(attr.span),
282292
sym::rustc_const_unstable => {
283293
if const_stab.is_some() {
284294
sess.dcx()
@@ -287,8 +297,15 @@ pub fn find_const_stability(
287297
}
288298

289299
if let Some((feature, level)) = parse_unstability(sess, attr) {
290-
const_stab =
291-
Some((ConstStability { level, feature, promotable: false }, attr.span));
300+
const_stab = Some((
301+
ConstStability {
302+
level,
303+
feature: Some(feature),
304+
const_stable_indirect: false,
305+
promotable: false,
306+
},
307+
attr.span,
308+
));
292309
}
293310
}
294311
sym::rustc_const_stable => {
@@ -298,15 +315,22 @@ pub fn find_const_stability(
298315
break;
299316
}
300317
if let Some((feature, level)) = parse_stability(sess, attr) {
301-
const_stab =
302-
Some((ConstStability { level, feature, promotable: false }, attr.span));
318+
const_stab = Some((
319+
ConstStability {
320+
level,
321+
feature: Some(feature),
322+
const_stable_indirect: false,
323+
promotable: false,
324+
},
325+
attr.span,
326+
));
303327
}
304328
}
305329
_ => {}
306330
}
307331
}
308332

309-
// Merge the const-unstable info into the stability info
333+
// Merge promotable and not_exposed_on_stable into stability info
310334
if promotable {
311335
match &mut const_stab {
312336
Some((stab, _)) => stab.promotable = promotable,
@@ -317,6 +341,46 @@ pub fn find_const_stability(
317341
}
318342
}
319343
}
344+
if const_stable_indirect.is_some() {
345+
match &mut const_stab {
346+
Some((stab, _)) => {
347+
if stab.is_const_unstable() {
348+
stab.const_stable_indirect = true;
349+
} else {
350+
_ = sess.dcx().emit_err(session_diagnostics::RustcConstStableIndirectPairing {
351+
span: item_sp,
352+
})
353+
}
354+
}
355+
_ => {
356+
// We ignore the `#[rustc_const_stable_indirect]` here, it should be picked up by
357+
// the `default_const_unstable` logic.
358+
}
359+
}
360+
}
361+
// Make sure if `const_stable_indirect` is present, that is recorded. Also make sure all `const
362+
// fn` get *some* marker, since we are a staged_api crate and therefore will do recursive const
363+
// stability checks for them. We need to do this because the default for whether an unmarked
364+
// function enforces recursive stability differs between staged-api crates and force-unmarked
365+
// crates: in force-unmarked crates, only functions *explicitly* marked `const_stable_indirect`
366+
// enforce recursive stability. Therefore when `lookup_const_stability` is `None`, we have to
367+
// assume the function does not have recursive stability. All functions that *do* have recursive
368+
// stability must explicitly record this, and so that's what we do for all `const fn` in a
369+
// staged_api crate.
370+
if (is_const_fn || const_stable_indirect.is_some()) && const_stab.is_none() {
371+
let c = ConstStability {
372+
feature: None,
373+
const_stable_indirect: const_stable_indirect.is_some(),
374+
promotable: false,
375+
level: StabilityLevel::Unstable {
376+
reason: UnstableReason::Default,
377+
issue: None,
378+
is_soft: false,
379+
implied_by: None,
380+
},
381+
};
382+
const_stab = Some((c, const_stable_indirect.unwrap_or(DUMMY_SP)));
383+
}
320384

321385
const_stab
322386
}

Diff for: compiler/rustc_attr/src/session_diagnostics.rs

+7
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,13 @@ pub(crate) struct RustcPromotablePairing {
318318
pub span: Span,
319319
}
320320

321+
#[derive(Diagnostic)]
322+
#[diag(attr_rustc_const_stable_indirect_pairing)]
323+
pub(crate) struct RustcConstStableIndirectPairing {
324+
#[primary_span]
325+
pub span: Span,
326+
}
327+
321328
#[derive(Diagnostic)]
322329
#[diag(attr_rustc_allowed_unstable_pairing, code = E0789)]
323330
pub(crate) struct RustcAllowedUnstablePairing {

Diff for: compiler/rustc_builtin_macros/src/proc_macro_harness.rs

+20-9
Original file line numberDiff line numberDiff line change
@@ -313,14 +313,23 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
313313
match m {
314314
ProcMacro::Derive(cd) => {
315315
cx.resolver.declare_proc_macro(cd.id);
316-
cx.expr_call(span, proc_macro_ty_method_path(cx, custom_derive), thin_vec![
317-
cx.expr_str(span, cd.trait_name),
318-
cx.expr_array_ref(
319-
span,
320-
cd.attrs.iter().map(|&s| cx.expr_str(span, s)).collect::<ThinVec<_>>(),
321-
),
322-
local_path(cx, cd.function_name),
323-
])
316+
// The call needs to use `harness_span` so that the const stability checker
317+
// accepts it.
318+
cx.expr_call(
319+
harness_span,
320+
proc_macro_ty_method_path(cx, custom_derive),
321+
thin_vec![
322+
cx.expr_str(span, cd.trait_name),
323+
cx.expr_array_ref(
324+
span,
325+
cd.attrs
326+
.iter()
327+
.map(|&s| cx.expr_str(span, s))
328+
.collect::<ThinVec<_>>(),
329+
),
330+
local_path(cx, cd.function_name),
331+
],
332+
)
324333
}
325334
ProcMacro::Attr(ca) | ProcMacro::Bang(ca) => {
326335
cx.resolver.declare_proc_macro(ca.id);
@@ -330,7 +339,9 @@ fn mk_decls(cx: &mut ExtCtxt<'_>, macros: &[ProcMacro]) -> P<ast::Item> {
330339
ProcMacro::Derive(_) => unreachable!(),
331340
};
332341

333-
cx.expr_call(span, proc_macro_ty_method_path(cx, ident), thin_vec![
342+
// The call needs to use `harness_span` so that the const stability checker
343+
// accepts it.
344+
cx.expr_call(harness_span, proc_macro_ty_method_path(cx, ident), thin_vec![
334345
cx.expr_str(span, ca.function_name.name),
335346
local_path(cx, ca.function_name),
336347
])

Diff for: compiler/rustc_codegen_cranelift/patches/0027-stdlib-128bit-atomic-operations.patch

+5-3
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs
3838
index d9de37e..8293fce 100644
3939
--- a/library/core/src/sync/atomic.rs
4040
+++ b/library/core/src/sync/atomic.rs
41-
@@ -2996,42 +2996,6 @@ atomic_int! {
41+
@@ -2996,44 +2996,6 @@ atomic_int! {
4242
8,
4343
u64 AtomicU64
4444
}
@@ -52,7 +52,8 @@ index d9de37e..8293fce 100644
5252
- unstable(feature = "integer_atomics", issue = "99069"),
5353
- unstable(feature = "integer_atomics", issue = "99069"),
5454
- unstable(feature = "integer_atomics", issue = "99069"),
55-
- rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
55+
- rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
56+
- rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
5657
- cfg_attr(not(test), rustc_diagnostic_item = "AtomicI128"),
5758
- "i128",
5859
- "#![feature(integer_atomics)]\n\n",
@@ -70,7 +71,8 @@ index d9de37e..8293fce 100644
7071
- unstable(feature = "integer_atomics", issue = "99069"),
7172
- unstable(feature = "integer_atomics", issue = "99069"),
7273
- unstable(feature = "integer_atomics", issue = "99069"),
73-
- rustc_const_stable(feature = "const_integer_atomics", since = "1.34.0"),
74+
- rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
75+
- rustc_const_unstable(feature = "integer_atomics", issue = "99069"),
7476
- cfg_attr(not(test), rustc_diagnostic_item = "AtomicU128"),
7577
- "u128",
7678
- "#![feature(integer_atomics)]\n\n",

Diff for: compiler/rustc_const_eval/messages.ftl

+20-7
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,6 @@ const_eval_const_context = {$kind ->
4141
*[other] {""}
4242
}
4343
44-
const_eval_const_stable = const-stable functions can only call other const-stable functions
45-
4644
const_eval_copy_nonoverlapping_overlapping =
4745
`copy_nonoverlapping` called on overlapping ranges
4846
@@ -259,6 +257,9 @@ const_eval_non_const_fn_call =
259257
const_eval_non_const_impl =
260258
impl defined here, but it is not `const`
261259
260+
const_eval_non_const_intrinsic =
261+
cannot call non-const intrinsic `{$name}` in {const_eval_const_context}s
262+
262263
const_eval_not_enough_caller_args =
263264
calling a function with fewer arguments than it requires
264265
@@ -397,17 +398,29 @@ const_eval_uninhabited_enum_variant_read =
397398
read discriminant of an uninhabited enum variant
398399
const_eval_uninhabited_enum_variant_written =
399400
writing discriminant of an uninhabited enum variant
401+
402+
const_eval_unmarked_const_fn_exposed = `{$def_path}` cannot be (indirectly) exposed to stable
403+
.help = either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
404+
const_eval_unmarked_intrinsic_exposed = intrinsic `{$def_path}` cannot be (indirectly) exposed to stable
405+
.help = mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_const_stable_indirect]` (but this requires team approval)
406+
400407
const_eval_unreachable = entering unreachable code
401408
const_eval_unreachable_unwind =
402409
unwinding past a stack frame that does not allow unwinding
403410
404411
const_eval_unsized_local = unsized locals are not supported
405412
const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn
406-
407-
const_eval_unstable_in_stable =
408-
const-stable function cannot use `#[feature({$gate})]`
409-
.unstable_sugg = if the function is not (yet) meant to be stable, make this function unstably const
410-
.bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (but requires team approval)
413+
const_eval_unstable_in_stable_exposed =
414+
const function that might be (indirectly) exposed to stable cannot use `#[feature({$gate})]`
415+
.is_function_call = mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
416+
.unstable_sugg = if the {$is_function_call2 ->
417+
[true] caller
418+
*[false] function
419+
} is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
420+
.bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
421+
422+
const_eval_unstable_intrinsic = `{$name}` is not yet stable as a const intrinsic
423+
.help = add `#![feature({$feature})]` to the crate attributes to enable
411424
412425
const_eval_unterminated_c_string =
413426
reading a null-terminated string starting at {$pointer} with no null found before end of allocation

0 commit comments

Comments
 (0)