Skip to content

Commit d60bc65

Browse files
committed
Implement trait const stability
1 parent 78af7da commit d60bc65

33 files changed

+799
-164
lines changed

compiler/rustc_const_eval/messages.ftl

-8
Original file line numberDiff line numberDiff line change
@@ -411,14 +411,6 @@ const_eval_unreachable_unwind =
411411
412412
const_eval_unsized_local = unsized locals are not supported
413413
const_eval_unstable_const_fn = `{$def_path}` is not yet stable as a const fn
414-
const_eval_unstable_in_stable_exposed =
415-
const function that might be (indirectly) exposed to stable cannot use `#[feature({$gate})]`
416-
.is_function_call = mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
417-
.unstable_sugg = if the {$is_function_call2 ->
418-
[true] caller
419-
*[false] function
420-
} is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
421-
.bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
422414
423415
const_eval_unstable_intrinsic = `{$name}` is not yet stable as a const intrinsic
424416
.help = add `#![feature({$feature})]` to the crate attributes to enable

compiler/rustc_const_eval/src/check_consts/check.rs

+15-22
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ use rustc_hir::def_id::DefId;
1212
use rustc_hir::{self as hir, LangItem};
1313
use rustc_index::bit_set::BitSet;
1414
use rustc_infer::infer::TyCtxtInferExt;
15+
use rustc_middle::middle::stability::emit_const_unstable_in_const_stable_exposed_error;
1516
use rustc_middle::mir::visit::Visitor;
1617
use rustc_middle::mir::*;
1718
use rustc_middle::span_bug;
1819
use rustc_middle::ty::adjustment::PointerCoercion;
1920
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
2021
use rustc_mir_dataflow::Analysis;
2122
use rustc_mir_dataflow::impls::{MaybeStorageLive, always_storage_live_locals};
22-
use rustc_span::{Span, Symbol, sym};
23+
use rustc_span::{Span, sym};
2324
use rustc_trait_selection::traits::{
2425
Obligation, ObligationCause, ObligationCauseCode, ObligationCtxt,
2526
};
@@ -287,9 +288,15 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
287288
// if this function wants to be safe-to-expose-on-stable.
288289
if !safe_to_expose_on_stable
289290
&& self.enforce_recursive_const_stability()
290-
&& !super::rustc_allow_const_fn_unstable(self.tcx, self.def_id(), gate)
291+
&& !self.tcx.rustc_allow_const_fn_unstable(self.def_id(), gate)
291292
{
292-
emit_unstable_in_stable_exposed_error(self.ccx, span, gate, is_function_call);
293+
emit_const_unstable_in_const_stable_exposed_error(
294+
self.tcx,
295+
self.def_id(),
296+
span,
297+
gate,
298+
is_function_call,
299+
);
293300
}
294301

295302
return;
@@ -709,8 +716,11 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
709716
if trait_is_const {
710717
// Trait calls are always conditionally-const.
711718
self.check_op(ops::ConditionallyConstCall { callee, args: fn_args });
712-
// FIXME(const_trait_impl): do a more fine-grained check whether this
713-
// particular trait can be const-stably called.
719+
self.tcx.enforce_trait_const_stability(
720+
trait_did,
721+
*fn_span,
722+
Some(self.def_id()),
723+
);
714724
} else {
715725
// Not even a const trait.
716726
self.check_op(ops::FnCallNonConst {
@@ -956,20 +966,3 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
956966
fn is_int_bool_float_or_char(ty: Ty<'_>) -> bool {
957967
ty.is_bool() || ty.is_integral() || ty.is_char() || ty.is_floating_point()
958968
}
959-
960-
fn emit_unstable_in_stable_exposed_error(
961-
ccx: &ConstCx<'_, '_>,
962-
span: Span,
963-
gate: Symbol,
964-
is_function_call: bool,
965-
) -> ErrorGuaranteed {
966-
let attr_span = ccx.tcx.def_span(ccx.def_id()).shrink_to_lo();
967-
968-
ccx.dcx().emit_err(errors::UnstableInStableExposed {
969-
gate: gate.to_string(),
970-
span,
971-
attr_span,
972-
is_function_call,
973-
is_function_call2: is_function_call,
974-
})
975-
}

compiler/rustc_const_eval/src/check_consts/mod.rs

+1-11
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
//! it finds operations that are invalid in a certain context.
66
77
use rustc_errors::DiagCtxtHandle;
8+
use rustc_hir as hir;
89
use rustc_hir::def_id::{DefId, LocalDefId};
910
use rustc_middle::ty::{self, PolyFnSig, TyCtxt};
1011
use rustc_middle::{bug, mir};
11-
use rustc_span::Symbol;
12-
use {rustc_attr_parsing as attr, rustc_hir as hir};
1312

1413
pub use self::qualifs::Qualif;
1514

@@ -75,15 +74,6 @@ impl<'mir, 'tcx> ConstCx<'mir, 'tcx> {
7574
}
7675
}
7776

78-
pub fn rustc_allow_const_fn_unstable(
79-
tcx: TyCtxt<'_>,
80-
def_id: LocalDefId,
81-
feature_gate: Symbol,
82-
) -> bool {
83-
let attrs = tcx.hir().attrs(tcx.local_def_id_to_hir_id(def_id));
84-
attr::rustc_allow_const_fn_unstable(tcx.sess, attrs).any(|name| name == feature_gate)
85-
}
86-
8777
/// Returns `true` if the given `const fn` is "safe to expose on stable".
8878
///
8979
/// Panics if the given `DefId` does not refer to a `const fn`.

compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,14 @@ use tracing::trace;
66

77
use super::ConstCx;
88
use crate::check_consts::check::Checker;
9-
use crate::check_consts::rustc_allow_const_fn_unstable;
109

1110
/// Returns `true` if we should use the more precise live drop checker that runs after drop
1211
/// elaboration.
1312
pub fn checking_enabled(ccx: &ConstCx<'_, '_>) -> bool {
1413
// Const-stable functions must always use the stable live drop checker...
1514
if ccx.enforce_recursive_const_stability() {
1615
// ...except if they have the feature flag set via `rustc_allow_const_fn_unstable`.
17-
return rustc_allow_const_fn_unstable(
18-
ccx.tcx,
19-
ccx.body.source.def_id().expect_local(),
20-
sym::const_precise_live_drops,
21-
);
16+
return ccx.tcx.rustc_allow_const_fn_unstable(ccx.def_id(), sym::const_precise_live_drops);
2217
}
2318

2419
ccx.tcx.features().const_precise_live_drops()

compiler/rustc_const_eval/src/errors.rs

-23
Original file line numberDiff line numberDiff line change
@@ -43,29 +43,6 @@ pub(crate) struct MutablePtrInFinal {
4343
pub kind: InternKind,
4444
}
4545

46-
#[derive(Diagnostic)]
47-
#[diag(const_eval_unstable_in_stable_exposed)]
48-
pub(crate) struct UnstableInStableExposed {
49-
pub gate: String,
50-
#[primary_span]
51-
pub span: Span,
52-
#[help(const_eval_is_function_call)]
53-
pub is_function_call: bool,
54-
/// Need to duplicate the field so that fluent also provides it as a variable...
55-
pub is_function_call2: bool,
56-
#[suggestion(
57-
const_eval_unstable_sugg,
58-
code = "#[rustc_const_unstable(feature = \"...\", issue = \"...\")]\n",
59-
applicability = "has-placeholders"
60-
)]
61-
#[suggestion(
62-
const_eval_bypass_sugg,
63-
code = "#[rustc_allow_const_fn_unstable({gate})]\n",
64-
applicability = "has-placeholders"
65-
)]
66-
pub attr_span: Span,
67-
}
68-
6946
#[derive(Diagnostic)]
7047
#[diag(const_eval_thread_local_access, code = E0625)]
7148
pub(crate) struct ThreadLocalAccessErr {

compiler/rustc_hir_analysis/src/collect.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,7 @@ fn check_impl_constness(
16121612

16131613
let Some(trait_def_id) = hir_trait_ref.trait_def_id() else { return };
16141614
if tcx.is_const_trait(trait_def_id) {
1615+
tcx.enforce_trait_const_stability(trait_def_id, hir_trait_ref.path.span, None);
16151616
return;
16161617
}
16171618

compiler/rustc_hir_analysis/src/collect/predicates_of.rs

+20-10
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,14 @@ fn gather_explicit_predicates_of(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::Gen
334334
debug!(?predicates);
335335
}
336336

337+
for (clause, span) in predicates.iter().copied() {
338+
// enforce trait const stability for `const Tr` bounds.
339+
let ty::ClauseKind::HostEffect(pred) = clause.kind().skip_binder() else {
340+
continue;
341+
};
342+
tcx.enforce_trait_const_stability(pred.trait_ref.def_id, span, Some(def_id));
343+
}
344+
337345
ty::GenericPredicates {
338346
parent: generics.parent,
339347
predicates: tcx.arena.alloc_from_iter(predicates),
@@ -1046,16 +1054,18 @@ pub(super) fn const_conditions<'tcx>(
10461054
ty::ConstConditions {
10471055
parent: has_parent.then(|| tcx.local_parent(def_id).to_def_id()),
10481056
predicates: tcx.arena.alloc_from_iter(bounds.clauses().map(|(clause, span)| {
1049-
(
1050-
clause.kind().map_bound(|clause| match clause {
1051-
ty::ClauseKind::HostEffect(ty::HostEffectPredicate {
1052-
trait_ref,
1053-
constness: ty::BoundConstness::Maybe,
1054-
}) => trait_ref,
1055-
_ => bug!("converted {clause:?}"),
1056-
}),
1057-
span,
1058-
)
1057+
let poly_trait_ref = clause.kind().map_bound(|clause| match clause {
1058+
ty::ClauseKind::HostEffect(ty::HostEffectPredicate {
1059+
trait_ref,
1060+
constness: ty::BoundConstness::Maybe,
1061+
}) => trait_ref,
1062+
_ => bug!("converted {clause:?}"),
1063+
});
1064+
1065+
// check the const-stability of `Tr` for `~const Tr` bounds
1066+
tcx.enforce_trait_const_stability(poly_trait_ref.def_id(), span, Some(def_id));
1067+
1068+
(poly_trait_ref, span)
10591069
})),
10601070
}
10611071
}

compiler/rustc_middle/messages.ftl

+11
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,15 @@ middle_const_eval_non_int =
5050
middle_const_not_used_in_type_alias =
5151
const parameter `{$ct}` is part of concrete type but not used in parameter list for the `impl Trait` type alias
5252
53+
middle_const_unstable_in_const_stable_exposed =
54+
const function that might be (indirectly) exposed to stable cannot use `#[feature({$gate})]`
55+
.is_function_call = mark the callee as `#[rustc_const_stable_indirect]` if it does not itself require any unsafe features
56+
.unstable_sugg = if the {$is_function_call2 ->
57+
[true] caller
58+
*[false] function
59+
} is not (yet) meant to be exposed to stable, add `#[rustc_const_unstable]` (this is what you probably want to do)
60+
.bypass_sugg = otherwise, as a last resort `#[rustc_allow_const_fn_unstable]` can be used to bypass stability checks (this requires team approval)
61+
5362
middle_cycle =
5463
a cycle occurred during layout computation
5564
@@ -105,6 +114,8 @@ middle_type_length_limit = reached the type-length limit while instantiating `{$
105114
middle_unknown_layout =
106115
the type `{$ty}` has an unknown layout
107116
117+
middle_unstable_const_trait = `{$def_path}` is not yet stable as a const trait
118+
108119
middle_values_too_big =
109120
values of the type `{$ty}` are too big for the target architecture
110121
middle_written_to_path = the full type name has been written to '{$path}'

compiler/rustc_middle/src/error.rs

+31
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,34 @@ pub struct TypeLengthLimit {
171171
pub path: PathBuf,
172172
pub type_length: usize,
173173
}
174+
175+
#[derive(Diagnostic)]
176+
#[diag(middle_unstable_const_trait)]
177+
pub struct UnstableConstTrait {
178+
#[primary_span]
179+
pub span: Span,
180+
pub def_path: String,
181+
}
182+
183+
#[derive(Diagnostic)]
184+
#[diag(middle_const_unstable_in_const_stable_exposed)]
185+
pub struct ConstUnstableInConstStableExposed {
186+
pub gate: String,
187+
#[primary_span]
188+
pub span: Span,
189+
#[help(middle_is_function_call)]
190+
pub is_function_call: bool,
191+
/// Need to duplicate the field so that fluent also provides it as a variable...
192+
pub is_function_call2: bool,
193+
#[suggestion(
194+
middle_unstable_sugg,
195+
code = "#[rustc_const_unstable(feature = \"...\", issue = \"...\")]\n",
196+
applicability = "has-placeholders"
197+
)]
198+
#[suggestion(
199+
middle_bypass_sugg,
200+
code = "#[rustc_allow_const_fn_unstable({gate})]\n",
201+
applicability = "has-placeholders"
202+
)]
203+
pub attr_span: Span,
204+
}

compiler/rustc_middle/src/middle/stability.rs

+91-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rustc_attr_parsing::{
1010
use rustc_data_structures::unord::UnordMap;
1111
use rustc_errors::{Applicability, Diag, EmissionGuarantee};
1212
use rustc_feature::GateIssue;
13+
use rustc_hir::def::DefKind;
1314
use rustc_hir::def_id::{DefId, LocalDefId, LocalDefIdMap};
1415
use rustc_hir::{self as hir, HirId};
1516
use rustc_macros::{Decodable, Encodable, HashStable, Subdiagnostic};
@@ -18,7 +19,7 @@ use rustc_session::Session;
1819
use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE};
1920
use rustc_session::lint::{BuiltinLintDiag, DeprecatedSinceKind, Level, Lint, LintBuffer};
2021
use rustc_session::parse::feature_err_issue;
21-
use rustc_span::{Span, Symbol, sym};
22+
use rustc_span::{ErrorGuaranteed, Span, Symbol, sym};
2223
use tracing::debug;
2324

2425
pub use self::StabilityLevel::*;
@@ -597,4 +598,93 @@ impl<'tcx> TyCtxt<'tcx> {
597598
pub fn lookup_deprecation(self, id: DefId) -> Option<Deprecation> {
598599
self.lookup_deprecation_entry(id).map(|depr| depr.attr)
599600
}
601+
602+
/// Returns true if `def_id` has an attribute that allows usage of the const unstable feature `feature_gate`.
603+
pub fn rustc_allow_const_fn_unstable(self, def_id: LocalDefId, feature_gate: Symbol) -> bool {
604+
let attrs = self.hir().attrs(self.local_def_id_to_hir_id(def_id));
605+
attr::rustc_allow_const_fn_unstable(self.sess, attrs).any(|name| name == feature_gate)
606+
}
607+
608+
pub fn enforce_trait_const_stability(
609+
self,
610+
trait_def_id: DefId,
611+
span: Span,
612+
parent_def: Option<LocalDefId>,
613+
) {
614+
match self.lookup_const_stability(trait_def_id) {
615+
Some(ConstStability {
616+
level: attr::StabilityLevel::Unstable { implied_by: implied_feature, .. },
617+
feature,
618+
..
619+
}) => {
620+
let unstable_feature_allowed = span.allows_unstable(feature)
621+
|| implied_feature.is_some_and(|f| span.allows_unstable(f));
622+
623+
let feature_enabled = trait_def_id.is_local()
624+
|| self.features().enabled(feature)
625+
|| implied_feature.is_some_and(|f| self.features().enabled(f));
626+
627+
if !unstable_feature_allowed && !feature_enabled {
628+
let mut diag = self.dcx().create_err(crate::error::UnstableConstTrait {
629+
span,
630+
def_path: self.def_path_str(trait_def_id),
631+
});
632+
self.disabled_nightly_features(&mut diag, None, [(String::new(), feature)]);
633+
diag.emit();
634+
} else if let Some(parent) = parent_def {
635+
// user either has enabled the feature or the unstable feature is allowed inside a macro,
636+
// but if we consider the item we're in to be const stable, we should error as const stable
637+
// items cannot use unstable features.
638+
let is_stable = matches!(
639+
self.def_kind(parent),
640+
DefKind::AssocFn | DefKind::Fn | DefKind::Trait
641+
) && match self.lookup_const_stability(parent) {
642+
None => {
643+
// `const fn`s without const stability attributes in a `staged_api` crate
644+
// are implicitly stable.
645+
self.features().staged_api()
646+
}
647+
Some(stab) => {
648+
// an explicitly stable `const fn`, or an unstable `const fn` that claims to not use any
649+
// other unstably-const features with `const_stable_indirect`
650+
stab.is_const_stable() || stab.const_stable_indirect
651+
}
652+
};
653+
654+
// if our parent function is unstable, no need to error
655+
if !is_stable {
656+
return;
657+
}
658+
659+
// if the feature is explicitly allowed, don't error
660+
if self.rustc_allow_const_fn_unstable(parent, feature) {
661+
return;
662+
}
663+
664+
emit_const_unstable_in_const_stable_exposed_error(
665+
self, parent, span, feature, false,
666+
);
667+
}
668+
}
669+
_ => {}
670+
}
671+
}
672+
}
673+
674+
pub fn emit_const_unstable_in_const_stable_exposed_error(
675+
tcx: TyCtxt<'_>,
676+
def_id: LocalDefId,
677+
span: Span,
678+
gate: Symbol,
679+
is_function_call: bool,
680+
) -> ErrorGuaranteed {
681+
let attr_span = tcx.def_span(def_id).shrink_to_lo();
682+
683+
tcx.dcx().emit_err(crate::error::ConstUnstableInConstStableExposed {
684+
gate: gate.to_string(),
685+
span,
686+
attr_span,
687+
is_function_call,
688+
is_function_call2: is_function_call,
689+
})
600690
}

0 commit comments

Comments
 (0)