Skip to content

Commit 4aaf467

Browse files
committed
Implement MIR const trait stability checks
1 parent 0df0662 commit 4aaf467

File tree

9 files changed

+232
-120
lines changed

9 files changed

+232
-120
lines changed

compiler/rustc_const_eval/messages.ftl

+2-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ const_eval_uninhabited_enum_variant_read =
403403
const_eval_uninhabited_enum_variant_written =
404404
writing discriminant of an uninhabited enum variant
405405
406-
const_eval_unmarked_const_fn_exposed = `{$def_path}` cannot be (indirectly) exposed to stable
406+
const_eval_unmarked_const_item_exposed = `{$def_path}` cannot be (indirectly) exposed to stable
407407
.help = either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
408408
const_eval_unmarked_intrinsic_exposed = intrinsic `{$def_path}` cannot be (indirectly) exposed to stable
409409
.help = mark the caller as `#[rustc_const_unstable]`, or mark the intrinsic `#[rustc_intrinsic_const_stable_indirect]` (but this requires team approval)
@@ -414,6 +414,7 @@ const_eval_unreachable_unwind =
414414
415415
const_eval_unsized_local = unsized locals are not supported
416416
const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn
417+
const_eval_unstable_const_trait = `{$def_path}` is not yet stable as a const trait
417418
const_eval_unstable_in_stable_exposed =
418419
const function that might be (indirectly) exposed to stable cannot use `#[feature({$gate})]`
419420
.is_function_call = mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unstable features

compiler/rustc_const_eval/src/check_consts/check.rs

+87-81
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::ops::Deref;
88

99
use rustc_attr_parsing::{ConstStability, StabilityLevel};
1010
use rustc_errors::{Diag, ErrorGuaranteed};
11+
use rustc_hir::def::DefKind;
1112
use rustc_hir::def_id::DefId;
1213
use rustc_hir::{self as hir, LangItem};
1314
use rustc_index::bit_set::DenseBitSet;
@@ -29,7 +30,7 @@ use super::ops::{self, NonConstOp, Status};
2930
use super::qualifs::{self, HasMutInterior, NeedsDrop, NeedsNonConstDrop};
3031
use super::resolver::FlowSensitiveAnalysis;
3132
use super::{ConstCx, Qualif};
32-
use crate::check_consts::is_safe_to_expose_on_stable_const_fn;
33+
use crate::check_consts::is_fn_or_trait_safe_to_expose_on_stable;
3334
use crate::errors;
3435

3536
type QualifResults<'mir, 'tcx, Q> =
@@ -470,6 +471,88 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
470471
self.tcx.crate_level_attribute_injection_span(self.tcx.local_def_id_to_hir_id(id))
471472
})
472473
}
474+
475+
/// Check the const stability of the given item (fn or trait).
476+
fn check_callee_stability(&mut self, def_id: DefId) {
477+
match self.tcx.lookup_const_stability(def_id) {
478+
Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => {
479+
// All good.
480+
}
481+
None => {
482+
// This doesn't need a separate const-stability check -- const-stability equals
483+
// regular stability, and regular stability is checked separately.
484+
// However, we *do* have to worry about *recursive* const stability.
485+
if self.enforce_recursive_const_stability()
486+
&& !is_fn_or_trait_safe_to_expose_on_stable(self.tcx, def_id)
487+
{
488+
self.dcx().emit_err(errors::UnmarkedConstItemExposed {
489+
span: self.span,
490+
def_path: self.tcx.def_path_str(def_id),
491+
});
492+
}
493+
}
494+
Some(ConstStability {
495+
level: StabilityLevel::Unstable { implied_by: implied_feature, issue, .. },
496+
feature,
497+
..
498+
}) => {
499+
// An unstable const fn/trait with a feature gate.
500+
let callee_safe_to_expose_on_stable =
501+
is_fn_or_trait_safe_to_expose_on_stable(self.tcx, def_id);
502+
503+
// We only honor `span.allows_unstable` aka `#[allow_internal_unstable]` if
504+
// the callee is safe to expose, to avoid bypassing recursive stability.
505+
// This is not ideal since it means the user sees an error, not the macro
506+
// author, but that's also the case if one forgets to set
507+
// `#[allow_internal_unstable]` in the first place. Note that this cannot be
508+
// integrated in the check below since we want to enforce
509+
// `callee_safe_to_expose_on_stable` even if
510+
// `!self.enforce_recursive_const_stability()`.
511+
if (self.span.allows_unstable(feature)
512+
|| implied_feature.is_some_and(|f| self.span.allows_unstable(f)))
513+
&& callee_safe_to_expose_on_stable
514+
{
515+
return;
516+
}
517+
518+
// We can't use `check_op` to check whether the feature is enabled because
519+
// the logic is a bit different than elsewhere: local functions don't need
520+
// the feature gate, and there might be an "implied" gate that also suffices
521+
// to allow this.
522+
let feature_enabled = def_id.is_local()
523+
|| self.tcx.features().enabled(feature)
524+
|| implied_feature.is_some_and(|f| self.tcx.features().enabled(f))
525+
|| {
526+
// When we're compiling the compiler itself we may pull in
527+
// crates from crates.io, but those crates may depend on other
528+
// crates also pulled in from crates.io. We want to ideally be
529+
// able to compile everything without requiring upstream
530+
// modifications, so in the case that this looks like a
531+
// `rustc_private` crate (e.g., a compiler crate) and we also have
532+
// the `-Z force-unstable-if-unmarked` flag present (we're
533+
// compiling a compiler crate), then let this missing feature
534+
// annotation slide.
535+
// This matches what we do in `eval_stability_allow_unstable` for
536+
// regular stability.
537+
feature == sym::rustc_private
538+
&& issue == NonZero::new(27812)
539+
&& self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked
540+
};
541+
// Even if the feature is enabled, we still need check_op to double-check
542+
// this if the callee is not safe to expose on stable.
543+
if !feature_enabled || !callee_safe_to_expose_on_stable {
544+
self.check_op(ops::CallUnstable {
545+
def_id,
546+
feature,
547+
feature_enabled,
548+
safe_to_expose_on_stable: callee_safe_to_expose_on_stable,
549+
suggestion_span: self.crate_inject_span(),
550+
is_function_call: self.tcx.def_kind(def_id) != DefKind::Trait,
551+
});
552+
}
553+
}
554+
}
555+
}
473556
}
474557

475558
impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
@@ -716,8 +799,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
716799
span: *fn_span,
717800
call_source,
718801
});
719-
// FIXME(const_trait_impl): do a more fine-grained check whether this
720-
// particular trait can be const-stably called.
802+
self.check_callee_stability(trait_did);
721803
} else {
722804
// Not even a const trait.
723805
self.check_op(ops::FnCallNonConst {
@@ -793,7 +875,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
793875
// fallback body is safe to expose on stable.
794876
let is_const_stable = intrinsic.const_stable
795877
|| (!intrinsic.must_be_overridden
796-
&& is_safe_to_expose_on_stable_const_fn(tcx, callee));
878+
&& is_fn_or_trait_safe_to_expose_on_stable(tcx, callee));
797879
match tcx.lookup_const_stability(callee) {
798880
None => {
799881
// This doesn't need a separate const-stability check -- const-stability equals
@@ -842,83 +924,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
842924
}
843925

844926
// Finally, stability for regular function calls -- this is the big one.
845-
match tcx.lookup_const_stability(callee) {
846-
Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => {
847-
// All good.
848-
}
849-
None => {
850-
// This doesn't need a separate const-stability check -- const-stability equals
851-
// regular stability, and regular stability is checked separately.
852-
// However, we *do* have to worry about *recursive* const stability.
853-
if self.enforce_recursive_const_stability()
854-
&& !is_safe_to_expose_on_stable_const_fn(tcx, callee)
855-
{
856-
self.dcx().emit_err(errors::UnmarkedConstFnExposed {
857-
span: self.span,
858-
def_path: self.tcx.def_path_str(callee),
859-
});
860-
}
861-
}
862-
Some(ConstStability {
863-
level: StabilityLevel::Unstable { implied_by: implied_feature, issue, .. },
864-
feature,
865-
..
866-
}) => {
867-
// An unstable const fn with a feature gate.
868-
let callee_safe_to_expose_on_stable =
869-
is_safe_to_expose_on_stable_const_fn(tcx, callee);
870-
871-
// We only honor `span.allows_unstable` aka `#[allow_internal_unstable]` if
872-
// the callee is safe to expose, to avoid bypassing recursive stability.
873-
// This is not ideal since it means the user sees an error, not the macro
874-
// author, but that's also the case if one forgets to set
875-
// `#[allow_internal_unstable]` in the first place. Note that this cannot be
876-
// integrated in the check below since we want to enforce
877-
// `callee_safe_to_expose_on_stable` even if
878-
// `!self.enforce_recursive_const_stability()`.
879-
if (self.span.allows_unstable(feature)
880-
|| implied_feature.is_some_and(|f| self.span.allows_unstable(f)))
881-
&& callee_safe_to_expose_on_stable
882-
{
883-
return;
884-
}
885-
886-
// We can't use `check_op` to check whether the feature is enabled because
887-
// the logic is a bit different than elsewhere: local functions don't need
888-
// the feature gate, and there might be an "implied" gate that also suffices
889-
// to allow this.
890-
let feature_enabled = callee.is_local()
891-
|| tcx.features().enabled(feature)
892-
|| implied_feature.is_some_and(|f| tcx.features().enabled(f))
893-
|| {
894-
// When we're compiling the compiler itself we may pull in
895-
// crates from crates.io, but those crates may depend on other
896-
// crates also pulled in from crates.io. We want to ideally be
897-
// able to compile everything without requiring upstream
898-
// modifications, so in the case that this looks like a
899-
// `rustc_private` crate (e.g., a compiler crate) and we also have
900-
// the `-Z force-unstable-if-unmarked` flag present (we're
901-
// compiling a compiler crate), then let this missing feature
902-
// annotation slide.
903-
// This matches what we do in `eval_stability_allow_unstable` for
904-
// regular stability.
905-
feature == sym::rustc_private
906-
&& issue == NonZero::new(27812)
907-
&& tcx.sess.opts.unstable_opts.force_unstable_if_unmarked
908-
};
909-
// Even if the feature is enabled, we still need check_op to double-check
910-
// this if the callee is not safe to expose on stable.
911-
if !feature_enabled || !callee_safe_to_expose_on_stable {
912-
self.check_op(ops::FnCallUnstable {
913-
def_id: callee,
914-
feature,
915-
feature_enabled,
916-
safe_to_expose_on_stable: callee_safe_to_expose_on_stable,
917-
suggestion_span: self.crate_inject_span(),
918-
});
919-
}
920-
}
921-
}
927+
self.check_callee_stability(callee);
922928
}
923929

924930
// Forbid all `Drop` terminators unless the place being dropped is a local with no

compiler/rustc_const_eval/src/check_consts/mod.rs

+3-17
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ impl<'mir, 'tcx> ConstCx<'mir, 'tcx> {
5656
self.const_kind == Some(hir::ConstContext::ConstFn)
5757
&& (self.tcx.features().staged_api()
5858
|| self.tcx.sess.opts.unstable_opts.force_unstable_if_unmarked)
59-
&& is_safe_to_expose_on_stable_const_fn(self.tcx, self.def_id().to_def_id())
59+
&& is_fn_or_trait_safe_to_expose_on_stable(self.tcx, self.def_id().to_def_id())
6060
}
6161

6262
fn is_async(&self) -> bool {
@@ -84,28 +84,14 @@ pub fn rustc_allow_const_fn_unstable(
8484
attr::rustc_allow_const_fn_unstable(tcx.sess, attrs).any(|name| name == feature_gate)
8585
}
8686

87-
/// Returns `true` if the given `const fn` is "safe to expose on stable".
88-
///
89-
/// Panics if the given `DefId` does not refer to a `const fn`.
87+
/// Returns `true` if the given `def_id` (trait or function) is "safe to expose on stable".
9088
///
9189
/// This is relevant within a `staged_api` crate. Unlike with normal features, the use of unstable
9290
/// const features *recursively* taints the functions that use them. This is to avoid accidentally
9391
/// exposing e.g. the implementation of an unstable const intrinsic on stable. So we partition the
9492
/// world into two functions: those that are safe to expose on stable (and hence may not use
9593
/// unstable features, not even recursively), and those that are not.
96-
pub fn is_safe_to_expose_on_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
97-
// A default body in a `#[const_trait]` is not const-stable because const trait fns currently
98-
// cannot be const-stable. These functions can't be called from anything stable, so we shouldn't
99-
// restrict them to only call const-stable functions.
100-
if tcx.is_const_default_method(def_id) {
101-
// FIXME(const_trait_impl): we have to eventually allow some of these if these things can ever be stable.
102-
// They should probably behave like regular `const fn` for that...
103-
return false;
104-
}
105-
106-
// Const-stability is only relevant for `const fn`.
107-
assert!(tcx.is_const_fn(def_id));
108-
94+
pub fn is_fn_or_trait_safe_to_expose_on_stable(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
10995
match tcx.lookup_const_stability(def_id) {
11096
None => {
11197
// In a `staged_api` crate, we do enforce recursive const stability for all unmarked

compiler/rustc_const_eval/src/check_consts/ops.rs

+18-9
Original file line numberDiff line numberDiff line change
@@ -377,36 +377,45 @@ fn build_error_for_const_call<'tcx>(
377377
err
378378
}
379379

380-
/// A call to an `#[unstable]` const fn or `#[rustc_const_unstable]` function.
380+
/// A call to an `#[unstable]` const fn, `#[rustc_const_unstable]` function or trait.
381381
///
382-
/// Contains the name of the feature that would allow the use of this function.
382+
/// Contains the name of the feature that would allow the use of this function/trait.
383383
#[derive(Debug)]
384-
pub(crate) struct FnCallUnstable {
384+
pub(crate) struct CallUnstable {
385385
pub def_id: DefId,
386386
pub feature: Symbol,
387387
/// If this is true, then the feature is enabled, but we need to still check if it is safe to
388388
/// expose on stable.
389389
pub feature_enabled: bool,
390390
pub safe_to_expose_on_stable: bool,
391391
pub suggestion_span: Option<Span>,
392+
/// true if `def_id` is the function we are calling, false if `def_id` is an unstable trait.
393+
pub is_function_call: bool,
392394
}
393395

394-
impl<'tcx> NonConstOp<'tcx> for FnCallUnstable {
396+
impl<'tcx> NonConstOp<'tcx> for CallUnstable {
395397
fn status_in_item(&self, _ccx: &ConstCx<'_, 'tcx>) -> Status {
396398
Status::Unstable {
397399
gate: self.feature,
398400
gate_already_checked: self.feature_enabled,
399401
safe_to_expose_on_stable: self.safe_to_expose_on_stable,
400-
is_function_call: true,
402+
is_function_call: self.is_function_call,
401403
}
402404
}
403405

404406
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
405407
assert!(!self.feature_enabled);
406-
let mut err = ccx.dcx().create_err(errors::UnstableConstFn {
407-
span,
408-
def_path: ccx.tcx.def_path_str(self.def_id),
409-
});
408+
let mut err = if self.is_function_call {
409+
ccx.dcx().create_err(errors::UnstableConstFn {
410+
span,
411+
def_path: ccx.tcx.def_path_str(self.def_id),
412+
})
413+
} else {
414+
ccx.dcx().create_err(errors::UnstableConstTrait {
415+
span,
416+
def_path: ccx.tcx.def_path_str(self.def_id),
417+
})
418+
};
410419
// FIXME: make this translatable
411420
let msg = format!("add `#![feature({})]` to the crate attributes to enable", self.feature);
412421
#[allow(rustc::untranslatable_diagnostic)]

compiler/rustc_const_eval/src/errors.rs

+10-2
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@ pub(crate) struct UnstableConstFn {
121121
pub def_path: String,
122122
}
123123

124+
#[derive(Diagnostic)]
125+
#[diag(const_eval_unstable_const_trait)]
126+
pub(crate) struct UnstableConstTrait {
127+
#[primary_span]
128+
pub span: Span,
129+
pub def_path: String,
130+
}
131+
124132
#[derive(Diagnostic)]
125133
#[diag(const_eval_unstable_intrinsic)]
126134
pub(crate) struct UnstableIntrinsic {
@@ -139,9 +147,9 @@ pub(crate) struct UnstableIntrinsic {
139147
}
140148

141149
#[derive(Diagnostic)]
142-
#[diag(const_eval_unmarked_const_fn_exposed)]
150+
#[diag(const_eval_unmarked_const_item_exposed)]
143151
#[help]
144-
pub(crate) struct UnmarkedConstFnExposed {
152+
pub(crate) struct UnmarkedConstItemExposed {
145153
#[primary_span]
146154
pub span: Span,
147155
pub def_path: String,

tests/ui/traits/const-traits/staged-api-user-crate.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ fn non_const_context() {
1111
const fn stable_const_context() {
1212
Unstable::func();
1313
//~^ ERROR cannot call conditionally-const associated function `<staged_api::Unstable as staged_api::MyTrait>::func` in constant functions
14+
//~| ERROR `staged_api::MyTrait` is not yet stable as a const trait
1415
}
1516

1617
fn main() {}

tests/ui/traits/const-traits/staged-api-user-crate.stderr

+12-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ LL | Unstable::func();
99
= help: add `#![feature(const_trait_impl)]` to the crate attributes to enable
1010
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
1111

12-
error: aborting due to 1 previous error
12+
error: `staged_api::MyTrait` is not yet stable as a const trait
13+
--> $DIR/staged-api-user-crate.rs:12:5
14+
|
15+
LL | Unstable::func();
16+
| ^^^^^^^^^^^^^^^^
17+
|
18+
help: add `#![feature(unstable)]` to the crate attributes to enable
19+
|
20+
LL + #![feature(unstable)]
21+
|
22+
23+
error: aborting due to 2 previous errors
1324

1425
For more information about this error, try `rustc --explain E0658`.

0 commit comments

Comments
 (0)