Skip to content

Commit eddc24c

Browse files
committed
Auto merge of rust-lang#131349 - RalfJung:const-stability-checks, r=<try>
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. 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]`. Note that the handling of inherited stability is an utter spaghetti mess with few comments and [odd behavior](https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/Trying.20to.20understand.20stability.2Ers), so I hope I didn't screw up this part... ### 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 rust-lang#129815 (but not finished since this is not yet sufficient to safely let us expose `const fn` from hashbrown) Fixes rust-lang#131073 by making it so that const-stable functions are always stable
2 parents 7caad69 + eb8d6c1 commit eddc24c

File tree

83 files changed

+1509
-521
lines changed

Some content is hidden

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

83 files changed

+1509
-521
lines changed

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

compiler/rustc_attr/src/builtin.rs

+98-8
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,15 @@ impl Stability {
9292
#[derive(HashStable_Generic)]
9393
pub struct ConstStability {
9494
pub level: StabilityLevel,
95-
pub feature: Symbol,
95+
/// Says whether this function has an explicit `rustc_const_(un)stable` attribute.
96+
/// If `false`, the const stability information was inferred from the regular
97+
/// stability information.
98+
pub has_const_stable_attr: bool,
99+
/// This can be `None` for functions that are not const-callable from outside code under any
100+
/// feature gate, but are tracked here for the purpose of `safe_to_expose_on_stable`.
101+
pub feature: Option<Symbol>,
102+
/// This is true iff the `const_stable_indirect` attribute is present.
103+
pub const_stable_indirect: bool,
96104
/// whether the function has a `#[rustc_promotable]` attribute
97105
pub promotable: bool,
98106
}
@@ -268,17 +276,22 @@ pub fn find_stability(
268276

269277
/// Collects stability info from `rustc_const_stable`/`rustc_const_unstable`/`rustc_promotable`
270278
/// attributes in `attrs`. Returns `None` if no stability attributes are found.
279+
///
280+
/// The second component is the `const_stable_indirect` attribute if present, which can be meaningful
281+
/// even if there is no `rustc_const_stable`/`rustc_const_unstable`.
271282
pub fn find_const_stability(
272283
sess: &Session,
273284
attrs: &[Attribute],
274285
item_sp: Span,
275-
) -> Option<(ConstStability, Span)> {
286+
) -> (Option<(ConstStability, Span)>, Option<Span>) {
276287
let mut const_stab: Option<(ConstStability, Span)> = None;
277288
let mut promotable = false;
289+
let mut const_stable_indirect = None;
278290

279291
for attr in attrs {
280292
match attr.name_or_empty() {
281293
sym::rustc_promotable => promotable = true,
294+
sym::rustc_const_stable_indirect => const_stable_indirect = Some(attr.span),
282295
sym::rustc_const_unstable => {
283296
if const_stab.is_some() {
284297
sess.dcx()
@@ -287,8 +300,16 @@ pub fn find_const_stability(
287300
}
288301

289302
if let Some((feature, level)) = parse_unstability(sess, attr) {
290-
const_stab =
291-
Some((ConstStability { level, feature, promotable: false }, attr.span));
303+
const_stab = Some((
304+
ConstStability {
305+
level,
306+
has_const_stable_attr: true,
307+
feature: Some(feature),
308+
const_stable_indirect: false,
309+
promotable: false,
310+
},
311+
attr.span,
312+
));
292313
}
293314
}
294315
sym::rustc_const_stable => {
@@ -298,15 +319,23 @@ pub fn find_const_stability(
298319
break;
299320
}
300321
if let Some((feature, level)) = parse_stability(sess, attr) {
301-
const_stab =
302-
Some((ConstStability { level, feature, promotable: false }, attr.span));
322+
const_stab = Some((
323+
ConstStability {
324+
level,
325+
has_const_stable_attr: true,
326+
feature: Some(feature),
327+
const_stable_indirect: false,
328+
promotable: false,
329+
},
330+
attr.span,
331+
));
303332
}
304333
}
305334
_ => {}
306335
}
307336
}
308337

309-
// Merge the const-unstable info into the stability info
338+
// Merge promotable and not_exposed_on_stable into stability info
310339
if promotable {
311340
match &mut const_stab {
312341
Some((stab, _)) => stab.promotable = promotable,
@@ -317,8 +346,69 @@ pub fn find_const_stability(
317346
}
318347
}
319348
}
349+
if const_stable_indirect.is_some() {
350+
match &mut const_stab {
351+
Some((stab, _)) => {
352+
if stab.is_const_unstable() {
353+
stab.const_stable_indirect = true;
354+
} else {
355+
_ = sess.dcx().emit_err(session_diagnostics::RustcConstStableIndirectPairing {
356+
span: item_sp,
357+
})
358+
}
359+
}
360+
_ => {
361+
// We ignore the `#[rustc_const_stable_indirect]` here, it should be picked up by
362+
// the `default_const_unstable` logic.
363+
}
364+
}
365+
}
320366

321-
const_stab
367+
(const_stab, const_stable_indirect)
368+
}
369+
370+
/// Called for `fn` that don't have a const stability.
371+
///
372+
/// `effective_reg_stability` must be the effecive regular stability, i.e. after applying all the
373+
/// rules about "inherited" stability.
374+
pub fn default_const_stability(
375+
_sess: &Session,
376+
is_const_fn: bool,
377+
const_stable_indirect: bool,
378+
effective_reg_stability: Option<&Stability>,
379+
) -> Option<ConstStability> {
380+
// Intrinsics are *not* `const fn` here, and yet we want to add a default const stability
381+
// for them if they are marked `const_stable_indirect`.
382+
if (is_const_fn || const_stable_indirect)
383+
&& let Some(reg_stability) = effective_reg_stability
384+
&& reg_stability.level.is_unstable()
385+
{
386+
// This has a feature gate, reuse that for const stability.
387+
// We only want to do this if it is an unstable feature gate.
388+
Some(ConstStability {
389+
feature: Some(reg_stability.feature),
390+
has_const_stable_attr: false,
391+
const_stable_indirect,
392+
promotable: false,
393+
level: reg_stability.level,
394+
})
395+
} else if const_stable_indirect {
396+
// Make it const-unstable without a feature gate, to record the `const_stable_indirect`.
397+
Some(ConstStability {
398+
feature: None,
399+
has_const_stable_attr: false,
400+
const_stable_indirect,
401+
promotable: false,
402+
level: StabilityLevel::Unstable {
403+
reason: UnstableReason::Default,
404+
issue: None,
405+
is_soft: false,
406+
implied_by: None,
407+
},
408+
})
409+
} else {
410+
None
411+
}
322412
}
323413

324414
/// Collects stability info from `rustc_default_body_unstable` attributes in `attrs`.

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 {

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
])

compiler/rustc_const_eval/messages.ftl

+8-6
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
@@ -396,17 +394,21 @@ const_eval_uninhabited_enum_variant_read =
396394
read discriminant of an uninhabited enum variant
397395
const_eval_uninhabited_enum_variant_written =
398396
writing discriminant of an uninhabited enum variant
397+
398+
const_eval_unmarked_const_fn_exposed = `{$def_path}` cannot be (indirectly) exposed to stable
399+
.help = either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
400+
399401
const_eval_unreachable = entering unreachable code
400402
const_eval_unreachable_unwind =
401403
unwinding past a stack frame that does not allow unwinding
402404
403405
const_eval_unsized_local = unsized locals are not supported
404406
const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn
405407
406-
const_eval_unstable_in_stable =
407-
const-stable function cannot use `#[feature({$gate})]`
408-
.unstable_sugg = if the function is not (yet) meant to be stable, make this function unstably const
409-
.bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (but requires team approval)
408+
const_eval_unstable_in_stable_exposed =
409+
const function that might be (indirectly) exposed to stable cannot use `#[feature({$gate})]`
410+
.unstable_sugg = if the function is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this what you probably want to do)
411+
.bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
410412
411413
const_eval_unterminated_c_string =
412414
reading a null-terminated string starting at {$pointer} with no null found before end of allocation

0 commit comments

Comments
 (0)