Skip to content

Commit 9c55da1

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

33 files changed

+786
-161
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

+5-21
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,9 @@ 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(self.tcx, self.def_id(), span, gate, is_function_call);
293294
}
294295

295296
return;
@@ -709,8 +710,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
709710
if trait_is_const {
710711
// Trait calls are always conditionally-const.
711712
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.
713+
self.tcx.enforce_trait_const_stability(trait_did, *fn_span, Some(self.def_id()));
714714
} else {
715715
// Not even a const trait.
716716
self.check_op(ops::FnCallNonConst {
@@ -957,19 +957,3 @@ fn is_int_bool_float_or_char(ty: Ty<'_>) -> bool {
957957
ty.is_bool() || ty.is_integral() || ty.is_char() || ty.is_floating_point()
958958
}
959959

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
@@ -8,8 +8,7 @@ use rustc_errors::DiagCtxtHandle;
88
use rustc_hir::def_id::{DefId, LocalDefId};
99
use rustc_middle::ty::{self, PolyFnSig, TyCtxt};
1010
use rustc_middle::{bug, mir};
11-
use rustc_span::Symbol;
12-
use {rustc_attr_parsing as attr, rustc_hir as hir};
11+
use 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

+2-4
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,15 @@ 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(),
16+
return ccx.tcx.rustc_allow_const_fn_unstable(
17+
ccx.def_id(),
2018
sym::const_precise_live_drops,
2119
);
2220
}

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

+18-10
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,12 @@ 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 { continue; };
340+
tcx.enforce_trait_const_stability(pred.trait_ref.def_id, span, Some(def_id));
341+
}
342+
337343
ty::GenericPredicates {
338344
parent: generics.parent,
339345
predicates: tcx.arena.alloc_from_iter(predicates),
@@ -1046,16 +1052,18 @@ pub(super) fn const_conditions<'tcx>(
10461052
ty::ConstConditions {
10471053
parent: has_parent.then(|| tcx.local_parent(def_id).to_def_id()),
10481054
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-
)
1055+
let poly_trait_ref = clause.kind().map_bound(|clause| match clause {
1056+
ty::ClauseKind::HostEffect(ty::HostEffectPredicate {
1057+
trait_ref,
1058+
constness: ty::BoundConstness::Maybe,
1059+
}) => trait_ref,
1060+
_ => bug!("converted {clause:?}"),
1061+
});
1062+
1063+
// check the const-stability of `Tr` for `~const Tr` bounds
1064+
tcx.enforce_trait_const_stability(poly_trait_ref.def_id(), span, Some(def_id));
1065+
1066+
(poly_trait_ref, span)
10591067
})),
10601068
}
10611069
}

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

+32
Original file line numberDiff line numberDiff line change
@@ -171,3 +171,35 @@ 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+
}
205+

compiler/rustc_middle/src/middle/stability.rs

+87-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::{sym, ErrorGuaranteed, Span, Symbol};
2223
use tracing::debug;
2324

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

compiler/rustc_passes/src/stability.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -593,9 +593,11 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> {
593593
}
594594

595595
fn check_missing_const_stability(&self, def_id: LocalDefId, span: Span) {
596-
let is_const = self.tcx.is_const_fn(def_id.to_def_id());
596+
let is_const = self.tcx.is_const_fn(def_id.to_def_id())
597+
|| (self.tcx.def_kind(def_id.to_def_id()) == DefKind::Trait
598+
&& self.tcx.is_const_trait(def_id.to_def_id()));
597599

598-
// Reachable const fn must have a stability attribute.
600+
// Reachable const fn/trait must have a stability attribute.
599601
if is_const
600602
&& self.effective_visibilities.is_reachable(def_id)
601603
&& self.tcx.lookup_const_stability(def_id).is_none()

library/core/src/marker.rs

+1
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,7 @@ marker_impls! {
960960
/// This should be used for `~const` bounds,
961961
/// as non-const bounds will always hold for every type.
962962
#[unstable(feature = "const_destruct", issue = "133214")]
963+
#[rustc_const_unstable(feature = "const_destruct", issue = "133214")]
963964
#[lang = "destruct"]
964965
#[rustc_on_unimplemented(message = "can't drop `{Self}`", append_const_msg)]
965966
#[cfg_attr(bootstrap, rustc_deny_explicit_impl(implement_via_object = false))]

library/core/src/ops/arith.rs

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
/// ```
6666
#[lang = "add"]
6767
#[stable(feature = "rust1", since = "1.0.0")]
68+
#[rustc_const_unstable(feature = "const_ops", issue = "90080")]
6869
#[rustc_on_unimplemented(
6970
on(all(_Self = "{integer}", Rhs = "{float}"), message = "cannot add a float to an integer",),
7071
on(all(_Self = "{float}", Rhs = "{integer}"), message = "cannot add an integer to a float",),

0 commit comments

Comments
 (0)