Skip to content

Commit d04c00d

Browse files
committed
Add a diagnostic attribute for special casing const bound errors for non-const impls
1 parent 0189eeb commit d04c00d

File tree

22 files changed

+240
-25
lines changed

22 files changed

+240
-25
lines changed

compiler/rustc_feature/src/builtin_attrs.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1567,9 +1567,10 @@ pub static BUILTIN_ATTRIBUTE_MAP: LazyLock<FxHashMap<Symbol, &BuiltinAttribute>>
15671567
map
15681568
});
15691569

1570-
pub fn is_stable_diagnostic_attribute(sym: Symbol, _features: &Features) -> bool {
1570+
pub fn is_stable_diagnostic_attribute(sym: Symbol, features: &Features) -> bool {
15711571
match sym {
15721572
sym::on_unimplemented | sym::do_not_recommend => true,
1573+
sym::on_const => features.diagnostic_on_const(),
15731574
_ => false,
15741575
}
15751576
}

compiler/rustc_feature/src/unstable.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,8 @@ declare_features! (
482482
(incomplete, deref_patterns, "1.79.0", Some(87121)),
483483
/// Allows deriving the From trait on single-field structs.
484484
(unstable, derive_from, "1.91.0", Some(144889)),
485+
/// Allows giving non-const impls custom diagnostic messages if attempted to be used as const
486+
(unstable, diagnostic_on_const, "CURRENT_RUSTC_VERSION", Some(143874)),
485487
/// Allows `#[doc(cfg(...))]`.
486488
(unstable, doc_cfg, "1.21.0", Some(43781)),
487489
/// Allows `#[doc(masked)]`.

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -806,6 +806,7 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(),
806806
tcx.ensure_ok().type_of(def_id);
807807
tcx.ensure_ok().predicates_of(def_id);
808808
tcx.ensure_ok().associated_items(def_id);
809+
check_diagnostic_attrs(tcx, def_id);
809810
if of_trait {
810811
let impl_trait_header = tcx.impl_trait_header(def_id);
811812
res = res.and(
@@ -828,7 +829,7 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(),
828829
tcx.ensure_ok().predicates_of(def_id);
829830
tcx.ensure_ok().associated_items(def_id);
830831
let assoc_items = tcx.associated_items(def_id);
831-
check_on_unimplemented(tcx, def_id);
832+
check_diagnostic_attrs(tcx, def_id);
832833

833834
for &assoc_item in assoc_items.in_definition_order() {
834835
match assoc_item.kind {
@@ -1078,7 +1079,7 @@ pub(crate) fn check_item_type(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(),
10781079
})
10791080
}
10801081

1081-
pub(super) fn check_on_unimplemented(tcx: TyCtxt<'_>, def_id: LocalDefId) {
1082+
pub(super) fn check_diagnostic_attrs(tcx: TyCtxt<'_>, def_id: LocalDefId) {
10821083
// an error would be reported if this fails.
10831084
let _ = OnUnimplementedDirective::of_item(tcx, def_id.to_def_id());
10841085
}

compiler/rustc_passes/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ passes_deprecated_annotation_has_no_effect =
9696
passes_deprecated_attribute =
9797
deprecated attribute must be paired with either stable or unstable attribute
9898
99+
passes_diagnostic_diagnostic_on_const_only_for_trait_impls =
100+
`#[diagnostic::on_const]` can only be applied to trait impls
101+
99102
passes_diagnostic_diagnostic_on_unimplemented_only_for_traits =
100103
`#[diagnostic::on_unimplemented]` can only be applied to trait definitions
101104

compiler/rustc_passes/src/check_attr.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ use crate::{errors, fluent_generated as fluent};
5555
#[diag(passes_diagnostic_diagnostic_on_unimplemented_only_for_traits)]
5656
struct DiagnosticOnUnimplementedOnlyForTraits;
5757

58+
#[derive(LintDiagnostic)]
59+
#[diag(passes_diagnostic_diagnostic_on_const_only_for_trait_impls)]
60+
struct DiagnosticOnConstOnlyForTraitImpls;
61+
5862
fn target_from_impl_item<'tcx>(tcx: TyCtxt<'tcx>, impl_item: &hir::ImplItem<'_>) -> Target {
5963
match impl_item.kind {
6064
hir::ImplItemKind::Const(..) => Target::AssocConst,
@@ -296,6 +300,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
296300
[sym::diagnostic, sym::on_unimplemented, ..] => {
297301
self.check_diagnostic_on_unimplemented(attr.span(), hir_id, target)
298302
}
303+
[sym::diagnostic, sym::on_const, ..] => {
304+
self.check_diagnostic_on_const(attr.span(), hir_id, target)
305+
}
299306
[sym::thread_local, ..] => self.check_thread_local(attr, span, target),
300307
[sym::doc, ..] => self.check_doc_attrs(
301308
attr,
@@ -519,6 +526,18 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
519526
}
520527
}
521528

529+
/// Checks if `#[diagnostic::on_const]` is applied to a trait impl
530+
fn check_diagnostic_on_const(&self, attr_span: Span, hir_id: HirId, target: Target) {
531+
if !matches!(target, Target::Impl { of_trait: true }) {
532+
self.tcx.emit_node_span_lint(
533+
MISPLACED_DIAGNOSTIC_ATTRIBUTES,
534+
hir_id,
535+
attr_span,
536+
DiagnosticOnConstOnlyForTraitImpls,
537+
);
538+
}
539+
}
540+
522541
/// Checks if an `#[inline]` is applied to a function or a closure.
523542
fn check_inline(&self, hir_id: HirId, attr_span: Span, kind: &InlineAttr, target: Target) {
524543
match target {

compiler/rustc_resolve/src/macros.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -687,10 +687,11 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
687687
if res == Res::NonMacroAttr(NonMacroAttrKind::Tool)
688688
&& let [namespace, attribute, ..] = &*path.segments
689689
&& namespace.ident.name == sym::diagnostic
690-
&& ![sym::on_unimplemented, sym::do_not_recommend].contains(&attribute.ident.name)
690+
&& ![sym::on_unimplemented, sym::do_not_recommend, sym::on_const]
691+
.contains(&attribute.ident.name)
691692
{
692693
let typo_name = find_best_match_for_name(
693-
&[sym::on_unimplemented, sym::do_not_recommend],
694+
&[sym::on_unimplemented, sym::do_not_recommend, sym::on_const],
694695
attribute.ident.name,
695696
Some(5),
696697
);

compiler/rustc_span/src/symbol.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,7 @@ symbols! {
872872
destructuring_assignment,
873873
diagnostic,
874874
diagnostic_namespace,
875+
diagnostic_on_const,
875876
dialect,
876877
direct,
877878
discriminant_kind,
@@ -1587,6 +1588,7 @@ symbols! {
15871588
old_name,
15881589
omit_gdb_pretty_printer_section,
15891590
on,
1591+
on_const,
15901592
on_unimplemented,
15911593
opaque,
15921594
opaque_module_name_placeholder: "<opaque>",

compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ use super::{
4242
};
4343
use crate::error_reporting::TypeErrCtxt;
4444
use crate::error_reporting::infer::TyCategory;
45+
use crate::error_reporting::traits::on_unimplemented::OnUnimplementedDirective;
4546
use crate::error_reporting::traits::report_dyn_incompatibility;
4647
use crate::errors::{ClosureFnMutLabel, ClosureFnOnceLabel, ClosureKindMismatch, CoroClosureNotFn};
4748
use crate::infer::{self, InferCtxt, InferCtxtExt as _};
@@ -586,7 +587,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
586587
}
587588

588589
ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(predicate)) => {
589-
self.report_host_effect_error(bound_predicate.rebind(predicate), obligation.param_env, span)
590+
self.report_host_effect_error(bound_predicate.rebind(predicate), &obligation, span)
590591
}
591592

592593
ty::PredicateKind::Subtype(predicate) => {
@@ -807,20 +808,18 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
807808
fn report_host_effect_error(
808809
&self,
809810
predicate: ty::Binder<'tcx, ty::HostEffectPredicate<'tcx>>,
810-
param_env: ty::ParamEnv<'tcx>,
811+
main_obligation: &PredicateObligation<'tcx>,
811812
span: Span,
812813
) -> Diag<'a> {
813814
// FIXME(const_trait_impl): We should recompute the predicate with `[const]`
814815
// if it's `const`, and if it holds, explain that this bound only
815-
// *conditionally* holds. If that fails, we should also do selection
816-
// to drill this down to an impl or built-in source, so we can
817-
// point at it and explain that while the trait *is* implemented,
818-
// that implementation is not const.
816+
// *conditionally* holds.
819817
let trait_ref = predicate.map_bound(|predicate| ty::TraitPredicate {
820818
trait_ref: predicate.trait_ref,
821819
polarity: ty::PredicatePolarity::Positive,
822820
});
823821
let mut file = None;
822+
824823
let err_msg = self.get_standard_error_message(
825824
trait_ref,
826825
None,
@@ -834,7 +833,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
834833
let obligation = Obligation::new(
835834
self.tcx,
836835
ObligationCause::dummy(),
837-
param_env,
836+
main_obligation.param_env,
838837
trait_ref,
839838
);
840839
if !self.predicate_may_hold(&obligation) {
@@ -867,6 +866,42 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
867866
impl_span,
868867
format!("trait `{trait_name}` is implemented but not `const`"),
869868
);
869+
870+
let (condition_options, format_args) = self.on_unimplemented_components(
871+
trait_ref,
872+
main_obligation,
873+
diag.long_ty_path(),
874+
);
875+
876+
if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, impl_did)
877+
{
878+
let note = command.evaluate(
879+
self.tcx,
880+
predicate.skip_binder().trait_ref,
881+
&condition_options,
882+
&format_args,
883+
);
884+
let OnUnimplementedNote {
885+
message,
886+
label,
887+
notes,
888+
parent_label,
889+
append_const_msg: _,
890+
} = note;
891+
892+
if let Some(message) = message {
893+
diag.primary_message(message);
894+
}
895+
if let Some(label) = label {
896+
diag.span_label(impl_span, label);
897+
}
898+
for note in notes {
899+
diag.note(note);
900+
}
901+
if let Some(parent_label) = parent_label {
902+
diag.span_label(impl_span, parent_label);
903+
}
904+
}
870905
}
871906
}
872907
}

compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
55
use rustc_errors::codes::*;
66
use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
77
use rustc_hir as hir;
8+
use rustc_hir::def::DefKind;
89
use rustc_hir::def_id::{DefId, LocalDefId};
910
use rustc_hir::{AttrArgs, Attribute};
1011
use rustc_macros::LintDiagnostic;
@@ -103,7 +104,27 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
103104
if trait_pred.polarity() != ty::PredicatePolarity::Positive {
104105
return OnUnimplementedNote::default();
105106
}
107+
let (condition_options, format_args) =
108+
self.on_unimplemented_components(trait_pred, obligation, long_ty_path);
109+
if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, trait_pred.def_id())
110+
{
111+
command.evaluate(
112+
self.tcx,
113+
trait_pred.skip_binder().trait_ref,
114+
&condition_options,
115+
&format_args,
116+
)
117+
} else {
118+
OnUnimplementedNote::default()
119+
}
120+
}
106121

122+
pub(crate) fn on_unimplemented_components(
123+
&self,
124+
trait_pred: ty::PolyTraitPredicate<'tcx>,
125+
obligation: &PredicateObligation<'tcx>,
126+
long_ty_path: &mut Option<PathBuf>,
127+
) -> (ConditionOptions, FormatArgs<'tcx>) {
107128
let (def_id, args) = self
108129
.impl_similar_to(trait_pred, obligation)
109130
.unwrap_or_else(|| (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args));
@@ -293,12 +314,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
293314
.collect();
294315

295316
let format_args = FormatArgs { this, trait_sugared, generic_args, item_context };
296-
297-
if let Ok(Some(command)) = OnUnimplementedDirective::of_item(self.tcx, def_id) {
298-
command.evaluate(self.tcx, trait_pred.trait_ref, &condition_options, &format_args)
299-
} else {
300-
OnUnimplementedNote::default()
301-
}
317+
(condition_options, format_args)
302318
}
303319
}
304320

@@ -325,7 +341,7 @@ pub struct OnUnimplementedDirective {
325341
}
326342

327343
/// For the `#[rustc_on_unimplemented]` attribute
328-
#[derive(Default)]
344+
#[derive(Default, Debug)]
329345
pub struct OnUnimplementedNote {
330346
pub message: Option<String>,
331347
pub label: Option<String>,
@@ -562,17 +578,21 @@ impl<'tcx> OnUnimplementedDirective {
562578
}
563579

564580
pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> {
565-
if !tcx.is_trait(item_def_id) {
581+
let attr = if tcx.is_trait(item_def_id) {
582+
sym::on_unimplemented
583+
} else if let DefKind::Impl { of_trait: true } = tcx.def_kind(item_def_id) {
584+
sym::on_const
585+
} else {
566586
// It could be a trait_alias (`trait MyTrait = SomeOtherTrait`)
567587
// or an implementation (`impl MyTrait for Foo {}`)
568588
//
569589
// We don't support those.
570590
return Ok(None);
571-
}
591+
};
572592
if let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) {
573593
return Self::parse_attribute(attr, false, tcx, item_def_id);
574594
} else {
575-
tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, sym::on_unimplemented])
595+
tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, attr])
576596
.filter_map(|attr| Self::parse_attribute(attr, true, tcx, item_def_id).transpose())
577597
.try_fold(None, |aggr: Option<Self>, directive| {
578598
let directive = directive?;

library/core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@
150150
#![feature(decl_macro)]
151151
#![feature(deprecated_suggestion)]
152152
#![feature(derive_const)]
153+
#![feature(diagnostic_on_const)]
153154
#![feature(doc_cfg)]
154155
#![feature(doc_notable_trait)]
155156
#![feature(extern_types)]

0 commit comments

Comments
 (0)