Skip to content

Commit 327e6cf

Browse files
committed
Auto merge of #114452 - weiznich:feature/diagnostic_on_unimplemented, r=compiler-errors
`#[diagnostic::on_unimplemented]` without filters This commit adds support for a `#[diagnostic::on_unimplemented]` attribute with the following options: * `message` to customize the primary error message * `note` to add a customized note message to an error message * `label` to customize the label part of the error message The relevant behavior is specified in [RFC-3366](https://rust-lang.github.io/rfcs/3366-diagnostic-attribute-namespace.html)
2 parents cdd182c + 5b8a7a0 commit 327e6cf

18 files changed

+360
-39
lines changed

compiler/rustc_ast/src/attr/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,22 @@ impl Attribute {
9999
}
100100
}
101101

102+
pub fn path_matches(&self, name: &[Symbol]) -> bool {
103+
match &self.kind {
104+
AttrKind::Normal(normal) => {
105+
normal.item.path.segments.len() == name.len()
106+
&& normal
107+
.item
108+
.path
109+
.segments
110+
.iter()
111+
.zip(name)
112+
.all(|(s, n)| s.args.is_none() && s.ident.name == *n)
113+
}
114+
AttrKind::DocComment(..) => false,
115+
}
116+
}
117+
102118
pub fn is_word(&self) -> bool {
103119
if let AttrKind::Normal(normal) = &self.kind {
104120
matches!(normal.item.args, AttrArgs::Empty)

compiler/rustc_feature/src/active.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ declare_features! (
416416
/// Allows having using `suggestion` in the `#[deprecated]` attribute.
417417
(active, deprecated_suggestion, "1.61.0", Some(94785), None),
418418
/// Allows using the `#[diagnostic]` attribute tool namespace
419-
(active, diagnostic_namespace, "1.73.0", Some(94785), None),
419+
(active, diagnostic_namespace, "1.73.0", Some(111996), None),
420420
/// Controls errors in trait implementations.
421421
(active, do_not_recommend, "1.67.0", Some(51992), None),
422422
/// Tells rustdoc to automatically generate `#[doc(cfg(...))]`.

compiler/rustc_lint_defs/src/builtin.rs

+7-4
Original file line numberDiff line numberDiff line change
@@ -3405,8 +3405,8 @@ declare_lint_pass! {
34053405
UNFULFILLED_LINT_EXPECTATIONS,
34063406
UNINHABITED_STATIC,
34073407
UNKNOWN_CRATE_TYPES,
3408-
UNKNOWN_DIAGNOSTIC_ATTRIBUTES,
34093408
UNKNOWN_LINTS,
3409+
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
34103410
UNNAMEABLE_TEST_ITEMS,
34113411
UNNAMEABLE_TYPES,
34123412
UNREACHABLE_CODE,
@@ -4420,7 +4420,8 @@ declare_lint! {
44204420
}
44214421

44224422
declare_lint! {
4423-
/// The `unknown_diagnostic_attributes` lint detects unrecognized diagnostic attributes.
4423+
/// The `unknown_or_malformed_diagnostic_attributes` lint detects unrecognized or otherwise malformed
4424+
/// diagnostic attributes.
44244425
///
44254426
/// ### Example
44264427
///
@@ -4432,15 +4433,17 @@ declare_lint! {
44324433
///
44334434
/// {{produces}}
44344435
///
4436+
///
44354437
/// ### Explanation
44364438
///
44374439
/// It is usually a mistake to specify a diagnostic attribute that does not exist. Check
44384440
/// the spelling, and check the diagnostic attribute listing for the correct name. Also
44394441
/// consider if you are using an old version of the compiler, and the attribute
44404442
/// is only available in a newer version.
4441-
pub UNKNOWN_DIAGNOSTIC_ATTRIBUTES,
4443+
pub UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
44424444
Warn,
4443-
"unrecognized diagnostic attribute"
4445+
"unrecognized or malformed diagnostic attribute",
4446+
@feature_gate = sym::diagnostic_namespace;
44444447
}
44454448

44464449
declare_lint! {

compiler/rustc_middle/src/ty/mod.rs

+16
Original file line numberDiff line numberDiff line change
@@ -2408,6 +2408,22 @@ impl<'tcx> TyCtxt<'tcx> {
24082408
}
24092409
}
24102410

2411+
pub fn get_attrs_by_path<'attr>(
2412+
self,
2413+
did: DefId,
2414+
attr: &'attr [Symbol],
2415+
) -> impl Iterator<Item = &'tcx ast::Attribute> + 'attr
2416+
where
2417+
'tcx: 'attr,
2418+
{
2419+
let filter_fn = move |a: &&ast::Attribute| a.path_matches(&attr);
2420+
if let Some(did) = did.as_local() {
2421+
self.hir().attrs(self.hir().local_def_id_to_hir_id(did)).iter().filter(filter_fn)
2422+
} else {
2423+
self.item_attrs(did).iter().filter(filter_fn)
2424+
}
2425+
}
2426+
24112427
pub fn get_attr(self, did: impl Into<DefId>, attr: Symbol) -> Option<&'tcx ast::Attribute> {
24122428
if cfg!(debug_assertions) && !rustc_feature::is_valid_for_get_attr(attr) {
24132429
let did: DefId = did.into();

compiler/rustc_passes/messages.ftl

+3
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ passes_deprecated_annotation_has_no_effect =
153153
passes_deprecated_attribute =
154154
deprecated attribute must be paired with either stable or unstable attribute
155155
156+
passes_diagnostic_diagnostic_on_unimplemented_only_for_traits =
157+
`#[diagnostic::on_unimplemented]` can only be applied to trait definitions
158+
156159
passes_diagnostic_item_first_defined =
157160
the diagnostic item is first defined here
158161

compiler/rustc_passes/src/check_attr.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use rustc_hir::{
1616
self, FnSig, ForeignItem, HirId, Item, ItemKind, TraitItem, CRATE_HIR_ID, CRATE_OWNER_ID,
1717
};
1818
use rustc_hir::{MethodKind, Target, Unsafety};
19+
use rustc_macros::LintDiagnostic;
1920
use rustc_middle::hir::nested_filter;
2021
use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault;
2122
use rustc_middle::query::Providers;
@@ -24,7 +25,7 @@ use rustc_middle::ty::error::{ExpectedFound, TypeError};
2425
use rustc_middle::ty::{self, TyCtxt};
2526
use rustc_session::lint::builtin::{
2627
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, INVALID_MACRO_EXPORT_ARGUMENTS,
27-
UNUSED_ATTRIBUTES,
28+
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
2829
};
2930
use rustc_session::parse::feature_err;
3031
use rustc_span::symbol::{kw, sym, Symbol};
@@ -36,6 +37,10 @@ use rustc_trait_selection::traits::ObligationCtxt;
3637
use std::cell::Cell;
3738
use std::collections::hash_map::Entry;
3839

40+
#[derive(LintDiagnostic)]
41+
#[diag(passes_diagnostic_diagnostic_on_unimplemented_only_for_traits)]
42+
pub struct DiagnosticOnUnimplementedOnlyForTraits;
43+
3944
pub(crate) fn target_from_impl_item<'tcx>(
4045
tcx: TyCtxt<'tcx>,
4146
impl_item: &hir::ImplItem<'_>,
@@ -104,6 +109,9 @@ impl CheckAttrVisitor<'_> {
104109
let mut seen = FxHashMap::default();
105110
let attrs = self.tcx.hir().attrs(hir_id);
106111
for attr in attrs {
112+
if attr.path_matches(&[sym::diagnostic, sym::on_unimplemented]) {
113+
self.check_diagnostic_on_unimplemented(attr.span, hir_id, target);
114+
}
107115
match attr.name_or_empty() {
108116
sym::do_not_recommend => self.check_do_not_recommend(attr.span, target),
109117
sym::inline => self.check_inline(hir_id, attr, span, target),
@@ -287,6 +295,18 @@ impl CheckAttrVisitor<'_> {
287295
}
288296
}
289297

298+
/// Checks if `#[diagnostic::on_unimplemented]` is applied to a trait definition
299+
fn check_diagnostic_on_unimplemented(&self, attr_span: Span, hir_id: HirId, target: Target) {
300+
if !matches!(target, Target::Trait) {
301+
self.tcx.emit_spanned_lint(
302+
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
303+
hir_id,
304+
attr_span,
305+
DiagnosticOnUnimplementedOnlyForTraits,
306+
);
307+
}
308+
}
309+
290310
/// Checks if an `#[inline]` is applied to a function or a closure. Returns `true` if valid.
291311
fn check_inline(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool {
292312
match target {

compiler/rustc_resolve/src/macros.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ use rustc_middle::middle::stability;
2626
use rustc_middle::ty::RegisteredTools;
2727
use rustc_middle::ty::{TyCtxt, Visibility};
2828
use rustc_session::lint::builtin::{
29-
LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE, UNKNOWN_DIAGNOSTIC_ATTRIBUTES,
29+
LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE, UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
3030
};
3131
use rustc_session::lint::builtin::{UNUSED_MACROS, UNUSED_MACRO_RULES};
3232
use rustc_session::lint::BuiltinLintDiagnostics;
@@ -610,9 +610,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
610610
if res == Res::NonMacroAttr(NonMacroAttrKind::Tool)
611611
&& path.segments.len() >= 2
612612
&& path.segments[0].ident.name == sym::diagnostic
613+
&& path.segments[1].ident.name != sym::on_unimplemented
613614
{
614615
self.tcx.sess.parse_sess.buffer_lint(
615-
UNKNOWN_DIAGNOSTIC_ATTRIBUTES,
616+
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
616617
path.segments[1].span(),
617618
node_id,
618619
"unknown diagnostic attribute",

compiler/rustc_trait_selection/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ trait_selection_inherent_projection_normalization_overflow = overflow evaluating
2727
trait_selection_invalid_on_clause_in_rustc_on_unimplemented = invalid `on`-clause in `#[rustc_on_unimplemented]`
2828
.label = invalid on-clause here
2929
30+
trait_selection_malformed_on_unimplemented_attr = malformed `on_unimplemented` attribute
31+
3032
trait_selection_negative_positive_conflict = found both positive and negative implementation of trait `{$trait_desc}`{$self_desc ->
3133
[none] {""}
3234
*[default] {" "}for type `{$self_desc}`

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

+84-25
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rustc_hir::def_id::DefId;
99
use rustc_middle::ty::GenericArgsRef;
1010
use rustc_middle::ty::{self, GenericParamDefKind, TyCtxt};
1111
use rustc_parse_format::{ParseMode, Parser, Piece, Position};
12+
use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
1213
use rustc_span::symbol::{kw, sym, Symbol};
1314
use rustc_span::{Span, DUMMY_SP};
1415
use std::iter;
@@ -336,14 +337,19 @@ pub enum AppendConstMessage {
336337
Custom(Symbol),
337338
}
338339

340+
#[derive(LintDiagnostic)]
341+
#[diag(trait_selection_malformed_on_unimplemented_attr)]
342+
pub struct NoValueInOnUnimplementedLint;
343+
339344
impl<'tcx> OnUnimplementedDirective {
340345
fn parse(
341346
tcx: TyCtxt<'tcx>,
342347
item_def_id: DefId,
343348
items: &[NestedMetaItem],
344349
span: Span,
345350
is_root: bool,
346-
) -> Result<Self, ErrorGuaranteed> {
351+
is_diagnostic_namespace_variant: bool,
352+
) -> Result<Option<Self>, ErrorGuaranteed> {
347353
let mut errored = None;
348354
let mut item_iter = items.iter();
349355

@@ -391,7 +397,10 @@ impl<'tcx> OnUnimplementedDirective {
391397
note = parse_value(note_)?;
392398
continue;
393399
}
394-
} else if item.has_name(sym::parent_label) && parent_label.is_none() {
400+
} else if item.has_name(sym::parent_label)
401+
&& parent_label.is_none()
402+
&& !is_diagnostic_namespace_variant
403+
{
395404
if let Some(parent_label_) = item.value_str() {
396405
parent_label = parse_value(parent_label_)?;
397406
continue;
@@ -401,15 +410,30 @@ impl<'tcx> OnUnimplementedDirective {
401410
&& message.is_none()
402411
&& label.is_none()
403412
&& note.is_none()
413+
&& !is_diagnostic_namespace_variant
414+
// FIXME(diagnostic_namespace): disallow filters for now
404415
{
405416
if let Some(items) = item.meta_item_list() {
406-
match Self::parse(tcx, item_def_id, &items, item.span(), false) {
407-
Ok(subcommand) => subcommands.push(subcommand),
417+
match Self::parse(
418+
tcx,
419+
item_def_id,
420+
&items,
421+
item.span(),
422+
false,
423+
is_diagnostic_namespace_variant,
424+
) {
425+
Ok(Some(subcommand)) => subcommands.push(subcommand),
426+
Ok(None) => bug!(
427+
"This cannot happen for now as we only reach that if `is_diagnostic_namespace_variant` is false"
428+
),
408429
Err(reported) => errored = Some(reported),
409430
};
410431
continue;
411432
}
412-
} else if item.has_name(sym::append_const_msg) && append_const_msg.is_none() {
433+
} else if item.has_name(sym::append_const_msg)
434+
&& append_const_msg.is_none()
435+
&& !is_diagnostic_namespace_variant
436+
{
413437
if let Some(msg) = item.value_str() {
414438
append_const_msg = Some(AppendConstMessage::Custom(msg));
415439
continue;
@@ -419,47 +443,82 @@ impl<'tcx> OnUnimplementedDirective {
419443
}
420444
}
421445

422-
// nothing found
423-
tcx.sess.emit_err(NoValueInOnUnimplemented { span: item.span() });
446+
if is_diagnostic_namespace_variant {
447+
tcx.emit_spanned_lint(
448+
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
449+
tcx.hir().local_def_id_to_hir_id(item_def_id.expect_local()),
450+
vec![item.span()],
451+
NoValueInOnUnimplementedLint,
452+
);
453+
} else {
454+
// nothing found
455+
tcx.sess.emit_err(NoValueInOnUnimplemented { span: item.span() });
456+
}
424457
}
425458

426459
if let Some(reported) = errored {
427-
Err(reported)
460+
if is_diagnostic_namespace_variant { Ok(None) } else { Err(reported) }
428461
} else {
429-
Ok(OnUnimplementedDirective {
462+
Ok(Some(OnUnimplementedDirective {
430463
condition,
431464
subcommands,
432465
message,
433466
label,
434467
note,
435468
parent_label,
436469
append_const_msg,
437-
})
470+
}))
438471
}
439472
}
440473

441474
pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> {
442-
let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) else {
475+
let mut is_diagnostic_namespace_variant = false;
476+
let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented).or_else(|| {
477+
if tcx.features().diagnostic_namespace {
478+
is_diagnostic_namespace_variant = true;
479+
tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, sym::on_unimplemented]).next()
480+
} else {
481+
None
482+
}
483+
}) else {
443484
return Ok(None);
444485
};
445486

446487
let result = if let Some(items) = attr.meta_item_list() {
447-
Self::parse(tcx, item_def_id, &items, attr.span, true).map(Some)
488+
Self::parse(tcx, item_def_id, &items, attr.span, true, is_diagnostic_namespace_variant)
448489
} else if let Some(value) = attr.value_str() {
449-
Ok(Some(OnUnimplementedDirective {
450-
condition: None,
451-
message: None,
452-
subcommands: vec![],
453-
label: Some(OnUnimplementedFormatString::try_parse(
454-
tcx,
455-
item_def_id,
456-
value,
490+
if !is_diagnostic_namespace_variant {
491+
Ok(Some(OnUnimplementedDirective {
492+
condition: None,
493+
message: None,
494+
subcommands: vec![],
495+
label: Some(OnUnimplementedFormatString::try_parse(
496+
tcx,
497+
item_def_id,
498+
value,
499+
attr.span,
500+
)?),
501+
note: None,
502+
parent_label: None,
503+
append_const_msg: None,
504+
}))
505+
} else {
506+
tcx.emit_spanned_lint(
507+
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
508+
tcx.hir().local_def_id_to_hir_id(item_def_id.expect_local()),
457509
attr.span,
458-
)?),
459-
note: None,
460-
parent_label: None,
461-
append_const_msg: None,
462-
}))
510+
NoValueInOnUnimplementedLint,
511+
);
512+
Ok(None)
513+
}
514+
} else if is_diagnostic_namespace_variant {
515+
tcx.emit_spanned_lint(
516+
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
517+
tcx.hir().local_def_id_to_hir_id(item_def_id.expect_local()),
518+
attr.span,
519+
NoValueInOnUnimplementedLint,
520+
);
521+
Ok(None)
463522
} else {
464523
let reported =
465524
tcx.sess.delay_span_bug(DUMMY_SP, "of_item: neither meta_item_list nor value_str");

tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
#[diagnostic::non_existing_attribute]
22
//~^ERROR `#[diagnostic]` attribute name space is experimental [E0658]
3-
//~|WARNING unknown diagnostic attribute [unknown_diagnostic_attributes]
3+
//~|WARNING unknown diagnostic attribute [unknown_or_malformed_diagnostic_attributes]
44
pub trait Bar {
55
}
66

77
#[diagnostic::non_existing_attribute(with_option = "foo")]
88
//~^ERROR `#[diagnostic]` attribute name space is experimental [E0658]
9-
//~|WARNING unknown diagnostic attribute [unknown_diagnostic_attributes]
9+
//~|WARNING unknown diagnostic attribute [unknown_or_malformed_diagnostic_attributes]
1010
struct Foo;
1111

1212
fn main() {

0 commit comments

Comments
 (0)