Skip to content

Commit 7081449

Browse files
committed
Implement MIR const trait stability checks
1 parent f940188 commit 7081449

File tree

10 files changed

+233
-122
lines changed

10 files changed

+233
-122
lines changed

compiler/rustc_const_eval/messages.ftl

+3-2
Original file line numberDiff line numberDiff line change
@@ -403,8 +403,8 @@ 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
407-
.help = either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
406+
const_eval_unmarked_const_item_exposed = `{$def_path}` cannot be (indirectly) exposed to stable
407+
.help = either mark the item 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)
410410
@@ -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 unsafe features

compiler/rustc_const_eval/src/check_consts/check.rs

+86-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> =
@@ -694,6 +695,87 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
694695
}
695696
};
696697

698+
let check_stability = |this: &mut Self, def_id| {
699+
match tcx.lookup_const_stability(def_id) {
700+
Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => {
701+
// All good.
702+
}
703+
None => {
704+
// This doesn't need a separate const-stability check -- const-stability equals
705+
// regular stability, and regular stability is checked separately.
706+
// However, we *do* have to worry about *recursive* const stability.
707+
if this.enforce_recursive_const_stability()
708+
&& !is_fn_or_trait_safe_to_expose_on_stable(tcx, def_id)
709+
{
710+
this.dcx().emit_err(errors::UnmarkedConstItemExposed {
711+
span: this.span,
712+
def_path: this.tcx.def_path_str(def_id),
713+
});
714+
}
715+
}
716+
Some(ConstStability {
717+
level: StabilityLevel::Unstable { implied_by: implied_feature, issue, .. },
718+
feature,
719+
..
720+
}) => {
721+
// An unstable const fn/trait with a feature gate.
722+
let callee_safe_to_expose_on_stable =
723+
is_fn_or_trait_safe_to_expose_on_stable(tcx, def_id);
724+
725+
// We only honor `span.allows_unstable` aka `#[allow_internal_unstable]` if
726+
// the callee is safe to expose, to avoid bypassing recursive stability.
727+
// This is not ideal since it means the user sees an error, not the macro
728+
// author, but that's also the case if one forgets to set
729+
// `#[allow_internal_unstable]` in the first place. Note that this cannot be
730+
// integrated in the check below since we want to enforce
731+
// `callee_safe_to_expose_on_stable` even if
732+
// `!self.enforce_recursive_const_stability()`.
733+
if (this.span.allows_unstable(feature)
734+
|| implied_feature.is_some_and(|f| this.span.allows_unstable(f)))
735+
&& callee_safe_to_expose_on_stable
736+
{
737+
return;
738+
}
739+
740+
// We can't use `check_op` to check whether the feature is enabled because
741+
// the logic is a bit different than elsewhere: local functions don't need
742+
// the feature gate, and there might be an "implied" gate that also suffices
743+
// to allow this.
744+
let feature_enabled = def_id.is_local()
745+
|| tcx.features().enabled(feature)
746+
|| implied_feature.is_some_and(|f| tcx.features().enabled(f))
747+
|| {
748+
// When we're compiling the compiler itself we may pull in
749+
// crates from crates.io, but those crates may depend on other
750+
// crates also pulled in from crates.io. We want to ideally be
751+
// able to compile everything without requiring upstream
752+
// modifications, so in the case that this looks like a
753+
// `rustc_private` crate (e.g., a compiler crate) and we also have
754+
// the `-Z force-unstable-if-unmarked` flag present (we're
755+
// compiling a compiler crate), then let this missing feature
756+
// annotation slide.
757+
// This matches what we do in `eval_stability_allow_unstable` for
758+
// regular stability.
759+
feature == sym::rustc_private
760+
&& issue == NonZero::new(27812)
761+
&& tcx.sess.opts.unstable_opts.force_unstable_if_unmarked
762+
};
763+
// Even if the feature is enabled, we still need check_op to double-check
764+
// this if the callee is not safe to expose on stable.
765+
if !feature_enabled || !callee_safe_to_expose_on_stable {
766+
this.check_op(ops::CallUnstable {
767+
def_id,
768+
feature,
769+
feature_enabled,
770+
safe_to_expose_on_stable: callee_safe_to_expose_on_stable,
771+
suggestion_span: this.crate_inject_span(),
772+
is_function_call: tcx.def_kind(def_id) != DefKind::Trait,
773+
});
774+
}
775+
}
776+
}
777+
};
778+
697779
let has_const_conditions =
698780
self.revalidate_conditional_constness(callee, fn_args, *fn_span);
699781

@@ -716,8 +798,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
716798
span: *fn_span,
717799
call_source,
718800
});
719-
// FIXME(const_trait_impl): do a more fine-grained check whether this
720-
// particular trait can be const-stably called.
801+
check_stability(self, trait_did);
721802
} else {
722803
// Not even a const trait.
723804
self.check_op(ops::FnCallNonConst {
@@ -793,7 +874,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
793874
// fallback body is safe to expose on stable.
794875
let is_const_stable = intrinsic.const_stable
795876
|| (!intrinsic.must_be_overridden
796-
&& is_safe_to_expose_on_stable_const_fn(tcx, callee));
877+
&& is_fn_or_trait_safe_to_expose_on_stable(tcx, callee));
797878
match tcx.lookup_const_stability(callee) {
798879
None => {
799880
// This doesn't need a separate const-stability check -- const-stability equals
@@ -842,83 +923,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
842923
}
843924

844925
// 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-
}
926+
check_stability(self, callee)
922927
}
923928

924929
// 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/consts/min_const_fn/recursive_const_stab_unmarked_crate_imports.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ error: `just_a_fn` cannot be (indirectly) exposed to stable
2222
LL | unmarked_const_fn_crate::just_a_fn();
2323
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2424
|
25-
= help: either mark the callee as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
25+
= help: either mark the item as `#[rustc_const_stable_indirect]`, or the caller as `#[rustc_const_unstable]`
2626

2727
error: aborting due to 2 previous errors
2828

0 commit comments

Comments
 (0)