about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGeorg Semmler <github@weiznich.de>2023-06-02 13:55:46 +0200
committerGeorg Semmler <github@weiznich.de>2023-09-12 20:03:18 +0200
commit5b8a7a09178f73a581db2b21cf516fea7be9b49f (patch)
tree369b9a76b0f426403c66d007390830fb15208f89
parente5fedceabf4e0564231db592b6d1f35e1ca27908 (diff)
downloadrust-5b8a7a09178f73a581db2b21cf516fea7be9b49f.tar.gz
rust-5b8a7a09178f73a581db2b21cf516fea7be9b49f.zip
`#[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

Co-authored-by: León Orell Valerian Liehr <me@fmease.dev>
Co-authored-by: Michael Goulet <michael@errs.io>
-rw-r--r--compiler/rustc_ast/src/attr/mod.rs16
-rw-r--r--compiler/rustc_feature/src/active.rs2
-rw-r--r--compiler/rustc_lint_defs/src/builtin.rs11
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs16
-rw-r--r--compiler/rustc_passes/messages.ftl3
-rw-r--r--compiler/rustc_passes/src/check_attr.rs22
-rw-r--r--compiler/rustc_resolve/src/macros.rs5
-rw-r--r--compiler/rustc_trait_selection/messages.ftl2
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs109
-rw-r--r--tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.rs4
-rw-r--r--tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.stderr6
-rw-r--r--tests/ui/diagnostic_namespace/non_existing_attributes_accepted.stderr2
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.rs37
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr110
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/feature-gate-diagnostic_on_unimplemented.rs7
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/feature-gate-diagnostic_on_unimplemented.stderr12
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/on_unimplemented_simple.rs11
-rw-r--r--tests/ui/diagnostic_namespace/on_unimplemented/on_unimplemented_simple.stderr24
18 files changed, 360 insertions, 39 deletions
diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs
index 19a2b3017bc..db008ea139d 100644
--- a/compiler/rustc_ast/src/attr/mod.rs
+++ b/compiler/rustc_ast/src/attr/mod.rs
@@ -99,6 +99,22 @@ impl Attribute {
         }
     }
 
+    pub fn path_matches(&self, name: &[Symbol]) -> bool {
+        match &self.kind {
+            AttrKind::Normal(normal) => {
+                normal.item.path.segments.len() == name.len()
+                    && normal
+                        .item
+                        .path
+                        .segments
+                        .iter()
+                        .zip(name)
+                        .all(|(s, n)| s.args.is_none() && s.ident.name == *n)
+            }
+            AttrKind::DocComment(..) => false,
+        }
+    }
+
     pub fn is_word(&self) -> bool {
         if let AttrKind::Normal(normal) = &self.kind {
             matches!(normal.item.args, AttrArgs::Empty)
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index 1941390dc4a..e651fdc4ad2 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -411,7 +411,7 @@ declare_features! (
     /// Allows having using `suggestion` in the `#[deprecated]` attribute.
     (active, deprecated_suggestion, "1.61.0", Some(94785), None),
     /// Allows using the `#[diagnostic]` attribute tool namespace
-    (active, diagnostic_namespace, "1.73.0", Some(94785), None),
+    (active, diagnostic_namespace, "1.73.0", Some(111996), None),
     /// Controls errors in trait implementations.
     (active, do_not_recommend, "1.67.0", Some(51992), None),
     /// Tells rustdoc to automatically generate `#[doc(cfg(...))]`.
diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs
index 2567e273e11..63e147ec82f 100644
--- a/compiler/rustc_lint_defs/src/builtin.rs
+++ b/compiler/rustc_lint_defs/src/builtin.rs
@@ -3404,8 +3404,8 @@ declare_lint_pass! {
         UNFULFILLED_LINT_EXPECTATIONS,
         UNINHABITED_STATIC,
         UNKNOWN_CRATE_TYPES,
-        UNKNOWN_DIAGNOSTIC_ATTRIBUTES,
         UNKNOWN_LINTS,
+        UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
         UNNAMEABLE_TEST_ITEMS,
         UNNAMEABLE_TYPES,
         UNREACHABLE_CODE,
@@ -4419,7 +4419,8 @@ declare_lint! {
 }
 
 declare_lint! {
-    /// The `unknown_diagnostic_attributes` lint detects unrecognized diagnostic attributes.
+    /// The `unknown_or_malformed_diagnostic_attributes` lint detects unrecognized or otherwise malformed
+    /// diagnostic attributes.
     ///
     /// ### Example
     ///
@@ -4431,15 +4432,17 @@ declare_lint! {
     ///
     /// {{produces}}
     ///
+    ///
     /// ### Explanation
     ///
     /// It is usually a mistake to specify a diagnostic attribute that does not exist. Check
     /// the spelling, and check the diagnostic attribute listing for the correct name. Also
     /// consider if you are using an old version of the compiler, and the attribute
     /// is only available in a newer version.
-    pub UNKNOWN_DIAGNOSTIC_ATTRIBUTES,
+    pub UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
     Warn,
-    "unrecognized diagnostic attribute"
+    "unrecognized or malformed diagnostic attribute",
+    @feature_gate = sym::diagnostic_namespace;
 }
 
 declare_lint! {
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index 99b697d0ea5..ff420cf902a 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -2408,6 +2408,22 @@ impl<'tcx> TyCtxt<'tcx> {
         }
     }
 
+    pub fn get_attrs_by_path<'attr>(
+        self,
+        did: DefId,
+        attr: &'attr [Symbol],
+    ) -> impl Iterator<Item = &'tcx ast::Attribute> + 'attr
+    where
+        'tcx: 'attr,
+    {
+        let filter_fn = move |a: &&ast::Attribute| a.path_matches(&attr);
+        if let Some(did) = did.as_local() {
+            self.hir().attrs(self.hir().local_def_id_to_hir_id(did)).iter().filter(filter_fn)
+        } else {
+            self.item_attrs(did).iter().filter(filter_fn)
+        }
+    }
+
     pub fn get_attr(self, did: impl Into<DefId>, attr: Symbol) -> Option<&'tcx ast::Attribute> {
         if cfg!(debug_assertions) && !rustc_feature::is_valid_for_get_attr(attr) {
             let did: DefId = did.into();
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index eb1e1269f7f..a4abded67c5 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -140,6 +140,9 @@ passes_deprecated_annotation_has_no_effect =
 passes_deprecated_attribute =
     deprecated attribute must be paired with either stable or unstable attribute
 
+passes_diagnostic_diagnostic_on_unimplemented_only_for_traits =
+    `#[diagnostic::on_unimplemented]` can only be applied to trait definitions
+
 passes_diagnostic_item_first_defined =
     the diagnostic item is first defined here
 
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index 50a087c7847..ba9e7ddaf51 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -16,6 +16,7 @@ use rustc_hir::{
     self, FnSig, ForeignItem, HirId, Item, ItemKind, TraitItem, CRATE_HIR_ID, CRATE_OWNER_ID,
 };
 use rustc_hir::{MethodKind, Target, Unsafety};
+use rustc_macros::LintDiagnostic;
 use rustc_middle::hir::nested_filter;
 use rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault;
 use rustc_middle::query::Providers;
@@ -24,7 +25,7 @@ use rustc_middle::ty::error::{ExpectedFound, TypeError};
 use rustc_middle::ty::{self, TyCtxt};
 use rustc_session::lint::builtin::{
     CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, INVALID_MACRO_EXPORT_ARGUMENTS,
-    UNUSED_ATTRIBUTES,
+    UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
 };
 use rustc_session::parse::feature_err;
 use rustc_span::symbol::{kw, sym, Symbol};
@@ -36,6 +37,10 @@ use rustc_trait_selection::traits::ObligationCtxt;
 use std::cell::Cell;
 use std::collections::hash_map::Entry;
 
+#[derive(LintDiagnostic)]
+#[diag(passes_diagnostic_diagnostic_on_unimplemented_only_for_traits)]
+pub struct DiagnosticOnUnimplementedOnlyForTraits;
+
 pub(crate) fn target_from_impl_item<'tcx>(
     tcx: TyCtxt<'tcx>,
     impl_item: &hir::ImplItem<'_>,
@@ -104,6 +109,9 @@ impl CheckAttrVisitor<'_> {
         let mut seen = FxHashMap::default();
         let attrs = self.tcx.hir().attrs(hir_id);
         for attr in attrs {
+            if attr.path_matches(&[sym::diagnostic, sym::on_unimplemented]) {
+                self.check_diagnostic_on_unimplemented(attr.span, hir_id, target);
+            }
             match attr.name_or_empty() {
                 sym::do_not_recommend => self.check_do_not_recommend(attr.span, target),
                 sym::inline => self.check_inline(hir_id, attr, span, target),
@@ -284,6 +292,18 @@ impl CheckAttrVisitor<'_> {
         }
     }
 
+    /// Checks if `#[diagnostic::on_unimplemented]` is applied to a trait definition
+    fn check_diagnostic_on_unimplemented(&self, attr_span: Span, hir_id: HirId, target: Target) {
+        if !matches!(target, Target::Trait) {
+            self.tcx.emit_spanned_lint(
+                UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                hir_id,
+                attr_span,
+                DiagnosticOnUnimplementedOnlyForTraits,
+            );
+        }
+    }
+
     /// Checks if an `#[inline]` is applied to a function or a closure. Returns `true` if valid.
     fn check_inline(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool {
         match target {
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index 82060716575..90ae08ce37c 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -26,7 +26,7 @@ use rustc_middle::middle::stability;
 use rustc_middle::ty::RegisteredTools;
 use rustc_middle::ty::{TyCtxt, Visibility};
 use rustc_session::lint::builtin::{
-    LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE, UNKNOWN_DIAGNOSTIC_ATTRIBUTES,
+    LEGACY_DERIVE_HELPERS, SOFT_UNSTABLE, UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
 };
 use rustc_session::lint::builtin::{UNUSED_MACROS, UNUSED_MACRO_RULES};
 use rustc_session::lint::BuiltinLintDiagnostics;
@@ -610,9 +610,10 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
         if res == Res::NonMacroAttr(NonMacroAttrKind::Tool)
             && path.segments.len() >= 2
             && path.segments[0].ident.name == sym::diagnostic
+            && path.segments[1].ident.name != sym::on_unimplemented
         {
             self.tcx.sess.parse_sess.buffer_lint(
-                UNKNOWN_DIAGNOSTIC_ATTRIBUTES,
+                UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
                 path.segments[1].span(),
                 node_id,
                 "unknown diagnostic attribute",
diff --git a/compiler/rustc_trait_selection/messages.ftl b/compiler/rustc_trait_selection/messages.ftl
index 2db24c43734..20253b32add 100644
--- a/compiler/rustc_trait_selection/messages.ftl
+++ b/compiler/rustc_trait_selection/messages.ftl
@@ -27,6 +27,8 @@ trait_selection_inherent_projection_normalization_overflow = overflow evaluating
 trait_selection_invalid_on_clause_in_rustc_on_unimplemented = invalid `on`-clause in `#[rustc_on_unimplemented]`
     .label = invalid on-clause here
 
+trait_selection_malformed_on_unimplemented_attr = malformed `on_unimplemented` attribute
+
 trait_selection_negative_positive_conflict = found both positive and negative implementation of trait `{$trait_desc}`{$self_desc ->
         [none] {""}
        *[default] {" "}for type `{$self_desc}`
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs
index 0e73bad1918..e01713e3d88 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/on_unimplemented.rs
@@ -9,6 +9,7 @@ use rustc_hir::def_id::DefId;
 use rustc_middle::ty::GenericArgsRef;
 use rustc_middle::ty::{self, GenericParamDefKind, TyCtxt};
 use rustc_parse_format::{ParseMode, Parser, Piece, Position};
+use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
 use rustc_span::symbol::{kw, sym, Symbol};
 use rustc_span::{Span, DUMMY_SP};
 use std::iter;
@@ -336,6 +337,10 @@ pub enum AppendConstMessage {
     Custom(Symbol),
 }
 
+#[derive(LintDiagnostic)]
+#[diag(trait_selection_malformed_on_unimplemented_attr)]
+pub struct NoValueInOnUnimplementedLint;
+
 impl<'tcx> OnUnimplementedDirective {
     fn parse(
         tcx: TyCtxt<'tcx>,
@@ -343,7 +348,8 @@ impl<'tcx> OnUnimplementedDirective {
         items: &[NestedMetaItem],
         span: Span,
         is_root: bool,
-    ) -> Result<Self, ErrorGuaranteed> {
+        is_diagnostic_namespace_variant: bool,
+    ) -> Result<Option<Self>, ErrorGuaranteed> {
         let mut errored = None;
         let mut item_iter = items.iter();
 
@@ -391,7 +397,10 @@ impl<'tcx> OnUnimplementedDirective {
                     note = parse_value(note_)?;
                     continue;
                 }
-            } else if item.has_name(sym::parent_label) && parent_label.is_none() {
+            } else if item.has_name(sym::parent_label)
+                && parent_label.is_none()
+                && !is_diagnostic_namespace_variant
+            {
                 if let Some(parent_label_) = item.value_str() {
                     parent_label = parse_value(parent_label_)?;
                     continue;
@@ -401,15 +410,30 @@ impl<'tcx> OnUnimplementedDirective {
                 && message.is_none()
                 && label.is_none()
                 && note.is_none()
+                && !is_diagnostic_namespace_variant
+            // FIXME(diagnostic_namespace): disallow filters for now
             {
                 if let Some(items) = item.meta_item_list() {
-                    match Self::parse(tcx, item_def_id, &items, item.span(), false) {
-                        Ok(subcommand) => subcommands.push(subcommand),
+                    match Self::parse(
+                        tcx,
+                        item_def_id,
+                        &items,
+                        item.span(),
+                        false,
+                        is_diagnostic_namespace_variant,
+                    ) {
+                        Ok(Some(subcommand)) => subcommands.push(subcommand),
+                        Ok(None) => bug!(
+                            "This cannot happen for now as we only reach that if `is_diagnostic_namespace_variant` is false"
+                        ),
                         Err(reported) => errored = Some(reported),
                     };
                     continue;
                 }
-            } else if item.has_name(sym::append_const_msg) && append_const_msg.is_none() {
+            } else if item.has_name(sym::append_const_msg)
+                && append_const_msg.is_none()
+                && !is_diagnostic_namespace_variant
+            {
                 if let Some(msg) = item.value_str() {
                     append_const_msg = Some(AppendConstMessage::Custom(msg));
                     continue;
@@ -419,14 +443,23 @@ impl<'tcx> OnUnimplementedDirective {
                 }
             }
 
-            // nothing found
-            tcx.sess.emit_err(NoValueInOnUnimplemented { span: item.span() });
+            if is_diagnostic_namespace_variant {
+                tcx.emit_spanned_lint(
+                    UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                    tcx.hir().local_def_id_to_hir_id(item_def_id.expect_local()),
+                    vec![item.span()],
+                    NoValueInOnUnimplementedLint,
+                );
+            } else {
+                // nothing found
+                tcx.sess.emit_err(NoValueInOnUnimplemented { span: item.span() });
+            }
         }
 
         if let Some(reported) = errored {
-            Err(reported)
+            if is_diagnostic_namespace_variant { Ok(None) } else { Err(reported) }
         } else {
-            Ok(OnUnimplementedDirective {
+            Ok(Some(OnUnimplementedDirective {
                 condition,
                 subcommands,
                 message,
@@ -434,32 +467,58 @@ impl<'tcx> OnUnimplementedDirective {
                 note,
                 parent_label,
                 append_const_msg,
-            })
+            }))
         }
     }
 
     pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> {
-        let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) else {
+        let mut is_diagnostic_namespace_variant = false;
+        let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented).or_else(|| {
+            if tcx.features().diagnostic_namespace {
+                is_diagnostic_namespace_variant = true;
+                tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, sym::on_unimplemented]).next()
+            } else {
+                None
+            }
+        }) else {
             return Ok(None);
         };
 
         let result = if let Some(items) = attr.meta_item_list() {
-            Self::parse(tcx, item_def_id, &items, attr.span, true).map(Some)
+            Self::parse(tcx, item_def_id, &items, attr.span, true, is_diagnostic_namespace_variant)
         } else if let Some(value) = attr.value_str() {
-            Ok(Some(OnUnimplementedDirective {
-                condition: None,
-                message: None,
-                subcommands: vec![],
-                label: Some(OnUnimplementedFormatString::try_parse(
-                    tcx,
-                    item_def_id,
-                    value,
+            if !is_diagnostic_namespace_variant {
+                Ok(Some(OnUnimplementedDirective {
+                    condition: None,
+                    message: None,
+                    subcommands: vec![],
+                    label: Some(OnUnimplementedFormatString::try_parse(
+                        tcx,
+                        item_def_id,
+                        value,
+                        attr.span,
+                    )?),
+                    note: None,
+                    parent_label: None,
+                    append_const_msg: None,
+                }))
+            } else {
+                tcx.emit_spanned_lint(
+                    UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                    tcx.hir().local_def_id_to_hir_id(item_def_id.expect_local()),
                     attr.span,
-                )?),
-                note: None,
-                parent_label: None,
-                append_const_msg: None,
-            }))
+                    NoValueInOnUnimplementedLint,
+                );
+                Ok(None)
+            }
+        } else if is_diagnostic_namespace_variant {
+            tcx.emit_spanned_lint(
+                UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
+                tcx.hir().local_def_id_to_hir_id(item_def_id.expect_local()),
+                attr.span,
+                NoValueInOnUnimplementedLint,
+            );
+            Ok(None)
         } else {
             let reported =
                 tcx.sess.delay_span_bug(DUMMY_SP, "of_item: neither meta_item_list nor value_str");
diff --git a/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.rs b/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.rs
index a686ed9c84e..b08e291621f 100644
--- a/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.rs
+++ b/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.rs
@@ -1,12 +1,12 @@
 #[diagnostic::non_existing_attribute]
 //~^ERROR `#[diagnostic]` attribute name space is experimental [E0658]
-//~|WARNING unknown diagnostic attribute [unknown_diagnostic_attributes]
+//~|WARNING unknown diagnostic attribute [unknown_or_malformed_diagnostic_attributes]
 pub trait Bar {
 }
 
 #[diagnostic::non_existing_attribute(with_option = "foo")]
 //~^ERROR `#[diagnostic]` attribute name space is experimental [E0658]
-//~|WARNING unknown diagnostic attribute [unknown_diagnostic_attributes]
+//~|WARNING unknown diagnostic attribute [unknown_or_malformed_diagnostic_attributes]
 struct Foo;
 
 fn main() {
diff --git a/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.stderr b/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.stderr
index 45c95cbb3c7..017d00e2c8e 100644
--- a/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.stderr
+++ b/tests/ui/diagnostic_namespace/feature-gate-diagnostic_namespace.stderr
@@ -4,7 +4,7 @@ error[E0658]: `#[diagnostic]` attribute name space is experimental
 LL | #[diagnostic::non_existing_attribute]
    |   ^^^^^^^^^^
    |
-   = note: see issue #94785 <https://github.com/rust-lang/rust/issues/94785> for more information
+   = note: see issue #111996 <https://github.com/rust-lang/rust/issues/111996> for more information
    = help: add `#![feature(diagnostic_namespace)]` to the crate attributes to enable
 
 error[E0658]: `#[diagnostic]` attribute name space is experimental
@@ -13,7 +13,7 @@ error[E0658]: `#[diagnostic]` attribute name space is experimental
 LL | #[diagnostic::non_existing_attribute(with_option = "foo")]
    |   ^^^^^^^^^^
    |
-   = note: see issue #94785 <https://github.com/rust-lang/rust/issues/94785> for more information
+   = note: see issue #111996 <https://github.com/rust-lang/rust/issues/111996> for more information
    = help: add `#![feature(diagnostic_namespace)]` to the crate attributes to enable
 
 warning: unknown diagnostic attribute
@@ -22,7 +22,7 @@ warning: unknown diagnostic attribute
 LL | #[diagnostic::non_existing_attribute]
    |               ^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: `#[warn(unknown_diagnostic_attributes)]` on by default
+   = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default
 
 warning: unknown diagnostic attribute
   --> $DIR/feature-gate-diagnostic_namespace.rs:7:15
diff --git a/tests/ui/diagnostic_namespace/non_existing_attributes_accepted.stderr b/tests/ui/diagnostic_namespace/non_existing_attributes_accepted.stderr
index 4f9b7ba2bcf..753077b365e 100644
--- a/tests/ui/diagnostic_namespace/non_existing_attributes_accepted.stderr
+++ b/tests/ui/diagnostic_namespace/non_existing_attributes_accepted.stderr
@@ -4,7 +4,7 @@ warning: unknown diagnostic attribute
 LL | #[diagnostic::non_existing_attribute]
    |               ^^^^^^^^^^^^^^^^^^^^^^
    |
-   = note: `#[warn(unknown_diagnostic_attributes)]` on by default
+   = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default
 
 warning: unknown diagnostic attribute
   --> $DIR/non_existing_attributes_accepted.rs:8:15
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.rs b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.rs
new file mode 100644
index 00000000000..00fb59d14d7
--- /dev/null
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.rs
@@ -0,0 +1,37 @@
+#![feature(diagnostic_namespace)]
+
+#[diagnostic::on_unimplemented(unsupported = "foo")]
+//~^WARN malformed `on_unimplemented` attribute
+//~|WARN malformed `on_unimplemented` attribute
+trait Foo {}
+
+#[diagnostic::on_unimplemented(message = "Baz")]
+//~^WARN `#[diagnostic::on_unimplemented]` can only be applied to trait definitions
+struct Bar {}
+
+#[diagnostic::on_unimplemented(message = "Boom", unsupported = "Bar")]
+//~^WARN malformed `on_unimplemented` attribute
+//~|WARN malformed `on_unimplemented` attribute
+trait Baz {}
+
+#[diagnostic::on_unimplemented(message = "Boom", on(_Self = "i32", message = "whatever"))]
+//~^WARN malformed `on_unimplemented` attribute
+//~|WARN malformed `on_unimplemented` attribute
+trait Boom {}
+
+#[diagnostic::on_unimplemented = "boom"]
+//~^WARN malformed `on_unimplemented` attribute
+trait Doom {}
+
+fn take_foo(_: impl Foo) {}
+fn take_baz(_: impl Baz) {}
+fn take_boom(_: impl Boom) {}
+
+fn main() {
+    take_foo(1_i32);
+    //~^ERROR the trait bound `i32: Foo` is not satisfied
+    take_baz(1_i32);
+    //~^ERROR Boom
+    take_boom(1_i32);
+    //~^ERROR Boom
+}
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr
new file mode 100644
index 00000000000..bb1b29ef248
--- /dev/null
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/do_not_fail_parsing_on_invalid_options_1.stderr
@@ -0,0 +1,110 @@
+warning: `#[diagnostic::on_unimplemented]` can only be applied to trait definitions
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:8:1
+   |
+LL | #[diagnostic::on_unimplemented(message = "Baz")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default
+
+warning: malformed `on_unimplemented` attribute
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:3:32
+   |
+LL | #[diagnostic::on_unimplemented(unsupported = "foo")]
+   |                                ^^^^^^^^^^^^^^^^^^^
+
+warning: malformed `on_unimplemented` attribute
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:12:50
+   |
+LL | #[diagnostic::on_unimplemented(message = "Boom", unsupported = "Bar")]
+   |                                                  ^^^^^^^^^^^^^^^^^^^
+
+warning: malformed `on_unimplemented` attribute
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:17:50
+   |
+LL | #[diagnostic::on_unimplemented(message = "Boom", on(_Self = "i32", message = "whatever"))]
+   |                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: malformed `on_unimplemented` attribute
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:22:1
+   |
+LL | #[diagnostic::on_unimplemented = "boom"]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: malformed `on_unimplemented` attribute
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:3:32
+   |
+LL | #[diagnostic::on_unimplemented(unsupported = "foo")]
+   |                                ^^^^^^^^^^^^^^^^^^^
+
+error[E0277]: the trait bound `i32: Foo` is not satisfied
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:31:14
+   |
+LL |     take_foo(1_i32);
+   |     -------- ^^^^^ the trait `Foo` is not implemented for `i32`
+   |     |
+   |     required by a bound introduced by this call
+   |
+help: this trait has no implementations, consider adding one
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:6:1
+   |
+LL | trait Foo {}
+   | ^^^^^^^^^
+note: required by a bound in `take_foo`
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:26:21
+   |
+LL | fn take_foo(_: impl Foo) {}
+   |                     ^^^ required by this bound in `take_foo`
+
+warning: malformed `on_unimplemented` attribute
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:12:50
+   |
+LL | #[diagnostic::on_unimplemented(message = "Boom", unsupported = "Bar")]
+   |                                                  ^^^^^^^^^^^^^^^^^^^
+
+error[E0277]: Boom
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:33:14
+   |
+LL |     take_baz(1_i32);
+   |     -------- ^^^^^ the trait `Baz` is not implemented for `i32`
+   |     |
+   |     required by a bound introduced by this call
+   |
+help: this trait has no implementations, consider adding one
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:15:1
+   |
+LL | trait Baz {}
+   | ^^^^^^^^^
+note: required by a bound in `take_baz`
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:27:21
+   |
+LL | fn take_baz(_: impl Baz) {}
+   |                     ^^^ required by this bound in `take_baz`
+
+warning: malformed `on_unimplemented` attribute
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:17:50
+   |
+LL | #[diagnostic::on_unimplemented(message = "Boom", on(_Self = "i32", message = "whatever"))]
+   |                                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error[E0277]: Boom
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:35:15
+   |
+LL |     take_boom(1_i32);
+   |     --------- ^^^^^ the trait `Boom` is not implemented for `i32`
+   |     |
+   |     required by a bound introduced by this call
+   |
+help: this trait has no implementations, consider adding one
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:20:1
+   |
+LL | trait Boom {}
+   | ^^^^^^^^^^
+note: required by a bound in `take_boom`
+  --> $DIR/do_not_fail_parsing_on_invalid_options_1.rs:28:22
+   |
+LL | fn take_boom(_: impl Boom) {}
+   |                      ^^^^ required by this bound in `take_boom`
+
+error: aborting due to 3 previous errors; 8 warnings emitted
+
+For more information about this error, try `rustc --explain E0277`.
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/feature-gate-diagnostic_on_unimplemented.rs b/tests/ui/diagnostic_namespace/on_unimplemented/feature-gate-diagnostic_on_unimplemented.rs
new file mode 100644
index 00000000000..609a840c118
--- /dev/null
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/feature-gate-diagnostic_on_unimplemented.rs
@@ -0,0 +1,7 @@
+#[diagnostic::on_unimplemented(message = "Foo")]
+//~^ERROR `#[diagnostic]` attribute name space is experimental [E0658]
+pub trait Bar {
+}
+
+fn main() {
+}
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/feature-gate-diagnostic_on_unimplemented.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/feature-gate-diagnostic_on_unimplemented.stderr
new file mode 100644
index 00000000000..21f02e3a73b
--- /dev/null
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/feature-gate-diagnostic_on_unimplemented.stderr
@@ -0,0 +1,12 @@
+error[E0658]: `#[diagnostic]` attribute name space is experimental
+  --> $DIR/feature-gate-diagnostic_on_unimplemented.rs:1:3
+   |
+LL | #[diagnostic::on_unimplemented(message = "Foo")]
+   |   ^^^^^^^^^^
+   |
+   = note: see issue #111996 <https://github.com/rust-lang/rust/issues/111996> for more information
+   = help: add `#![feature(diagnostic_namespace)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/on_unimplemented_simple.rs b/tests/ui/diagnostic_namespace/on_unimplemented/on_unimplemented_simple.rs
new file mode 100644
index 00000000000..797edbc9ec5
--- /dev/null
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/on_unimplemented_simple.rs
@@ -0,0 +1,11 @@
+#![feature(diagnostic_namespace)]
+
+#[diagnostic::on_unimplemented(message = "Foo", label = "Bar", note = "Baz")]
+trait Foo {}
+
+fn takes_foo(_: impl Foo) {}
+
+fn main() {
+    takes_foo(());
+    //~^ERROR Foo
+}
diff --git a/tests/ui/diagnostic_namespace/on_unimplemented/on_unimplemented_simple.stderr b/tests/ui/diagnostic_namespace/on_unimplemented/on_unimplemented_simple.stderr
new file mode 100644
index 00000000000..549c7caa720
--- /dev/null
+++ b/tests/ui/diagnostic_namespace/on_unimplemented/on_unimplemented_simple.stderr
@@ -0,0 +1,24 @@
+error[E0277]: Foo
+  --> $DIR/on_unimplemented_simple.rs:9:15
+   |
+LL |     takes_foo(());
+   |     --------- ^^ Bar
+   |     |
+   |     required by a bound introduced by this call
+   |
+   = help: the trait `Foo` is not implemented for `()`
+   = note: Baz
+help: this trait has no implementations, consider adding one
+  --> $DIR/on_unimplemented_simple.rs:4:1
+   |
+LL | trait Foo {}
+   | ^^^^^^^^^
+note: required by a bound in `takes_foo`
+  --> $DIR/on_unimplemented_simple.rs:6:22
+   |
+LL | fn takes_foo(_: impl Foo) {}
+   |                      ^^^ required by this bound in `takes_foo`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0277`.