Skip to content

Commit 7a0b29b

Browse files
committed
#[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
1 parent c9228ae commit 7a0b29b

File tree

16 files changed

+364
-31
lines changed

16 files changed

+364
-31
lines changed

compiler/rustc_ast/src/attr/mod.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,22 @@ impl Attribute {
9999
}
100100
}
101101

102+
pub fn is_path(&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.ident == Ident::with_dummy_span(*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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ declare_features! (
411411
/// Allows having using `suggestion` in the `#[deprecated]` attribute.
412412
(active, deprecated_suggestion, "1.61.0", Some(94785), None),
413413
/// Allows using the `#[diagnostic]` attribute tool namespace
414-
(active, diagnostic_namespace, "1.73.0", Some(94785), None),
414+
(active, diagnostic_namespace, "1.73.0", Some(111996), None),
415415
/// Controls errors in trait implementations.
416416
(active, do_not_recommend, "1.67.0", Some(51992), None),
417417
/// Tells rustdoc to automatically generate `#[doc(cfg(...))]`.

compiler/rustc_hir_analysis/messages.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ hir_analysis_copy_impl_on_type_with_dtor =
5757
the trait `Copy` cannot be implemented for this type; the type has a destructor
5858
.label = `Copy` not allowed on types with destructors
5959
60+
hir_analysis_diagnostic_on_unimplemented_only_for_traits =
61+
`#[diagnostic::on_unimplemented]` can only be applied to trait definitions
62+
6063
hir_analysis_drop_impl_negative = negative `Drop` impls are not supported
6164
6265
hir_analysis_drop_impl_on_wrong_item =

compiler/rustc_hir_analysis/src/check/check.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use rustc_infer::infer::outlives::env::OutlivesEnvironment;
1616
use rustc_infer::infer::{RegionVariableOrigin, TyCtxtInferExt};
1717
use rustc_infer::traits::{Obligation, TraitEngineExt as _};
1818
use rustc_lint_defs::builtin::REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS;
19+
use rustc_macros::LintDiagnostic;
1920
use rustc_middle::hir::nested_filter;
2021
use rustc_middle::middle::stability::EvalResult;
2122
use rustc_middle::traits::DefiningAnchor;
@@ -27,7 +28,9 @@ use rustc_middle::ty::{
2728
self, AdtDef, ParamEnv, RegionKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
2829
TypeVisitableExt,
2930
};
30-
use rustc_session::lint::builtin::{UNINHABITED_STATIC, UNSUPPORTED_CALLING_CONVENTIONS};
31+
use rustc_session::lint::builtin::{
32+
MALFORMED_DIAGNOSTIC_ATTRIBUTES, UNINHABITED_STATIC, UNSUPPORTED_CALLING_CONVENTIONS,
33+
};
3134
use rustc_span::symbol::sym;
3235
use rustc_span::{self, Span};
3336
use rustc_target::abi::FieldIdx;
@@ -40,6 +43,10 @@ use rustc_type_ir::fold::TypeFoldable;
4043

4144
use std::ops::ControlFlow;
4245

46+
#[derive(LintDiagnostic)]
47+
#[diag(hir_analysis_diagnostic_on_unimplemented_only_for_traits)]
48+
pub struct DiagnosticOnUnimplementedOnlyForTraits;
49+
4350
pub fn check_abi(tcx: TyCtxt<'_>, hir_id: hir::HirId, span: Span, abi: Abi) {
4451
match tcx.sess.target.is_abi_supported(abi) {
4552
Some(true) => (),
@@ -567,7 +574,22 @@ fn check_item_type(tcx: TyCtxt<'_>, id: hir::ItemId) {
567574
tcx.def_path_str(id.owner_id)
568575
);
569576
let _indenter = indenter();
570-
match tcx.def_kind(id.owner_id) {
577+
let def_kind = tcx.def_kind(id.owner_id);
578+
if !matches!(def_kind, DefKind::Trait) {
579+
let item_def_id = id.owner_id.to_def_id();
580+
if let Some(attr) = tcx
581+
.get_attrs_by_path(item_def_id, Box::new([sym::diagnostic, sym::on_unimplemented]))
582+
.next()
583+
{
584+
tcx.emit_spanned_lint(
585+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
586+
tcx.hir().local_def_id_to_hir_id(item_def_id.expect_local()),
587+
vec![attr.span],
588+
DiagnosticOnUnimplementedOnlyForTraits,
589+
);
590+
}
591+
}
592+
match def_kind {
571593
DefKind::Static(..) => {
572594
tcx.ensure().typeck(id.owner_id.def_id);
573595
maybe_check_static_with_link_section(tcx, id.owner_id.def_id);

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4475,6 +4475,7 @@ declare_lint! {
44754475
///
44764476
/// {{produces}}
44774477
///
4478+
///
44784479
/// ### Explanation
44794480
///
44804481
/// It is usually a mistake to specify a diagnostic attribute that does not exist. Check
@@ -4486,6 +4487,30 @@ declare_lint! {
44864487
"unrecognized diagnostic attribute"
44874488
}
44884489

4490+
declare_lint! {
4491+
/// The `malformed_diagnostic_attributes` lint detects malformed diagnostic attributes.
4492+
///
4493+
/// ### Example
4494+
///
4495+
/// ```rust
4496+
/// #![feature(diagnostic_namespace)]
4497+
/// #[diagnostic::on_unimplemented]
4498+
/// struct Foo;
4499+
/// ```
4500+
///
4501+
/// {{produces}}
4502+
///
4503+
/// ### Explanation
4504+
///
4505+
/// It is usually a mistake to specify invalid options to an existing diagnostic attribute.
4506+
/// Check the spelling, and check the diagnostic attribute listing for the correct set of options.
4507+
/// Also consider if you are using an old version of the compiler. Certain options may only
4508+
/// be available on newer compiler versions
4509+
pub MALFORMED_DIAGNOSTIC_ATTRIBUTES,
4510+
Warn,
4511+
"unrecognized diagnostic attribute options"
4512+
}
4513+
44894514
declare_lint! {
44904515
/// The `ambiguous_glob_imports` lint detects glob imports that should report ambiguity
44914516
/// errors, but previously didn't do that due to rustc bugs.

compiler/rustc_middle/src/ty/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2410,6 +2410,23 @@ impl<'tcx> TyCtxt<'tcx> {
24102410
}
24112411
}
24122412

2413+
pub fn get_attrs_by_path(
2414+
self,
2415+
did: impl Into<DefId>,
2416+
attr: Box<[Symbol]>,
2417+
) -> impl Iterator<Item = &'tcx ast::Attribute> {
2418+
let did: DefId = did.into();
2419+
let s = attr[0];
2420+
let filter_fn = move |a: &&ast::Attribute| a.is_path(&attr);
2421+
if let Some(did) = did.as_local() {
2422+
self.hir().attrs(self.hir().local_def_id_to_hir_id(did)).iter().filter(filter_fn)
2423+
} else if cfg!(debug_assertions) && rustc_feature::is_builtin_only_local(s) {
2424+
bug!("tried to access the `only_local` attribute `{}` from an extern crate", s);
2425+
} else {
2426+
self.item_attrs(did).iter().filter(filter_fn)
2427+
}
2428+
}
2429+
24132430
pub fn get_attr(self, did: impl Into<DefId>, attr: Symbol) -> Option<&'tcx ast::Attribute> {
24142431
if cfg!(debug_assertions) && !rustc_feature::is_valid_for_get_attr(attr) {
24152432
let did: DefId = did.into();

compiler/rustc_resolve/src/macros.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
609609
if res == Res::NonMacroAttr(NonMacroAttrKind::Tool)
610610
&& path.segments.len() >= 2
611611
&& path.segments[0].ident.name == sym::diagnostic
612+
&& path.segments[1].ident.name != sym::on_unimplemented
612613
{
613614
self.tcx.sess.parse_sess.buffer_lint(
614615
UNKNOWN_DIAGNOSTIC_ATTRIBUTES,

compiler/rustc_trait_selection/messages.ftl

Lines changed: 2 additions & 0 deletions
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

Lines changed: 89 additions & 26 deletions
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::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+
// 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) => unreachable!(
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,86 @@ 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+
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(
480+
item_def_id,
481+
Box::new([sym::diagnostic, sym::on_unimplemented]),
482+
)
483+
.next()
484+
} else {
485+
None
486+
}
487+
}) else {
443488
return Ok(None);
444489
};
445490

446491
let result = if let Some(items) = attr.meta_item_list() {
447-
Self::parse(tcx, item_def_id, &items, attr.span, true).map(Some)
492+
Self::parse(tcx, item_def_id, &items, attr.span, true, is_diagnostic_namespace_variant)
448493
} 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,
457-
attr.span,
458-
)?),
459-
note: None,
460-
parent_label: None,
461-
append_const_msg: None,
462-
}))
494+
if !is_diagnostic_namespace_variant {
495+
Ok(Some(OnUnimplementedDirective {
496+
condition: None,
497+
message: None,
498+
subcommands: vec![],
499+
label: Some(OnUnimplementedFormatString::try_parse(
500+
tcx,
501+
item_def_id,
502+
value,
503+
attr.span,
504+
)?),
505+
note: None,
506+
parent_label: None,
507+
append_const_msg: None,
508+
}))
509+
} else {
510+
tcx.emit_spanned_lint(
511+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
512+
tcx.hir().local_def_id_to_hir_id(item_def_id.expect_local()),
513+
vec![attr.span],
514+
NoValueInOnUnimplementedLint,
515+
);
516+
Ok(None)
517+
}
518+
} else if is_diagnostic_namespace_variant {
519+
tcx.emit_spanned_lint(
520+
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
521+
tcx.hir().local_def_id_to_hir_id(item_def_id.expect_local()),
522+
vec![attr.span],
523+
NoValueInOnUnimplementedLint,
524+
);
525+
Ok(None)
463526
} else {
464527
let reported =
465528
tcx.sess.delay_span_bug(DUMMY_SP, "of_item: neither meta_item_list nor value_str");

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ error[E0658]: `#[diagnostic]` attribute name space is experimental
44
LL | #[diagnostic::non_existing_attribute]
55
| ^^^^^^^^^^
66
|
7-
= note: see issue #94785 <https://github.com/rust-lang/rust/issues/94785> for more information
7+
= note: see issue #111996 <https://github.com/rust-lang/rust/issues/111996> for more information
88
= help: add `#![feature(diagnostic_namespace)]` to the crate attributes to enable
99

1010
error[E0658]: `#[diagnostic]` attribute name space is experimental
@@ -13,7 +13,7 @@ error[E0658]: `#[diagnostic]` attribute name space is experimental
1313
LL | #[diagnostic::non_existing_attribute(with_option = "foo")]
1414
| ^^^^^^^^^^
1515
|
16-
= note: see issue #94785 <https://github.com/rust-lang/rust/issues/94785> for more information
16+
= note: see issue #111996 <https://github.com/rust-lang/rust/issues/111996> for more information
1717
= help: add `#![feature(diagnostic_namespace)]` to the crate attributes to enable
1818

1919
warning: unknown diagnostic attribute

0 commit comments

Comments
 (0)