From 06f480661f66e35869521ccd60278107233f9670 Mon Sep 17 00:00:00 2001
From: David Wood <david.wood@huawei.com>
Date: Mon, 11 Jul 2022 16:28:38 +0100
Subject: [PATCH 1/5] errors: impl `IntoDiagnosticArg` for `char`

Implements `IntoDiagnosticArg` for `char` using its `Debug`
implementation and introduces a macro for those types which just
delegate the implementation to `ToString`.

Signed-off-by: David Wood <david.wood@huawei.com>
---
 compiler/rustc_errors/src/diagnostic.rs | 110 +++++++-----------------
 1 file changed, 33 insertions(+), 77 deletions(-)

diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index da321c4587509..0ce7f3c7e8270 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -40,6 +40,34 @@ pub trait IntoDiagnosticArg {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static>;
 }
 
+macro_rules! into_diagnostic_arg_using_display {
+    ($( $ty:ty ),+ $(,)?) => {
+        $(
+            impl IntoDiagnosticArg for $ty {
+                fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
+                    self.to_string().into_diagnostic_arg()
+                }
+            }
+        )+
+    }
+}
+
+into_diagnostic_arg_using_display!(
+    i8,
+    u8,
+    i16,
+    u16,
+    i32,
+    u32,
+    i64,
+    u64,
+    i128,
+    u128,
+    std::num::NonZeroU32,
+    Edition,
+    Ident,
+);
+
 impl IntoDiagnosticArg for bool {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         if self {
@@ -50,81 +78,9 @@ impl IntoDiagnosticArg for bool {
     }
 }
 
-impl IntoDiagnosticArg for i8 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for u8 {
+impl IntoDiagnosticArg for char {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for i16 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for u16 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for i32 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for u32 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for i64 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for u64 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for i128 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for u128 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for String {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self))
-    }
-}
-
-impl IntoDiagnosticArg for std::num::NonZeroU32 {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
-    }
-}
-
-impl IntoDiagnosticArg for Edition {
-    fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        DiagnosticArgValue::Str(Cow::Owned(self.to_string()))
+        DiagnosticArgValue::Str(Cow::Owned(format!("{:?}", self)))
     }
 }
 
@@ -134,15 +90,15 @@ impl IntoDiagnosticArg for Symbol {
     }
 }
 
-impl IntoDiagnosticArg for Ident {
+impl<'a> IntoDiagnosticArg for &'a str {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
         self.to_string().into_diagnostic_arg()
     }
 }
 
-impl<'a> IntoDiagnosticArg for &'a str {
+impl IntoDiagnosticArg for String {
     fn into_diagnostic_arg(self) -> DiagnosticArgValue<'static> {
-        self.to_string().into_diagnostic_arg()
+        DiagnosticArgValue::Str(Cow::Owned(self))
     }
 }
 

From c3fdf748856fa220dc251647808db8535ac0bba2 Mon Sep 17 00:00:00 2001
From: David Wood <david.wood@huawei.com>
Date: Mon, 11 Jul 2022 16:29:24 +0100
Subject: [PATCH 2/5] errors: lint on `LintDiagnosticBuilder::build`

Apply the `#[rustc_lint_diagnostics]` attribute to
`LintDiagnosticBuilder::build` so that diagnostic migration lints will
trigger for it.

Signed-off-by: David Wood <david.wood@huawei.com>
---
 .../locales/en-US/privacy.ftl                 |  9 +++++
 .../rustc_errors/src/diagnostic_builder.rs    |  1 +
 compiler/rustc_privacy/src/errors.rs          | 18 +++++++++-
 compiler/rustc_privacy/src/lib.rs             | 35 +++++++------------
 4 files changed, 39 insertions(+), 24 deletions(-)

diff --git a/compiler/rustc_error_messages/locales/en-US/privacy.ftl b/compiler/rustc_error_messages/locales/en-US/privacy.ftl
index 2b0778f48caee..f8a750da93f8d 100644
--- a/compiler/rustc_error_messages/locales/en-US/privacy.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/privacy.ftl
@@ -10,3 +10,12 @@ privacy-unnamed-item-is-private = {$kind} is private
 privacy-in-public-interface = {$vis_descr} {$kind} `{$descr}` in public interface
     .label = can't leak {$vis_descr} {$kind}
     .visibility-label = `{$descr}` declared as {$vis_descr}
+
+privacy-from-private-dep-in-public-interface =
+    {$kind} `{$descr}` from private dependency '{$krate}' in public interface
+
+private-in-public-lint =
+    {$vis_descr} {$kind} `{$descr}` in public interface (error {$kind ->
+        [trait] E0445
+        *[other] E0446
+    })
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index 99ac6a3546ed6..9e68ee282e652 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -595,6 +595,7 @@ macro_rules! error_code {
 pub struct LintDiagnosticBuilder<'a, G: EmissionGuarantee>(DiagnosticBuilder<'a, G>);
 
 impl<'a, G: EmissionGuarantee> LintDiagnosticBuilder<'a, G> {
+    #[rustc_lint_diagnostics]
     /// Return the inner `DiagnosticBuilder`, first setting the primary message to `msg`.
     pub fn build(mut self, msg: impl Into<DiagnosticMessage>) -> DiagnosticBuilder<'a, G> {
         self.0.set_primary_message(msg);
diff --git a/compiler/rustc_privacy/src/errors.rs b/compiler/rustc_privacy/src/errors.rs
index 482721d373ab7..b0fac91f6ebc3 100644
--- a/compiler/rustc_privacy/src/errors.rs
+++ b/compiler/rustc_privacy/src/errors.rs
@@ -1,4 +1,4 @@
-use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic};
+use rustc_macros::{LintDiagnostic, SessionDiagnostic, SessionSubdiagnostic};
 use rustc_span::{Span, Symbol};
 
 #[derive(SessionDiagnostic)]
@@ -73,3 +73,19 @@ pub struct InPublicInterface<'a> {
     #[label(privacy::visibility_label)]
     pub vis_span: Span,
 }
+
+#[derive(LintDiagnostic)]
+#[lint(privacy::from_private_dep_in_public_interface)]
+pub struct FromPrivateDependencyInPublicInterface<'a> {
+    pub kind: &'a str,
+    pub descr: String,
+    pub krate: Symbol,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(privacy::private_in_public_lint)]
+pub struct PrivateInPublicLint<'a> {
+    pub vis_descr: &'static str,
+    pub kind: &'a str,
+    pub descr: String,
+}
diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs
index 5b21c04664774..9a835808d4935 100644
--- a/compiler/rustc_privacy/src/lib.rs
+++ b/compiler/rustc_privacy/src/lib.rs
@@ -38,8 +38,8 @@ use std::ops::ControlFlow;
 use std::{cmp, fmt, mem};
 
 use errors::{
-    FieldIsPrivate, FieldIsPrivateLabel, InPublicInterface, InPublicInterfaceTraits, ItemIsPrivate,
-    UnnamedItemIsPrivate,
+    FieldIsPrivate, FieldIsPrivateLabel, FromPrivateDependencyInPublicInterface, InPublicInterface,
+    InPublicInterfaceTraits, ItemIsPrivate, PrivateInPublicLint, UnnamedItemIsPrivate,
 };
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1716,19 +1716,14 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> {
 
     fn check_def_id(&mut self, def_id: DefId, kind: &str, descr: &dyn fmt::Display) -> bool {
         if self.leaks_private_dep(def_id) {
-            self.tcx.struct_span_lint_hir(
+            self.tcx.emit_spanned_lint(
                 lint::builtin::EXPORTED_PRIVATE_DEPENDENCIES,
                 self.tcx.hir().local_def_id_to_hir_id(self.item_def_id),
                 self.tcx.def_span(self.item_def_id.to_def_id()),
-                |lint| {
-                    lint.build(&format!(
-                        "{} `{}` from private dependency '{}' in public \
-                                                interface",
-                        kind,
-                        descr,
-                        self.tcx.crate_name(def_id.krate)
-                    ))
-                    .emit();
+                FromPrivateDependencyInPublicInterface {
+                    kind,
+                    descr: descr.to_string(),
+                    krate: self.tcx.crate_name(def_id.krate),
                 },
             );
         }
@@ -1754,12 +1749,14 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> {
                 }
             };
             let span = self.tcx.def_span(self.item_def_id.to_def_id());
+            let descr = descr.to_string();
             if self.has_old_errors
                 || self.in_assoc_ty
                 || self.tcx.resolutions(()).has_pub_restricted
             {
                 let descr = descr.to_string();
-                let vis_span = self.tcx.def_span(def_id);
+                let vis_span =
+                    self.tcx.sess.source_map().guess_head_span(self.tcx.def_span(def_id));
                 if kind == "trait" {
                     self.tcx.sess.emit_err(InPublicInterfaceTraits {
                         span,
@@ -1778,19 +1775,11 @@ impl SearchInterfaceForPrivateItemsVisitor<'_> {
                     });
                 }
             } else {
-                let err_code = if kind == "trait" { "E0445" } else { "E0446" };
-                self.tcx.struct_span_lint_hir(
+                self.tcx.emit_spanned_lint(
                     lint::builtin::PRIVATE_IN_PUBLIC,
                     hir_id,
                     span,
-                    |lint| {
-                        lint.build(&format!(
-                            "{} (error {})",
-                            format!("{} {} `{}` in public interface", vis_descr, kind, descr),
-                            err_code
-                        ))
-                        .emit();
-                    },
+                    PrivateInPublicLint { vis_descr, kind, descr },
                 );
             }
         }

From 88c11c5bfffea445c6cc49b62da17f172eb8f055 Mon Sep 17 00:00:00 2001
From: David Wood <david.wood@huawei.com>
Date: Mon, 11 Jul 2022 17:15:31 +0100
Subject: [PATCH 3/5] macros: support `MultiSpan` in diag derives

Add support for `MultiSpan` with any of the attributes that work on a
`Span` - requires that diagnostic logic generated for these attributes
are emitted in the by-move block rather than the by-ref block that they
would normally have been generated in.

Signed-off-by: David Wood <david.wood@huawei.com>
---
 .../src/diagnostics/diagnostic_builder.rs     | 139 ++++++++++--------
 .../rustc_macros/src/diagnostics/utils.rs     |   8 +-
 .../session-diagnostic/diagnostic-derive.rs   |  15 +-
 .../diagnostic-derive.stderr                  |   6 +-
 .../subdiagnostic-derive.rs                   |   2 +-
 .../subdiagnostic-derive.stderr               |   2 +-
 6 files changed, 104 insertions(+), 68 deletions(-)

diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
index 74ce1ab08c264..db3ba20d8f9d8 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -13,7 +13,8 @@ use quote::{format_ident, quote};
 use std::collections::HashMap;
 use std::str::FromStr;
 use syn::{
-    parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
+    parse_quote, spanned::Spanned, Attribute, Field, Meta, MetaList, MetaNameValue, NestedMeta,
+    Path, Type,
 };
 use synstructure::{BindingInfo, Structure};
 
@@ -80,8 +81,8 @@ impl DiagnosticDeriveBuilder {
     }
 
     pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, TokenStream) {
-        // Keep track of which fields are subdiagnostics or have no attributes.
-        let mut subdiagnostics_or_empty = std::collections::HashSet::new();
+        // Keep track of which fields need to be handled with a by-move binding.
+        let mut needs_moved = std::collections::HashSet::new();
 
         // Generates calls to `span_label` and similar functions based on the attributes
         // on fields. Code for suggestions uses formatting machinery and the value of
@@ -92,16 +93,11 @@ impl DiagnosticDeriveBuilder {
         let attrs = structure
             .clone()
             .filter(|field_binding| {
-                let attrs = &field_binding.ast().attrs;
-
-                (!attrs.is_empty()
-                    && attrs.iter().all(|attr| {
-                        "subdiagnostic" != attr.path.segments.last().unwrap().ident.to_string()
-                    }))
-                    || {
-                        subdiagnostics_or_empty.insert(field_binding.binding.clone());
-                        false
-                    }
+                let ast = &field_binding.ast();
+                !self.needs_move(ast) || {
+                    needs_moved.insert(field_binding.binding.clone());
+                    false
+                }
             })
             .each(|field_binding| self.generate_field_attrs_code(field_binding));
 
@@ -111,12 +107,41 @@ impl DiagnosticDeriveBuilder {
         // attributes or a `#[subdiagnostic]` attribute then it must be passed as an
         // argument to the diagnostic so that it can be referred to by Fluent messages.
         let args = structure
-            .filter(|field_binding| subdiagnostics_or_empty.contains(&field_binding.binding))
+            .filter(|field_binding| needs_moved.contains(&field_binding.binding))
             .each(|field_binding| self.generate_field_attrs_code(field_binding));
 
         (attrs, args)
     }
 
+    /// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
+    /// call (like `span_label`).
+    fn should_generate_set_arg(&self, field: &Field) -> bool {
+        field.attrs.is_empty()
+    }
+
+    /// Returns `true` if `field` needs to have code generated in the by-move branch of the
+    /// generated derive rather than the by-ref branch.
+    fn needs_move(&self, field: &Field) -> bool {
+        let generates_set_arg = self.should_generate_set_arg(field);
+        let is_multispan = type_matches_path(&field.ty, &["rustc_errors", "MultiSpan"]);
+        // FIXME(davidtwco): better support for one field needing to be in the by-move and
+        // by-ref branches.
+        let is_subdiagnostic = field
+            .attrs
+            .iter()
+            .map(|attr| attr.path.segments.last().unwrap().ident.to_string())
+            .any(|attr| attr == "subdiagnostic");
+
+        // `set_arg` calls take their argument by-move..
+        generates_set_arg
+            // If this is a `MultiSpan` field then it needs to be moved to be used by any
+            // attribute..
+            || is_multispan
+            // If this a `#[subdiagnostic]` then it needs to be moved as the other diagnostic is
+            // unlikely to be `Copy`..
+            || is_subdiagnostic
+    }
+
     /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
     /// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
     /// diagnostic builder calls for setting error code and creating note/help messages.
@@ -227,57 +252,55 @@ impl DiagnosticDeriveBuilder {
         let field = binding_info.ast();
         let field_binding = &binding_info.binding;
 
-        let inner_ty = FieldInnerTy::from_type(&field.ty);
-
-        // When generating `set_arg` or `add_subdiagnostic` calls, move data rather than
-        // borrow it to avoid requiring clones - this must therefore be the last use of
-        // each field (for example, any formatting machinery that might refer to a field
-        // should be generated already).
-        if field.attrs.is_empty() {
+        if self.should_generate_set_arg(&field) {
             let diag = &self.diag;
             let ident = field.ident.as_ref().unwrap();
-            quote! {
+            return quote! {
                 #diag.set_arg(
                     stringify!(#ident),
                     #field_binding
                 );
-            }
-        } else {
-            field
-                .attrs
-                .iter()
-                .map(move |attr| {
-                    let name = attr.path.segments.last().unwrap().ident.to_string();
-                    let (binding, needs_destructure) = match (name.as_str(), &inner_ty) {
-                        // `primary_span` can accept a `Vec<Span>` so don't destructure that.
-                        ("primary_span", FieldInnerTy::Vec(_)) => {
-                            (quote! { #field_binding.clone() }, false)
-                        }
-                        // `subdiagnostics` are not derefed because they are bound by value.
-                        ("subdiagnostic", _) => (quote! { #field_binding }, true),
-                        _ => (quote! { *#field_binding }, true),
-                    };
-
-                    let generated_code = self
-                        .generate_inner_field_code(
-                            attr,
-                            FieldInfo {
-                                binding: binding_info,
-                                ty: inner_ty.inner_type().unwrap_or(&field.ty),
-                                span: &field.span(),
-                            },
-                            binding,
-                        )
-                        .unwrap_or_else(|v| v.to_compile_error());
-
-                    if needs_destructure {
-                        inner_ty.with(field_binding, generated_code)
-                    } else {
-                        generated_code
-                    }
-                })
-                .collect()
+            };
         }
+
+        let needs_move = self.needs_move(&field);
+        let inner_ty = FieldInnerTy::from_type(&field.ty);
+
+        field
+            .attrs
+            .iter()
+            .map(move |attr| {
+                let name = attr.path.segments.last().unwrap().ident.to_string();
+                let needs_clone =
+                    name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_));
+                let (binding, needs_destructure) = if needs_clone {
+                    // `primary_span` can accept a `Vec<Span>` so don't destructure that.
+                    (quote! { #field_binding.clone() }, false)
+                } else if needs_move {
+                    (quote! { #field_binding }, true)
+                } else {
+                    (quote! { *#field_binding }, true)
+                };
+
+                let generated_code = self
+                    .generate_inner_field_code(
+                        attr,
+                        FieldInfo {
+                            binding: binding_info,
+                            ty: inner_ty.inner_type().unwrap_or(&field.ty),
+                            span: &field.span(),
+                        },
+                        binding,
+                    )
+                    .unwrap_or_else(|v| v.to_compile_error());
+
+                if needs_destructure {
+                    inner_ty.with(field_binding, generated_code)
+                } else {
+                    generated_code
+                }
+            })
+            .collect()
     }
 
     fn generate_inner_field_code(
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
index 8977db4606c56..002abb152f759 100644
--- a/compiler/rustc_macros/src/diagnostics/utils.rs
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -85,7 +85,13 @@ pub(crate) fn report_error_if_not_applied_to_span(
     attr: &Attribute,
     info: &FieldInfo<'_>,
 ) -> Result<(), DiagnosticDeriveError> {
-    report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "`Span`")
+    if !type_matches_path(&info.ty, &["rustc_span", "Span"])
+        && !type_matches_path(&info.ty, &["rustc_errors", "MultiSpan"])
+    {
+        report_type_error(attr, "`Span` or `MultiSpan`")?;
+    }
+
+    Ok(())
 }
 
 /// Inner type of a field and type of wrapper.
diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
index 56e95d70fd53d..b343bcb772142 100644
--- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
+++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
@@ -23,7 +23,7 @@ extern crate rustc_middle;
 use rustc_middle::ty::Ty;
 
 extern crate rustc_errors;
-use rustc_errors::Applicability;
+use rustc_errors::{Applicability, MultiSpan};
 
 extern crate rustc_session;
 
@@ -140,7 +140,7 @@ struct CodeNotProvided {}
 #[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
 struct MessageWrongType {
     #[primary_span]
-    //~^ ERROR `#[primary_span]` attribute can only be applied to fields of type `Span`
+    //~^ ERROR `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
     foo: String,
 }
 
@@ -165,7 +165,7 @@ struct ErrorWithField {
 #[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
 struct ErrorWithMessageAppliedToField {
     #[label(typeck::label)]
-    //~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span`
+    //~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
     name: String,
 }
 
@@ -208,7 +208,7 @@ struct LabelOnSpan {
 #[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
 struct LabelOnNonSpan {
     #[label(typeck::label)]
-    //~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span`
+    //~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
     id: u32,
 }
 
@@ -552,3 +552,10 @@ struct LintsGood {
 //~^ ERROR only `#[lint(..)]` is supported
 struct ErrorsBad {
 }
+
+#[derive(SessionDiagnostic)]
+#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
+struct ErrorWithMultiSpan {
+    #[primary_span]
+    span: MultiSpan,
+}
diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
index e282884289db3..e2580c6485a95 100644
--- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
+++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
@@ -233,7 +233,7 @@ LL | | struct SlugNotProvided {}
    |
    = help: specify the slug as the first argument to the attribute, such as `#[error(typeck::example_error)]`
 
-error: the `#[primary_span]` attribute can only be applied to fields of type `Span`
+error: the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
   --> $DIR/diagnostic-derive.rs:142:5
    |
 LL |     #[primary_span]
@@ -247,7 +247,7 @@ LL |     #[nonsense]
    |
    = help: only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes
 
-error: the `#[label(...)]` attribute can only be applied to fields of type `Span`
+error: the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
   --> $DIR/diagnostic-derive.rs:167:5
    |
 LL |     #[label(typeck::label)]
@@ -279,7 +279,7 @@ LL | #[derive(SessionDiagnostic)]
    = note: if you intended to print `}`, you can escape it using `}}`
    = note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error: the `#[label(...)]` attribute can only be applied to fields of type `Span`
+error: the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
   --> $DIR/diagnostic-derive.rs:210:5
    |
 LL |     #[label(typeck::label)]
diff --git a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
index 6f4b6105b3e49..cbb66c13c680c 100644
--- a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
+++ b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
@@ -244,7 +244,7 @@ enum V {
 //~^ ERROR label without `#[primary_span]` field
 struct W {
     #[primary_span]
-    //~^ ERROR the `#[primary_span]` attribute can only be applied to fields of type `Span`
+    //~^ ERROR the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
     span: String,
 }
 
diff --git a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr
index f833bd210f7f5..a289c4fffd936 100644
--- a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr
+++ b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr
@@ -120,7 +120,7 @@ error: subdiagnostic kind not specified
 LL |     B {
    |     ^
 
-error: the `#[primary_span]` attribute can only be applied to fields of type `Span`
+error: the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
   --> $DIR/subdiagnostic-derive.rs:246:5
    |
 LL |     #[primary_span]

From 81cf2294b4d912ec410696c5e2dec7659243d191 Mon Sep 17 00:00:00 2001
From: David Wood <david.wood@huawei.com>
Date: Mon, 11 Jul 2022 18:46:24 +0100
Subject: [PATCH 4/5] macros: support adding warnings to diags

Both diagnostic and subdiagnostic derives were missing the ability to
add warnings to diagnostics - this is made more difficult by the `warn`
attribute already existing, so this name being unavailable for the
derives to use. `#[warn_]` is used instead, which requires
special-casing so that `{span_,}warn` is called instead of
`{span_,}warn_`.

Signed-off-by: David Wood <david.wood@huawei.com>
---
 .../src/diagnostics/diagnostic.rs             |  2 +-
 .../src/diagnostics/diagnostic_builder.rs     | 51 ++++++++++++-------
 .../rustc_macros/src/diagnostics/fluent.rs    |  6 ++-
 .../src/diagnostics/subdiagnostic.rs          |  4 ++
 compiler/rustc_macros/src/lib.rs              |  7 ++-
 .../session-diagnostic/diagnostic-derive.rs   |  9 +++-
 .../diagnostic-derive.stderr                  |  4 +-
 .../subdiagnostic-derive.rs                   | 12 +++++
 8 files changed, 70 insertions(+), 25 deletions(-)

diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic.rs b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
index 027f377b0acc7..6b5b8b5932018 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic.rs
@@ -59,7 +59,7 @@ impl<'a> SessionDiagnosticDerive<'a> {
                         return DiagnosticDeriveError::ErrorHandled.to_compile_error();
                     }
                     (Some(DiagnosticDeriveKind::Lint), _) => {
-                        span_err(span, "only `#[error(..)]` and `#[warn(..)]` are supported")
+                        span_err(span, "only `#[error(..)]` and `#[warning(..)]` are supported")
                             .help("use the `#[error(...)]` attribute to create a error")
                             .emit();
                         return DiagnosticDeriveError::ErrorHandled.to_compile_error();
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
index db3ba20d8f9d8..5c5275b7cfb92 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -8,7 +8,7 @@ use crate::diagnostics::utils::{
     report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path,
     Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
 };
-use proc_macro2::{Ident, TokenStream};
+use proc_macro2::{Ident, Span, TokenStream};
 use quote::{format_ident, quote};
 use std::collections::HashMap;
 use std::str::FromStr;
@@ -156,7 +156,7 @@ impl DiagnosticDeriveBuilder {
         let name = name.as_str();
         let meta = attr.parse_meta()?;
 
-        let is_help_or_note = matches!(name, "help" | "note");
+        let is_help_note_or_warn = matches!(name, "help" | "note" | "warn_");
 
         let nested = match meta {
             // Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` for most cases or
@@ -164,8 +164,12 @@ impl DiagnosticDeriveBuilder {
             Meta::List(MetaList { ref nested, .. }) => nested,
             // Subdiagnostics without spans can be applied to the type too, and these are just
             // paths: `#[help]` and `#[note]`
-            Meta::Path(_) if is_help_or_note => {
-                let fn_name = proc_macro2::Ident::new(name, attr.span());
+            Meta::Path(_) if is_help_note_or_warn => {
+                let fn_name = if name == "warn_" {
+                    Ident::new("warn", attr.span())
+                } else {
+                    Ident::new(name, attr.span())
+                };
                 return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::_subdiag::#fn_name); });
             }
             _ => throw_invalid_attr!(attr, &meta),
@@ -177,9 +181,11 @@ impl DiagnosticDeriveBuilder {
             "error" => self.kind.set_once((DiagnosticDeriveKind::Error, span)),
             "warning" => self.kind.set_once((DiagnosticDeriveKind::Warn, span)),
             "lint" => self.kind.set_once((DiagnosticDeriveKind::Lint, span)),
-            "help" | "note" => (),
+            "help" | "note" | "warn_" => (),
             _ => throw_invalid_attr!(attr, &meta, |diag| {
-                diag.help("only `error`, `warning`, `help` and `note` are valid attributes")
+                diag.help(
+                    "only `error`, `warning`, `help`, `note` and `warn_` are valid attributes",
+                )
             }),
         }
 
@@ -188,14 +194,16 @@ impl DiagnosticDeriveBuilder {
         let mut nested_iter = nested.into_iter();
         if let Some(nested_attr) = nested_iter.next() {
             // Report an error if there are any other list items after the path.
-            if is_help_or_note && nested_iter.next().is_some() {
+            if is_help_note_or_warn && nested_iter.next().is_some() {
                 throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                    diag.help("`help` and `note` struct attributes can only have one argument")
+                    diag.help(
+                        "`help`, `note` and `warn_` struct attributes can only have one argument",
+                    )
                 });
             }
 
             match nested_attr {
-                NestedMeta::Meta(Meta::Path(path)) if is_help_or_note => {
+                NestedMeta::Meta(Meta::Path(path)) if is_help_note_or_warn => {
                     let fn_name = proc_macro2::Ident::new(name, attr.span());
                     return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); });
                 }
@@ -203,7 +211,7 @@ impl DiagnosticDeriveBuilder {
                     self.slug.set_once((path.clone(), span));
                 }
                 NestedMeta::Meta(meta @ Meta::NameValue(_))
-                    if !is_help_or_note
+                    if !is_help_note_or_warn
                         && meta.path().segments.last().unwrap().ident.to_string() == "code" =>
                 {
                     // don't error for valid follow-up attributes
@@ -347,10 +355,12 @@ impl DiagnosticDeriveBuilder {
                 report_error_if_not_applied_to_span(attr, &info)?;
                 Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label }))
             }
-            "note" | "help" => {
-                let path = match name {
-                    "note" => parse_quote! { _subdiag::note },
-                    "help" => parse_quote! { _subdiag::help },
+            "note" | "help" | "warn_" => {
+                let warn_ident = Ident::new("warn", Span::call_site());
+                let (ident, path) = match name {
+                    "note" => (ident, parse_quote! { _subdiag::note }),
+                    "help" => (ident, parse_quote! { _subdiag::help }),
+                    "warn_" => (&warn_ident, parse_quote! { _subdiag::warn }),
                     _ => unreachable!(),
                 };
                 if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
@@ -387,10 +397,10 @@ impl DiagnosticDeriveBuilder {
             "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => {
                 return self.generate_inner_field_code_suggestion(attr, info);
             }
-            "label" | "help" | "note" => (),
+            "label" | "help" | "note" | "warn_" => (),
             _ => throw_invalid_attr!(attr, &meta, |diag| {
                 diag.help(
-                    "only `label`, `note`, `help` or `suggestion{,_short,_hidden,_verbose}` are \
+                    "only `label`, `help`, `note`, `warn` or `suggestion{,_short,_hidden,_verbose}` are \
                      valid field attributes",
                 )
             }),
@@ -419,7 +429,14 @@ impl DiagnosticDeriveBuilder {
                 Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
             }
             "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)),
-            "note" | "help" => report_type_error(attr, "`Span` or `()`")?,
+            // `warn_` must be special-cased because the attribute `warn` already has meaning and
+            // so isn't used, despite the diagnostic API being named `warn`.
+            "warn_" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => Ok(self
+                .add_spanned_subdiagnostic(binding, &Ident::new("warn", Span::call_site()), msg)),
+            "warn_" if type_is_unit(&info.ty) => {
+                Ok(self.add_subdiagnostic(&Ident::new("warn", Span::call_site()), msg))
+            }
+            "note" | "help" | "warn_" => report_type_error(attr, "`Span` or `()`")?,
             _ => unreachable!(),
         }
     }
diff --git a/compiler/rustc_macros/src/diagnostics/fluent.rs b/compiler/rustc_macros/src/diagnostics/fluent.rs
index 2758fcd1310fe..1170d2b3c59a4 100644
--- a/compiler/rustc_macros/src/diagnostics/fluent.rs
+++ b/compiler/rustc_macros/src/diagnostics/fluent.rs
@@ -260,10 +260,12 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
             #generated
 
             pub mod _subdiag {
-                pub const note: crate::SubdiagnosticMessage =
-                    crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("note"));
                 pub const help: crate::SubdiagnosticMessage =
                     crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("help"));
+                pub const note: crate::SubdiagnosticMessage =
+                    crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("note"));
+                pub const warn: crate::SubdiagnosticMessage =
+                    crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("warn"));
                 pub const label: crate::SubdiagnosticMessage =
                     crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("label"));
                 pub const suggestion: crate::SubdiagnosticMessage =
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index 2a5b6beba94bb..edf4dbed9853e 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -37,6 +37,8 @@ enum SubdiagnosticKind {
     Note,
     /// `#[help(...)]`
     Help,
+    /// `#[warn_(...)]`
+    Warn,
     /// `#[suggestion{,_short,_hidden,_verbose}]`
     Suggestion(SubdiagnosticSuggestionKind),
 }
@@ -49,6 +51,7 @@ impl FromStr for SubdiagnosticKind {
             "label" => Ok(SubdiagnosticKind::Label),
             "note" => Ok(SubdiagnosticKind::Note),
             "help" => Ok(SubdiagnosticKind::Help),
+            "warn_" => Ok(SubdiagnosticKind::Warn),
             "suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)),
             "suggestion_short" => {
                 Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short))
@@ -70,6 +73,7 @@ impl quote::IdentFragment for SubdiagnosticKind {
             SubdiagnosticKind::Label => write!(f, "label"),
             SubdiagnosticKind::Note => write!(f, "note"),
             SubdiagnosticKind::Help => write!(f, "help"),
+            SubdiagnosticKind::Warn => write!(f, "warn"),
             SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal) => {
                 write!(f, "suggestion")
             }
diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs
index 168530c54b99e..ab509b26f1c55 100644
--- a/compiler/rustc_macros/src/lib.rs
+++ b/compiler/rustc_macros/src/lib.rs
@@ -130,8 +130,9 @@ decl_derive!(
         warning,
         error,
         lint,
-        note,
         help,
+        note,
+        warn_,
         // field attributes
         skip_arg,
         primary_span,
@@ -148,8 +149,9 @@ decl_derive!(
         warning,
         error,
         lint,
-        note,
         help,
+        note,
+        warn_,
         // field attributes
         skip_arg,
         primary_span,
@@ -166,6 +168,7 @@ decl_derive!(
         label,
         help,
         note,
+        warn_,
         suggestion,
         suggestion_short,
         suggestion_hidden,
diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
index b343bcb772142..0a210cbdc9430 100644
--- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
+++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
@@ -538,7 +538,7 @@ struct LabelWithTrailingList {
 
 #[derive(SessionDiagnostic)]
 #[lint(typeck::ambiguous_lifetime_bound)]
-//~^ ERROR only `#[error(..)]` and `#[warn(..)]` are supported
+//~^ ERROR only `#[error(..)]` and `#[warning(..)]` are supported
 struct LintsBad {
 }
 
@@ -559,3 +559,10 @@ struct ErrorWithMultiSpan {
     #[primary_span]
     span: MultiSpan,
 }
+
+#[derive(SessionDiagnostic)]
+#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
+#[warn_]
+struct ErrorWithWarn {
+    val: String,
+}
diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
index e2580c6485a95..c1080aa24521f 100644
--- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
+++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
@@ -21,7 +21,7 @@ error: `#[nonsense(...)]` is not a valid attribute
 LL | #[nonsense(typeck::ambiguous_lifetime_bound, code = "E0123")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
-   = help: only `error`, `warning`, `help` and `note` are valid attributes
+   = help: only `error`, `warning`, `help`, `note` and `warn_` are valid attributes
 
 error: diagnostic kind not specified
   --> $DIR/diagnostic-derive.rs:53:1
@@ -363,7 +363,7 @@ error: `#[label(...)]` is not a valid attribute
 LL |     #[label(typeck::label, foo("..."))]
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: only `#[error(..)]` and `#[warn(..)]` are supported
+error: only `#[error(..)]` and `#[warning(..)]` are supported
   --> $DIR/diagnostic-derive.rs:540:1
    |
 LL | / #[lint(typeck::ambiguous_lifetime_bound)]
diff --git a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
index cbb66c13c680c..16da25c402b57 100644
--- a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
+++ b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
@@ -508,3 +508,15 @@ enum AX {
         span: Span,
     }
 }
+
+#[derive(SessionSubdiagnostic)]
+#[warn_(parser::add_paren)]
+struct AY {
+}
+
+#[derive(SessionSubdiagnostic)]
+#[warn_(parser::add_paren)]
+struct AZ {
+    #[primary_span]
+    span: Span,
+}

From 78b19a90b7d728f3bde6a70a2509ae177561ce5f Mon Sep 17 00:00:00 2001
From: David Wood <david.wood@huawei.com>
Date: Mon, 11 Jul 2022 18:59:04 +0100
Subject: [PATCH 5/5] passes: migrate half of `check_attr`

Migrate half of the `rustc_passes::check_attr` diagnostics to using
diagnostic derives and being translatable.
---
 Cargo.lock                                    |   1 +
 .../locales/en-US/passes.ftl                  | 151 ++++
 compiler/rustc_error_messages/src/lib.rs      |   1 +
 compiler/rustc_errors/src/diagnostic.rs       |   1 +
 compiler/rustc_passes/Cargo.toml              |   1 +
 compiler/rustc_passes/src/check_attr.rs       | 690 +++++++-----------
 compiler/rustc_passes/src/errors.rs           | 362 +++++++++
 compiler/rustc_passes/src/lib.rs              |   3 +-
 src/test/rustdoc-ui/invalid-doc-attr.stderr   |  10 +-
 .../ui/attributes/invalid-doc-attr.stderr     |  10 +-
 .../ui/attributes/multiple-invalid.stderr     |   4 +-
 ...issue-43106-gating-of-builtin-attrs.stderr |  22 +-
 .../ui/future-incompatible-lint-group.stderr  |   2 +-
 src/test/ui/issues/issue-54044.stderr         |   8 +-
 src/test/ui/issues/issue-78957.stderr         |  12 +-
 src/test/ui/macros/issue-68060.stderr         |   4 +-
 .../marker-attribute-on-non-trait.rs          |  12 +-
 .../marker-attribute-on-non-trait.stderr      |  12 +-
 .../invalid-attribute.rs                      |   4 +-
 .../invalid-attribute.stderr                  |   4 +-
 .../ui/rfc-2091-track-caller/only-for-fns.rs  |   2 +-
 .../rfc-2091-track-caller/only-for-fns.stderr |   4 +-
 src/test/ui/rustdoc/doc_keyword.rs            |   2 +-
 src/test/ui/rustdoc/doc_keyword.stderr        |   6 +-
 .../target-feature/invalid-attribute.stderr   |  32 +-
 .../rustc_must_implement_one_of_misuse.rs     |   4 +-
 .../rustc_must_implement_one_of_misuse.stderr |   4 +-
 27 files changed, 844 insertions(+), 524 deletions(-)
 create mode 100644 compiler/rustc_error_messages/locales/en-US/passes.ftl
 create mode 100644 compiler/rustc_passes/src/errors.rs

diff --git a/Cargo.lock b/Cargo.lock
index 0b44201d56f0b..9a8d9d40b6e45 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4239,6 +4239,7 @@ dependencies = [
  "rustc_hir",
  "rustc_index",
  "rustc_lexer",
+ "rustc_macros",
  "rustc_middle",
  "rustc_serialize",
  "rustc_session",
diff --git a/compiler/rustc_error_messages/locales/en-US/passes.ftl b/compiler/rustc_error_messages/locales/en-US/passes.ftl
new file mode 100644
index 0000000000000..e4c9a4dad7b48
--- /dev/null
+++ b/compiler/rustc_error_messages/locales/en-US/passes.ftl
@@ -0,0 +1,151 @@
+-passes-previously-accepted =
+    this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+
+-passes-see-issue =
+    see issue #{$issue} <https://github.com/rust-lang/rust/issues/{$issue}> for more information
+
+passes-outer-crate-level-attr =
+    crate-level attribute should be an inner attribute: add an exclamation mark: `#![foo]`
+
+passes-inner-crate-level-attr =
+    crate-level attribute should be in the root module
+
+passes-ignored-attr-with-macro = `#[{$sym}]` is ignored on struct fields, match arms and macro defs
+    .warn = {-passes-previously-accepted}
+    .note = {-passes-see-issue(issue: "80564")}
+
+passes-ignored-attr = `#[{$sym}]` is ignored on struct fields and match arms
+    .warn = {-passes-previously-accepted}
+    .note = {-passes-see-issue(issue: "80564")}
+
+passes-inline-ignored-function-prototype = `#[inline]` is ignored on function prototypes
+
+passes-inline-ignored-constants = `#[inline]` is ignored on constants
+    .warn = {-passes-previously-accepted}
+    .note = {-passes-see-issue(issue: "65833")}
+
+passes-inline-not-fn-or-closure = attribute should be applied to function or closure
+    .label = not a function or closure
+
+passes-no-coverage-ignored-function-prototype = `#[no_coverage]` is ignored on function prototypes
+
+passes-no-coverage-propagate =
+    `#[no_coverage]` does not propagate into items and must be applied to the contained functions directly
+
+passes-no-coverage-fn-defn = `#[no_coverage]` may only be applied to function definitions
+
+passes-no-coverage-not-coverable = `#[no_coverage]` must be applied to coverable code
+    .label = not coverable code
+
+passes-should-be-applied-to-fn = attribute should be applied to a function definition
+    .label = not a function definition
+
+passes-naked-tracked-caller = cannot use `#[track_caller]` with `#[naked]`
+
+passes-should-be-applied-to-struct-enum = attribute should be applied to a struct or enum
+    .label = not a struct or enum
+
+passes-should-be-applied-to-trait = attribute should be applied to a trait
+    .label = not a trait
+
+passes-target-feature-on-statement = {passes-should-be-applied-to-fn}
+    .warn = {-passes-previously-accepted}
+    .label = {passes-should-be-applied-to-fn.label}
+
+passes-should-be-applied-to-static = attribute should be applied to a static
+    .label = not a static
+
+passes-doc-expect-str = doc {$attr_name} attribute expects a string: #[doc({$attr_name} = "a")]
+
+passes-doc-alias-empty = {$attr_str} attribute cannot have empty value
+
+passes-doc-alias-bad-char = {$char_} character isn't allowed in {$attr_str}
+
+passes-doc-alias-start-end = {$attr_str} cannot start or end with ' '
+
+passes-doc-alias-bad-location = {$attr_str} isn't allowed on {$location}
+
+passes-doc-alias-not-an-alias = {$attr_str} is the same as the item's name
+
+passes-doc-alias-duplicated = doc alias is duplicated
+    .label = first defined here
+
+passes-doc-alias-not-string-literal = `#[doc(alias("a"))]` expects string literals
+
+passes-doc-alias-malformed =
+    doc alias attribute expects a string `#[doc(alias = "a")]` or a list of strings `#[doc(alias("a", "b"))]`
+
+passes-doc-keyword-empty-mod = `#[doc(keyword = "...")]` should be used on empty modules
+
+passes-doc-keyword-not-mod = `#[doc(keyword = "...")]` should be used on modules
+
+passes-doc-keyword-invalid-ident = `{$doc_keyword}` is not a valid identifier
+
+passes-doc-tuple-variadic-not-first =
+    `#[doc(tuple_variadic)]` must be used on the first of a set of tuple trait impls with varying arity
+
+passes-doc-keyword-only-impl = `#[doc(keyword = "...")]` should be used on impl blocks
+
+passes-doc-inline-conflict-first = this attribute...
+passes-doc-inline-conflict-second = ...conflicts with this attribute
+passes-doc-inline-conflict = conflicting doc inlining attributes
+    .help = remove one of the conflicting attributes
+
+passes-doc-inline-only-use = this attribute can only be applied to a `use` item
+    .label = only applicable on `use` items
+    .not-a-use-item-label = not a `use` item
+    .note = read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline> for more information
+
+passes-doc-attr-not-crate-level =
+    `#![doc({$attr_name} = "...")]` isn't allowed as a crate-level attribute
+
+passes-attr-crate-level = this attribute can only be applied at the crate level
+    .suggestion = to apply to the crate, use an inner attribute
+    .help = to apply to the crate, use an inner attribute
+    .note = read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
+
+passes-doc-test-unknown = unknown `doc(test)` attribute `{$path}`
+
+passes-doc-test-takes-list = `#[doc(test(...)]` takes a list of attributes
+
+passes-doc-primitive = `doc(primitive)` should never have been stable
+
+passes-doc-test-unknown-any = unknown `doc` attribute `{$path}`
+
+passes-doc-test-unknown-spotlight = unknown `doc` attribute `{$path}`
+    .note = `doc(spotlight)` was renamed to `doc(notable_trait)`
+    .suggestion = use `notable_trait` instead
+    .no-op-note = `doc(spotlight)` is now a no-op
+
+passes-doc-test-unknown-include = unknown `doc` attribute `{$path}`
+    .suggestion = use `doc = include_str!` instead
+
+passes-doc-invalid = invalid `doc` attribute
+
+passes-pass-by-value = `pass_by_value` attribute should be applied to a struct, enum or type alias
+    .label = is not a struct, enum or type alias
+
+passes-allow-incoherent-impl =
+    `rustc_allow_incoherent_impl` attribute should be applied to impl items.
+    .label = the only currently supported targets are inherent methods
+
+passes-has-incoherent-inherent-impl =
+    `rustc_has_incoherent_inherent_impls` attribute should be applied to types or traits.
+    .label = only adts, extern types and traits are supported
+
+passes-must-use-async =
+    `must_use` attribute on `async` functions applies to the anonymous `Future` returned by the function, not the value within
+    .label = this attribute does nothing, the `Future`s returned by async functions are already `must_use`
+
+passes-must-use-no-effect = `#[must_use]` has no effect when applied to {$article} {$target}
+
+passes-must-not-suspend = `must_not_suspend` attribute should be applied to a struct, enum, or trait
+    .label = is not a struct, enum, or trait
+
+passes-cold = {passes-should-be-applied-to-fn}
+    .warn = {-passes-previously-accepted}
+    .label = {passes-should-be-applied-to-fn.label}
+
+passes-link = attribute should be applied to an `extern` block with non-Rust ABI
+    .warn = {-passes-previously-accepted}
+    .label = not an `extern` block
diff --git a/compiler/rustc_error_messages/src/lib.rs b/compiler/rustc_error_messages/src/lib.rs
index d16171cb162d6..a3040f83fdfc7 100644
--- a/compiler/rustc_error_messages/src/lib.rs
+++ b/compiler/rustc_error_messages/src/lib.rs
@@ -37,6 +37,7 @@ fluent_messages! {
     expand => "../locales/en-US/expand.ftl",
     lint => "../locales/en-US/lint.ftl",
     parser => "../locales/en-US/parser.ftl",
+    passes => "../locales/en-US/passes.ftl",
     privacy => "../locales/en-US/privacy.ftl",
     typeck => "../locales/en-US/typeck.ftl",
 }
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 0ce7f3c7e8270..267beb514847c 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -64,6 +64,7 @@ into_diagnostic_arg_using_display!(
     i128,
     u128,
     std::num::NonZeroU32,
+    hir::Target,
     Edition,
     Ident,
 );
diff --git a/compiler/rustc_passes/Cargo.toml b/compiler/rustc_passes/Cargo.toml
index 676812db59ae5..faa9c493d8875 100644
--- a/compiler/rustc_passes/Cargo.toml
+++ b/compiler/rustc_passes/Cargo.toml
@@ -15,6 +15,7 @@ rustc_hir = { path = "../rustc_hir" }
 rustc_index = { path = "../rustc_index" }
 rustc_session = { path = "../rustc_session" }
 rustc_target = { path = "../rustc_target" }
+rustc_macros = { path = "../rustc_macros" }
 rustc_ast = { path = "../rustc_ast" }
 rustc_serialize = { path = "../rustc_serialize" }
 rustc_span = { path = "../rustc_span" }
diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs
index e626a1e4ed101..d96e7d3efe83d 100644
--- a/compiler/rustc_passes/src/check_attr.rs
+++ b/compiler/rustc_passes/src/check_attr.rs
@@ -4,9 +4,10 @@
 //! conflicts between multiple such attributes attached to the same
 //! item.
 
+use crate::errors;
 use rustc_ast::{ast, AttrStyle, Attribute, Lit, LitKind, MetaItemKind, NestedMetaItem};
 use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::{pluralize, struct_span_err, Applicability, MultiSpan};
+use rustc_errors::{fluent, pluralize, struct_span_err, Applicability, MultiSpan};
 use rustc_expand::base::resolve_path;
 use rustc_feature::{AttributeDuplicates, AttributeType, BuiltinAttribute, BUILTIN_ATTRIBUTE_MAP};
 use rustc_hir as hir;
@@ -175,16 +176,20 @@ impl CheckAttrVisitor<'_> {
                 if let Some(BuiltinAttribute { type_: AttributeType::CrateLevel, .. }) =
                     attr.ident().and_then(|ident| BUILTIN_ATTRIBUTE_MAP.get(&ident.name))
                 {
-                    self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-                        let msg = match attr.style {
-                            ast::AttrStyle::Outer => {
-                                "crate-level attribute should be an inner attribute: add an exclamation \
-                                 mark: `#![foo]`"
-                            }
-                            ast::AttrStyle::Inner => "crate-level attribute should be in the root module",
-                        };
-                        lint.build(msg).emit();
-                    });
+                    match attr.style {
+                        ast::AttrStyle::Outer => self.tcx.emit_spanned_lint(
+                            UNUSED_ATTRIBUTES,
+                            hir_id,
+                            attr.span,
+                            errors::OuterCrateLevelAttr,
+                        ),
+                        ast::AttrStyle::Inner => self.tcx.emit_spanned_lint(
+                            UNUSED_ATTRIBUTES,
+                            hir_id,
+                            attr.span,
+                            errors::InnerCrateLevelAttr,
+                        ),
+                    }
                 }
             }
 
@@ -209,37 +214,21 @@ impl CheckAttrVisitor<'_> {
     }
 
     fn inline_attr_str_error_with_macro_def(&self, hir_id: HirId, attr: &Attribute, sym: &str) {
-        self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-            lint.build(&format!(
-                "`#[{sym}]` is ignored on struct fields, match arms and macro defs",
-            ))
-            .warn(
-                "this was previously accepted by the compiler but is \
-                 being phased out; it will become a hard error in \
-                 a future release!",
-            )
-            .note(
-                "see issue #80564 <https://github.com/rust-lang/rust/issues/80564> \
-                 for more information",
-            )
-            .emit();
-        });
+        self.tcx.emit_spanned_lint(
+            UNUSED_ATTRIBUTES,
+            hir_id,
+            attr.span,
+            errors::IgnoredAttrWithMacro { sym },
+        );
     }
 
     fn inline_attr_str_error_without_macro_def(&self, hir_id: HirId, attr: &Attribute, sym: &str) {
-        self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-            lint.build(&format!("`#[{sym}]` is ignored on struct fields and match arms"))
-                .warn(
-                    "this was previously accepted by the compiler but is \
-                 being phased out; it will become a hard error in \
-                 a future release!",
-                )
-                .note(
-                    "see issue #80564 <https://github.com/rust-lang/rust/issues/80564> \
-                 for more information",
-                )
-                .emit();
-        });
+        self.tcx.emit_spanned_lint(
+            UNUSED_ATTRIBUTES,
+            hir_id,
+            attr.span,
+            errors::IgnoredAttr { sym },
+        );
     }
 
     /// Checks if an `#[inline]` is applied to a function or a closure. Returns `true` if valid.
@@ -249,9 +238,12 @@ impl CheckAttrVisitor<'_> {
             | Target::Closure
             | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
             Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
-                self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-                    lint.build("`#[inline]` is ignored on function prototypes").emit();
-                });
+                self.tcx.emit_spanned_lint(
+                    UNUSED_ATTRIBUTES,
+                    hir_id,
+                    attr.span,
+                    errors::IgnoredInlineAttrFnProto,
+                );
                 true
             }
             // FIXME(#65833): We permit associated consts to have an `#[inline]` attribute with
@@ -259,19 +251,12 @@ impl CheckAttrVisitor<'_> {
             // accidentally, to to be compatible with crates depending on them, we can't throw an
             // error here.
             Target::AssocConst => {
-                self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-                    lint.build("`#[inline]` is ignored on constants")
-                        .warn(
-                            "this was previously accepted by the compiler but is \
-                             being phased out; it will become a hard error in \
-                             a future release!",
-                        )
-                        .note(
-                            "see issue #65833 <https://github.com/rust-lang/rust/issues/65833> \
-                             for more information",
-                        )
-                        .emit();
-                });
+                self.tcx.emit_spanned_lint(
+                    UNUSED_ATTRIBUTES,
+                    hir_id,
+                    attr.span,
+                    errors::IgnoredInlineAttrConstants,
+                );
                 true
             }
             // FIXME(#80564): Same for fields, arms, and macro defs
@@ -280,14 +265,10 @@ impl CheckAttrVisitor<'_> {
                 true
             }
             _ => {
-                struct_span_err!(
-                    self.tcx.sess,
-                    attr.span,
-                    E0518,
-                    "attribute should be applied to function or closure",
-                )
-                .span_label(span, "not a function or closure")
-                .emit();
+                self.tcx.sess.emit_err(errors::InlineNotFnOrClosure {
+                    attr_span: attr.span,
+                    defn_span: span,
+                });
                 false
             }
         }
@@ -309,36 +290,40 @@ impl CheckAttrVisitor<'_> {
 
             // function prototypes can't be covered
             Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
-                self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-                    lint.build("`#[no_coverage]` is ignored on function prototypes").emit();
-                });
+                self.tcx.emit_spanned_lint(
+                    UNUSED_ATTRIBUTES,
+                    hir_id,
+                    attr.span,
+                    errors::IgnoredNoCoverageFnProto,
+                );
                 true
             }
 
             Target::Mod | Target::ForeignMod | Target::Impl | Target::Trait => {
-                self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-                    lint.build("`#[no_coverage]` does not propagate into items and must be applied to the contained functions directly").emit();
-                });
+                self.tcx.emit_spanned_lint(
+                    UNUSED_ATTRIBUTES,
+                    hir_id,
+                    attr.span,
+                    errors::IgnoredNoCoveragePropagate,
+                );
                 true
             }
 
             Target::Expression | Target::Statement | Target::Arm => {
-                self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-                    lint.build("`#[no_coverage]` may only be applied to function definitions")
-                        .emit();
-                });
+                self.tcx.emit_spanned_lint(
+                    UNUSED_ATTRIBUTES,
+                    hir_id,
+                    attr.span,
+                    errors::IgnoredNoCoverageFnDefn,
+                );
                 true
             }
 
             _ => {
-                struct_span_err!(
-                    self.tcx.sess,
-                    attr.span,
-                    E0788,
-                    "`#[no_coverage]` must be applied to coverable code",
-                )
-                .span_label(span, "not coverable code")
-                .emit();
+                self.tcx.sess.emit_err(errors::IgnoredNoCoverageNotCoverable {
+                    attr_span: attr.span,
+                    defn_span: span,
+                });
                 false
             }
         }
@@ -389,14 +374,10 @@ impl CheckAttrVisitor<'_> {
                 true
             }
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(
-                        attr.span,
-                        "attribute should be applied to a function definition",
-                    )
-                    .span_label(span, "not a function definition")
-                    .emit();
+                self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn {
+                    attr_span: attr.span,
+                    defn_span: span,
+                });
                 false
             }
         }
@@ -408,14 +389,10 @@ impl CheckAttrVisitor<'_> {
             Target::Fn
             | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(
-                        attr.span,
-                        "attribute should be applied to a function definition",
-                    )
-                    .span_label(span, "not a function definition")
-                    .emit();
+                self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn {
+                    attr_span: attr.span,
+                    defn_span: span,
+                });
                 false
             }
         }
@@ -432,13 +409,7 @@ impl CheckAttrVisitor<'_> {
     ) -> bool {
         match target {
             _ if attrs.iter().any(|attr| attr.has_name(sym::naked)) => {
-                struct_span_err!(
-                    self.tcx.sess,
-                    attr_span,
-                    E0736,
-                    "cannot use `#[track_caller]` with `#[naked]`",
-                )
-                .emit();
+                self.tcx.sess.emit_err(errors::NakedTrackedCaller { attr_span });
                 false
             }
             Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => true,
@@ -453,14 +424,9 @@ impl CheckAttrVisitor<'_> {
                 true
             }
             _ => {
-                struct_span_err!(
-                    self.tcx.sess,
-                    attr_span,
-                    E0739,
-                    "attribute should be applied to function"
-                )
-                .span_label(span, "not a function")
-                .emit();
+                self.tcx
+                    .sess
+                    .emit_err(errors::TrackedCallerWrongLocation { attr_span, defn_span: span });
                 false
             }
         }
@@ -485,14 +451,10 @@ impl CheckAttrVisitor<'_> {
                 true
             }
             _ => {
-                struct_span_err!(
-                    self.tcx.sess,
-                    attr.span,
-                    E0701,
-                    "attribute can only be applied to a struct or enum"
-                )
-                .span_label(span, "not a struct or enum")
-                .emit();
+                self.tcx.sess.emit_err(errors::NonExhaustiveWrongLocation {
+                    attr_span: attr.span,
+                    defn_span: span,
+                });
                 false
             }
         }
@@ -511,11 +473,10 @@ impl CheckAttrVisitor<'_> {
                 true
             }
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(attr.span, "attribute can only be applied to a trait")
-                    .span_label(span, "not a trait")
-                    .emit();
+                self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToTrait {
+                    attr_span: attr.span,
+                    defn_span: span,
+                });
                 false
             }
         }
@@ -531,11 +492,10 @@ impl CheckAttrVisitor<'_> {
         match target {
             Target::Trait => true,
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(attr.span, "attribute can only be applied to a trait")
-                    .span_label(span, "not a trait")
-                    .emit();
+                self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToTrait {
+                    attr_span: attr.span,
+                    defn_span: span,
+                });
                 false
             }
         }
@@ -555,16 +515,12 @@ impl CheckAttrVisitor<'_> {
             // FIXME: #[target_feature] was previously erroneously allowed on statements and some
             // crates used this, so only emit a warning.
             Target::Statement => {
-                self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-                    lint.build("attribute should be applied to a function")
-                        .warn(
-                            "this was previously accepted by the compiler but is \
-                             being phased out; it will become a hard error in \
-                             a future release!",
-                        )
-                        .span_label(span, "not a function")
-                        .emit();
-                });
+                self.tcx.emit_spanned_lint(
+                    UNUSED_ATTRIBUTES,
+                    hir_id,
+                    attr.span,
+                    errors::TargetFeatureOnStatement,
+                );
                 true
             }
             // FIXME(#80564): We permit struct fields, match arms and macro defs to have an
@@ -576,11 +532,10 @@ impl CheckAttrVisitor<'_> {
                 true
             }
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(attr.span, "attribute should be applied to a function")
-                    .span_label(span, "not a function")
-                    .emit();
+                self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToFn {
+                    attr_span: attr.span,
+                    defn_span: span,
+                });
                 false
             }
         }
@@ -591,24 +546,17 @@ impl CheckAttrVisitor<'_> {
         match target {
             Target::ForeignStatic | Target::Static => true,
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(attr.span, "attribute should be applied to a static")
-                    .span_label(span, "not a static")
-                    .emit();
+                self.tcx.sess.emit_err(errors::AttrShouldBeAppliedToStatic {
+                    attr_span: attr.span,
+                    defn_span: span,
+                });
                 false
             }
         }
     }
 
     fn doc_attr_str_error(&self, meta: &NestedMetaItem, attr_name: &str) {
-        self.tcx
-            .sess
-            .struct_span_err(
-                meta.span(),
-                &format!("doc {0} attribute expects a string: #[doc({0} = \"a\")]", attr_name),
-            )
-            .emit();
+        self.tcx.sess.emit_err(errors::DocExpectStr { attr_span: meta.span(), attr_name });
     }
 
     fn check_doc_alias_value(
@@ -621,22 +569,12 @@ impl CheckAttrVisitor<'_> {
         aliases: &mut FxHashMap<String, Span>,
     ) -> bool {
         let tcx = self.tcx;
-        let err_fn = move |span: Span, msg: &str| {
-            tcx.sess.span_err(
-                span,
-                &format!(
-                    "`#[doc(alias{})]` {}",
-                    if is_list { "(\"...\")" } else { " = \"...\"" },
-                    msg,
-                ),
-            );
-            false
-        };
+        let span = meta.name_value_literal_span().unwrap_or_else(|| meta.span());
+        let attr_str =
+            &format!("`#[doc(alias{})]`", if is_list { "(\"...\")" } else { " = \"...\"" });
         if doc_alias == kw::Empty {
-            return err_fn(
-                meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
-                "attribute cannot have empty value",
-            );
+            tcx.sess.emit_err(errors::DocAliasEmpty { span, attr_str });
+            return false;
         }
 
         let doc_alias_str = doc_alias.as_str();
@@ -644,23 +582,16 @@ impl CheckAttrVisitor<'_> {
             .chars()
             .find(|&c| c == '"' || c == '\'' || (c.is_whitespace() && c != ' '))
         {
-            self.tcx.sess.span_err(
-                meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
-                &format!(
-                    "{:?} character isn't allowed in `#[doc(alias{})]`",
-                    c,
-                    if is_list { "(\"...\")" } else { " = \"...\"" },
-                ),
-            );
+            tcx.sess.emit_err(errors::DocAliasBadChar { span, attr_str, char_: c });
             return false;
         }
         if doc_alias_str.starts_with(' ') || doc_alias_str.ends_with(' ') {
-            return err_fn(
-                meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
-                "cannot start or end with ' '",
-            );
+            tcx.sess.emit_err(errors::DocAliasStartEnd { span, attr_str });
+            return false;
         }
-        if let Some(err) = match target {
+
+        let span = meta.span();
+        if let Some(location) = match target {
             Target::Impl => Some("implementation block"),
             Target::ForeignMod => Some("extern block"),
             Target::AssocTy => {
@@ -686,19 +617,21 @@ impl CheckAttrVisitor<'_> {
             Target::Param => return false,
             _ => None,
         } {
-            return err_fn(meta.span(), &format!("isn't allowed on {}", err));
+            tcx.sess.emit_err(errors::DocAliasBadLocation { span, attr_str, location });
+            return false;
         }
         let item_name = self.tcx.hir().name(hir_id);
         if item_name == doc_alias {
-            return err_fn(meta.span(), "is the same as the item's name");
+            tcx.sess.emit_err(errors::DocAliasNotAnAlias { span, attr_str });
+            return false;
         }
-        let span = meta.span();
         if let Err(entry) = aliases.try_insert(doc_alias_str.to_owned(), span) {
-            self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, span, |lint| {
-                lint.build("doc alias is duplicated")
-                    .span_label(*entry.entry.get(), "first defined here")
-                    .emit();
-            });
+            self.tcx.emit_spanned_lint(
+                UNUSED_ATTRIBUTES,
+                hir_id,
+                span,
+                errors::DocAliasDuplicated { first_defn: *entry.entry.get() },
+            );
         }
         true
     }
@@ -723,22 +656,12 @@ impl CheckAttrVisitor<'_> {
                         _ => {
                             self.tcx
                                 .sess
-                                .struct_span_err(
-                                    v.span(),
-                                    "`#[doc(alias(\"a\"))]` expects string literals",
-                                )
-                                .emit();
+                                .emit_err(errors::DocAliasNotStringLiteral { span: v.span() });
                             errors += 1;
                         }
                     },
                     None => {
-                        self.tcx
-                            .sess
-                            .struct_span_err(
-                                v.span(),
-                                "`#[doc(alias(\"a\"))]` expects string literals",
-                            )
-                            .emit();
+                        self.tcx.sess.emit_err(errors::DocAliasNotStringLiteral { span: v.span() });
                         errors += 1;
                     }
                 }
@@ -747,14 +670,7 @@ impl CheckAttrVisitor<'_> {
         } else if let Some(doc_alias) = meta.value_str() {
             self.check_doc_alias_value(meta, doc_alias, hir_id, target, false, aliases)
         } else {
-            self.tcx
-                .sess
-                .struct_span_err(
-                    meta.span(),
-                    "doc alias attribute expects a string `#[doc(alias = \"a\")]` or a list of \
-                     strings `#[doc(alias(\"a\", \"b\"))]`",
-                )
-                .emit();
+            self.tcx.sess.emit_err(errors::DocAliasMalformed { span: meta.span() });
             false
         }
     }
@@ -771,35 +687,20 @@ impl CheckAttrVisitor<'_> {
         }) {
             Some(ItemKind::Mod(ref module)) => {
                 if !module.item_ids.is_empty() {
-                    self.tcx
-                        .sess
-                        .struct_span_err(
-                            meta.span(),
-                            "`#[doc(keyword = \"...\")]` can only be used on empty modules",
-                        )
-                        .emit();
+                    self.tcx.sess.emit_err(errors::DocKeywordEmptyMod { span: meta.span() });
                     return false;
                 }
             }
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(
-                        meta.span(),
-                        "`#[doc(keyword = \"...\")]` can only be used on modules",
-                    )
-                    .emit();
+                self.tcx.sess.emit_err(errors::DocKeywordNotMod { span: meta.span() });
                 return false;
             }
         }
         if !rustc_lexer::is_ident(doc_keyword.as_str()) {
-            self.tcx
-                .sess
-                .struct_span_err(
-                    meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
-                    &format!("`{doc_keyword}` is not a valid identifier"),
-                )
-                .emit();
+            self.tcx.sess.emit_err(errors::DocKeywordInvalidIdent {
+                span: meta.name_value_literal_span().unwrap_or_else(|| meta.span()),
+                doc_keyword,
+            });
             return false;
         }
         true
@@ -812,24 +713,12 @@ impl CheckAttrVisitor<'_> {
         }) {
             Some(ItemKind::Impl(ref i)) => {
                 if !matches!(&i.self_ty.kind, hir::TyKind::Tup([_])) {
-                    self.tcx
-                        .sess
-                        .struct_span_err(
-                            meta.span(),
-                            "`#[doc(tuple_variadic)]` must be used on the first of a set of tuple trait impls with varying arity",
-                        )
-                        .emit();
+                    self.tcx.sess.emit_err(errors::DocTupleVariadicNotFirst { span: meta.span() });
                     return false;
                 }
             }
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(
-                        meta.span(),
-                        "`#[doc(keyword = \"...\")]` can only be used on impl blocks",
-                    )
-                    .emit();
+                self.tcx.sess.emit_err(errors::DocKeywordOnlyImpl { span: meta.span() });
                 return false;
             }
         }
@@ -858,13 +747,9 @@ impl CheckAttrVisitor<'_> {
             if let Some((prev_inline, prev_span)) = *specified_inline {
                 if do_inline != prev_inline {
                     let mut spans = MultiSpan::from_spans(vec![prev_span, meta.span()]);
-                    spans.push_span_label(prev_span, "this attribute...");
-                    spans.push_span_label(meta.span(), "...conflicts with this attribute");
-                    self.tcx
-                        .sess
-                        .struct_span_err(spans, "conflicting doc inlining attributes")
-                        .help("remove one of the conflicting attributes")
-                        .emit();
+                    spans.push_span_label(prev_span, fluent::passes::doc_inline_conflict_first);
+                    spans.push_span_label(meta.span(), fluent::passes::doc_inline_conflict_second);
+                    self.tcx.sess.emit_err(errors::DocKeywordConflict { spans });
                     return false;
                 }
                 true
@@ -873,23 +758,14 @@ impl CheckAttrVisitor<'_> {
                 true
             }
         } else {
-            self.tcx.struct_span_lint_hir(
+            self.tcx.emit_spanned_lint(
                 INVALID_DOC_ATTRIBUTES,
                 hir_id,
                 meta.span(),
-                |lint| {
-                    let mut err = lint.build(
-                        "this attribute can only be applied to a `use` item",
-                    );
-                    err.span_label(meta.span(), "only applicable on `use` items");
-                    if attr.style == AttrStyle::Outer {
-                        err.span_label(
-                            self.tcx.hir().span(hir_id),
-                            "not a `use` item",
-                        );
-                    }
-                    err.note("read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline for more information")
-                        .emit();
+                errors::DocInlineOnlyUse {
+                    attr_span: meta.span(),
+                    item_span: (attr.style == AttrStyle::Outer)
+                        .then(|| self.tcx.hir().span(hir_id)),
                 },
             );
             false
@@ -904,15 +780,7 @@ impl CheckAttrVisitor<'_> {
         attr_name: &str,
     ) -> bool {
         if CRATE_HIR_ID == hir_id {
-            self.tcx
-                .sess
-                .struct_span_err(
-                    meta.span(),
-                    &format!(
-                        "`#![doc({attr_name} = \"...\")]` isn't allowed as a crate-level attribute",
-                    ),
-                )
-                .emit();
+            self.tcx.sess.emit_err(errors::DocAttrNotCrateLevel { span: meta.span(), attr_name });
             return false;
         }
         true
@@ -926,36 +794,25 @@ impl CheckAttrVisitor<'_> {
         hir_id: HirId,
     ) -> bool {
         if hir_id != CRATE_HIR_ID {
-            self.tcx.struct_span_lint_hir(
-                INVALID_DOC_ATTRIBUTES,
-                hir_id,
-                meta.span(),
-                |lint| {
-                    let mut err = lint.build(
-                        "this attribute can only be applied at the crate level",
-                    );
-                    if attr.style == AttrStyle::Outer && self.tcx.hir().get_parent_item(hir_id) == CRATE_DEF_ID {
-                        if let Ok(mut src) =
-                            self.tcx.sess.source_map().span_to_snippet(attr.span)
-                        {
-                            src.insert(1, '!');
-                            err.span_suggestion_verbose(
-                                attr.span,
-                                "to apply to the crate, use an inner attribute",
-                                src,
-                                Applicability::MaybeIncorrect,
-                            );
-                        } else {
-                            err.span_help(
-                                attr.span,
-                                "to apply to the crate, use an inner attribute",
-                            );
-                        }
+            self.tcx.struct_span_lint_hir(INVALID_DOC_ATTRIBUTES, hir_id, meta.span(), |lint| {
+                let mut err = lint.build(fluent::passes::attr_crate_level);
+                if attr.style == AttrStyle::Outer
+                    && self.tcx.hir().get_parent_item(hir_id) == CRATE_DEF_ID
+                {
+                    if let Ok(mut src) = self.tcx.sess.source_map().span_to_snippet(attr.span) {
+                        src.insert(1, '!');
+                        err.span_suggestion_verbose(
+                            attr.span,
+                            fluent::passes::suggestion,
+                            src,
+                            Applicability::MaybeIncorrect,
+                        );
+                    } else {
+                        err.span_help(attr.span, fluent::passes::help);
                     }
-                    err.note("read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information")
-                        .emit();
-                },
-            );
+                }
+                err.note(fluent::passes::note).emit();
+            });
             return false;
         }
         true
@@ -970,18 +827,14 @@ impl CheckAttrVisitor<'_> {
                 match i_meta.name_or_empty() {
                     sym::attr | sym::no_crate_inject => {}
                     _ => {
-                        self.tcx.struct_span_lint_hir(
+                        self.tcx.emit_spanned_lint(
                             INVALID_DOC_ATTRIBUTES,
                             hir_id,
                             i_meta.span(),
-                            |lint| {
-                                lint.build(&format!(
-                                    "unknown `doc(test)` attribute `{}`",
-                                    rustc_ast_pretty::pprust::path_to_string(
-                                        &i_meta.meta_item().unwrap().path
-                                    ),
-                                ))
-                                .emit();
+                            errors::DocTestUnknown {
+                                path: rustc_ast_pretty::pprust::path_to_string(
+                                    &i_meta.meta_item().unwrap().path,
+                                ),
                             },
                         );
                         is_valid = false;
@@ -989,9 +842,12 @@ impl CheckAttrVisitor<'_> {
                 }
             }
         } else {
-            self.tcx.struct_span_lint_hir(INVALID_DOC_ATTRIBUTES, hir_id, meta.span(), |lint| {
-                lint.build("`#[doc(test(...)]` takes a list of attributes").emit();
-            });
+            self.tcx.emit_spanned_lint(
+                INVALID_DOC_ATTRIBUTES,
+                hir_id,
+                meta.span(),
+                errors::DocTestTakesList,
+            );
             is_valid = false;
         }
         is_valid
@@ -1093,79 +949,66 @@ impl CheckAttrVisitor<'_> {
 
                         sym::primitive => {
                             if !self.tcx.features().rustdoc_internals {
-                                self.tcx.struct_span_lint_hir(
+                                self.tcx.emit_spanned_lint(
                                     INVALID_DOC_ATTRIBUTES,
                                     hir_id,
                                     i_meta.span,
-                                    |lint| {
-                                        let mut diag = lint.build(
-                                            "`doc(primitive)` should never have been stable",
-                                        );
-                                        diag.emit();
-                                    },
+                                    errors::DocPrimitive,
                                 );
                             }
                         }
 
                         _ => {
-                            self.tcx.struct_span_lint_hir(
-                                INVALID_DOC_ATTRIBUTES,
-                                hir_id,
-                                i_meta.span,
-                                |lint| {
-                                    let mut diag = lint.build(&format!(
-                                        "unknown `doc` attribute `{}`",
-                                        rustc_ast_pretty::pprust::path_to_string(&i_meta.path),
-                                    ));
-                                    if i_meta.has_name(sym::spotlight) {
-                                        diag.note(
-                                            "`doc(spotlight)` was renamed to `doc(notable_trait)`",
-                                        );
-                                        diag.span_suggestion_short(
-                                            i_meta.span,
-                                            "use `notable_trait` instead",
-                                            "notable_trait",
-                                            Applicability::MachineApplicable,
-                                        );
-                                        diag.note("`doc(spotlight)` is now a no-op");
+                            let path = rustc_ast_pretty::pprust::path_to_string(&i_meta.path);
+                            if i_meta.has_name(sym::spotlight) {
+                                self.tcx.emit_spanned_lint(
+                                    INVALID_DOC_ATTRIBUTES,
+                                    hir_id,
+                                    i_meta.span,
+                                    errors::DocTestUnknownSpotlight {
+                                        path,
+                                        span: i_meta.span
                                     }
-                                    if i_meta.has_name(sym::include) {
-                                        if let Some(value) = i_meta.value_str() {
-                                            // if there are multiple attributes, the suggestion would suggest deleting all of them, which is incorrect
-                                            let applicability = if list.len() == 1 {
-                                                Applicability::MachineApplicable
-                                            } else {
-                                                Applicability::MaybeIncorrect
-                                            };
-                                            let inner = if attr.style == AttrStyle::Inner {
-                                                "!"
-                                            } else {
-                                                ""
-                                            };
-                                            diag.span_suggestion(
-                                                attr.meta().unwrap().span,
-                                                "use `doc = include_str!` instead",
-                                                format!(
-                                                    "#{inner}[doc = include_str!(\"{value}\")]",
-                                                ),
-                                                applicability,
-                                            );
-                                        }
+                                );
+                            } else if i_meta.has_name(sym::include) &&
+                                    let Some(value) = i_meta.value_str() {
+                                let applicability = if list.len() == 1 {
+                                    Applicability::MachineApplicable
+                                } else {
+                                    Applicability::MaybeIncorrect
+                                };
+                                // If there are multiple attributes, the suggestion would suggest
+                                // deleting all of them, which is incorrect.
+                                self.tcx.emit_spanned_lint(
+                                    INVALID_DOC_ATTRIBUTES,
+                                    hir_id,
+                                    i_meta.span,
+                                    errors::DocTestUnknownInclude {
+                                        path,
+                                        value: value.to_string(),
+                                        inner: (attr.style == AttrStyle::Inner)
+                                            .then_some("!")
+                                            .unwrap_or(""),
+                                        sugg: (attr.meta().unwrap().span, applicability),
                                     }
-                                    diag.emit();
-                                },
-                            );
+                                );
+                            } else {
+                                self.tcx.emit_spanned_lint(
+                                    INVALID_DOC_ATTRIBUTES,
+                                    hir_id,
+                                    i_meta.span,
+                                    errors::DocTestUnknownAny { path }
+                                );
+                            }
                             is_valid = false;
                         }
                     }
                 } else {
-                    self.tcx.struct_span_lint_hir(
+                    self.tcx.emit_spanned_lint(
                         INVALID_DOC_ATTRIBUTES,
                         hir_id,
                         meta.span(),
-                        |lint| {
-                            lint.build("invalid `doc` attribute").emit();
-                        },
+                        errors::DocInvalid,
                     );
                     is_valid = false;
                 }
@@ -1180,14 +1023,7 @@ impl CheckAttrVisitor<'_> {
         match target {
             Target::Struct | Target::Enum | Target::TyAlias => true,
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(
-                        attr.span,
-                        "`pass_by_value` attribute should be applied to a struct, enum or type alias.",
-                    )
-                    .span_label(span, "is not a struct, enum or type alias")
-                    .emit();
+                self.tcx.sess.emit_err(errors::PassByValue { attr_span: attr.span, span });
                 false
             }
         }
@@ -1197,14 +1033,7 @@ impl CheckAttrVisitor<'_> {
         match target {
             Target::Method(MethodKind::Inherent) => true,
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(
-                        attr.span,
-                        "`rustc_allow_incoherent_impl` attribute should be applied to impl items.",
-                    )
-                    .span_label(span, "the only currently supported targets are inherent methods")
-                    .emit();
+                self.tcx.sess.emit_err(errors::AllowIncoherentImpl { attr_span: attr.span, span });
                 false
             }
         }
@@ -1223,12 +1052,7 @@ impl CheckAttrVisitor<'_> {
             _ => {
                 self.tcx
                     .sess
-                    .struct_span_err(
-                        attr.span,
-                        "`rustc_has_incoherent_inherent_impls` attribute should be applied to types or traits.",
-                    )
-                    .span_label(span, "only adts, extern types and traits are supported")
-                    .emit();
+                    .emit_err(errors::HasIncoherentInherentImpl { attr_span: attr.span, span });
                 false
             }
         }
@@ -1238,19 +1062,12 @@ impl CheckAttrVisitor<'_> {
     fn check_must_use(&self, hir_id: HirId, attr: &Attribute, span: Span, target: Target) -> bool {
         let node = self.tcx.hir().get(hir_id);
         if let Some(kind) = node.fn_kind() && let rustc_hir::IsAsync::Async = kind.asyncness() {
-            self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-                lint.build(
-                    "`must_use` attribute on `async` functions \
-                    applies to the anonymous `Future` returned by the \
-                    function, not the value within",
-                )
-                .span_label(
-                    span,
-                    "this attribute does nothing, the `Future`s \
-                    returned by async functions are already `must_use`",
-                )
-                .emit();
-            });
+            self.tcx.emit_spanned_lint(
+                UNUSED_ATTRIBUTES,
+                hir_id,
+                attr.span,
+                errors::MustUseAsync { span }
+            );
         }
 
         if !matches!(
@@ -1278,12 +1095,12 @@ impl CheckAttrVisitor<'_> {
                 _ => "a",
             };
 
-            self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-                lint.build(&format!(
-                    "`#[must_use]` has no effect when applied to {article} {target}"
-                ))
-                .emit();
-            });
+            self.tcx.emit_spanned_lint(
+                UNUSED_ATTRIBUTES,
+                hir_id,
+                attr.span,
+                errors::MustUseNoEffect { article, target },
+            );
         }
 
         // For now, its always valid
@@ -1295,11 +1112,7 @@ impl CheckAttrVisitor<'_> {
         match target {
             Target::Struct | Target::Enum | Target::Union | Target::Trait => true,
             _ => {
-                self.tcx
-                    .sess
-                    .struct_span_err(attr.span, "`must_not_suspend` attribute should be applied to a struct, enum, or trait")
-                        .span_label(span, "is not a struct, enum, or trait")
-                        .emit();
+                self.tcx.sess.emit_err(errors::MustNotSuspend { attr_span: attr.span, span });
                 false
             }
         }
@@ -1319,16 +1132,12 @@ impl CheckAttrVisitor<'_> {
             _ => {
                 // FIXME: #[cold] was previously allowed on non-functions and some crates used
                 // this, so only emit a warning.
-                self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-                    lint.build("attribute should be applied to a function")
-                        .warn(
-                            "this was previously accepted by the compiler but is \
-                             being phased out; it will become a hard error in \
-                             a future release!",
-                        )
-                        .span_label(span, "not a function")
-                        .emit();
-                });
+                self.tcx.emit_spanned_lint(
+                    UNUSED_ATTRIBUTES,
+                    hir_id,
+                    attr.span,
+                    errors::Cold { span },
+                );
             }
         }
     }
@@ -1343,19 +1152,12 @@ impl CheckAttrVisitor<'_> {
             return;
         }
 
-        self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
-            let mut diag =
-                lint.build("attribute should be applied to an `extern` block with non-Rust ABI");
-            diag.warn(
-                "this was previously accepted by the compiler but is \
-                 being phased out; it will become a hard error in \
-                 a future release!",
-            );
-            if target != Target::ForeignMod {
-                diag.span_label(span, "not an `extern` block");
-            }
-            diag.emit();
-        });
+        self.tcx.emit_spanned_lint(
+            UNUSED_ATTRIBUTES,
+            hir_id,
+            attr.span,
+            errors::Link { span: (target != Target::ForeignMod).then_some(span) },
+        );
     }
 
     /// Checks if `#[link_name]` is applied to an item other than a foreign function or static.
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
new file mode 100644
index 0000000000000..f8e8720ab5474
--- /dev/null
+++ b/compiler/rustc_passes/src/errors.rs
@@ -0,0 +1,362 @@
+use rustc_errors::{Applicability, MultiSpan};
+use rustc_macros::{LintDiagnostic, SessionDiagnostic};
+use rustc_span::{Span, Symbol};
+
+#[derive(LintDiagnostic)]
+#[lint(passes::outer_crate_level_attr)]
+pub struct OuterCrateLevelAttr;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::inner_crate_level_attr)]
+pub struct InnerCrateLevelAttr;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::ignored_attr_with_macro)]
+pub struct IgnoredAttrWithMacro<'a> {
+    pub sym: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::ignored_attr)]
+pub struct IgnoredAttr<'a> {
+    pub sym: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::inline_ignored_function_prototype)]
+pub struct IgnoredInlineAttrFnProto;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::inline_ignored_constants)]
+#[warn_]
+#[note]
+pub struct IgnoredInlineAttrConstants;
+
+#[derive(SessionDiagnostic)]
+#[error(passes::inline_not_fn_or_closure, code = "E0518")]
+pub struct InlineNotFnOrClosure {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub defn_span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::no_coverage_ignored_function_prototype)]
+pub struct IgnoredNoCoverageFnProto;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::no_coverage_propagate)]
+pub struct IgnoredNoCoveragePropagate;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::no_coverage_fn_defn)]
+pub struct IgnoredNoCoverageFnDefn;
+
+#[derive(SessionDiagnostic)]
+#[error(passes::no_coverage_not_coverable, code = "E0788")]
+pub struct IgnoredNoCoverageNotCoverable {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub defn_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::should_be_applied_to_fn)]
+pub struct AttrShouldBeAppliedToFn {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub defn_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::naked_tracked_caller, code = "E0736")]
+pub struct NakedTrackedCaller {
+    #[primary_span]
+    pub attr_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::should_be_applied_to_fn, code = "E0739")]
+pub struct TrackedCallerWrongLocation {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub defn_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::should_be_applied_to_struct_enum, code = "E0701")]
+pub struct NonExhaustiveWrongLocation {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub defn_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::should_be_applied_to_trait)]
+pub struct AttrShouldBeAppliedToTrait {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub defn_span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::target_feature_on_statement)]
+pub struct TargetFeatureOnStatement;
+
+#[derive(SessionDiagnostic)]
+#[error(passes::should_be_applied_to_static)]
+pub struct AttrShouldBeAppliedToStatic {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub defn_span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_expect_str)]
+pub struct DocExpectStr<'a> {
+    #[primary_span]
+    pub attr_span: Span,
+    pub attr_name: &'a str,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_empty)]
+pub struct DocAliasEmpty<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub attr_str: &'a str,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_bad_char)]
+pub struct DocAliasBadChar<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub attr_str: &'a str,
+    pub char_: char,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_start_end)]
+pub struct DocAliasStartEnd<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub attr_str: &'a str,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_bad_location)]
+pub struct DocAliasBadLocation<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub attr_str: &'a str,
+    pub location: &'a str,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_not_an_alias)]
+pub struct DocAliasNotAnAlias<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub attr_str: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_alias_duplicated)]
+pub struct DocAliasDuplicated {
+    #[label]
+    pub first_defn: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_not_string_literal)]
+pub struct DocAliasNotStringLiteral {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_alias_malformed)]
+pub struct DocAliasMalformed {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_keyword_empty_mod)]
+pub struct DocKeywordEmptyMod {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_keyword_not_mod)]
+pub struct DocKeywordNotMod {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_keyword_invalid_ident)]
+pub struct DocKeywordInvalidIdent {
+    #[primary_span]
+    pub span: Span,
+    pub doc_keyword: Symbol,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_tuple_variadic_not_first)]
+pub struct DocTupleVariadicNotFirst {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_keyword_only_impl)]
+pub struct DocKeywordOnlyImpl {
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_inline_conflict)]
+#[help]
+pub struct DocKeywordConflict {
+    #[primary_span]
+    pub spans: MultiSpan,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_inline_only_use)]
+#[note]
+pub struct DocInlineOnlyUse {
+    #[label]
+    pub attr_span: Span,
+    #[label(passes::not_a_use_item_label)]
+    pub item_span: Option<Span>,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::doc_attr_not_crate_level)]
+pub struct DocAttrNotCrateLevel<'a> {
+    #[primary_span]
+    pub span: Span,
+    pub attr_name: &'a str,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_test_unknown)]
+pub struct DocTestUnknown {
+    pub path: String,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_test_takes_list)]
+pub struct DocTestTakesList;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_primitive)]
+pub struct DocPrimitive;
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_test_unknown_any)]
+pub struct DocTestUnknownAny {
+    pub path: String,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_test_unknown_spotlight)]
+#[note]
+#[note(passes::no_op_note)]
+pub struct DocTestUnknownSpotlight {
+    pub path: String,
+    #[suggestion_short(applicability = "machine-applicable", code = "notable_trait")]
+    pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_test_unknown_include)]
+pub struct DocTestUnknownInclude {
+    pub path: String,
+    pub value: String,
+    pub inner: &'static str,
+    #[suggestion(code = "#{inner}[doc = include_str!(\"{value}\")]")]
+    pub sugg: (Span, Applicability),
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::doc_invalid)]
+pub struct DocInvalid;
+
+#[derive(SessionDiagnostic)]
+#[error(passes::pass_by_value)]
+pub struct PassByValue {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::allow_incoherent_impl)]
+pub struct AllowIncoherentImpl {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub span: Span,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::has_incoherent_inherent_impl)]
+pub struct HasIncoherentInherentImpl {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::must_use_async)]
+pub struct MustUseAsync {
+    #[label]
+    pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::must_use_no_effect)]
+pub struct MustUseNoEffect {
+    pub article: &'static str,
+    pub target: rustc_hir::Target,
+}
+
+#[derive(SessionDiagnostic)]
+#[error(passes::must_not_suspend)]
+pub struct MustNotSuspend {
+    #[primary_span]
+    pub attr_span: Span,
+    #[label]
+    pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::cold)]
+#[warn_]
+pub struct Cold {
+    #[label]
+    pub span: Span,
+}
+
+#[derive(LintDiagnostic)]
+#[lint(passes::link)]
+#[warn_]
+pub struct Link {
+    #[label]
+    pub span: Option<Span>,
+}
diff --git a/compiler/rustc_passes/src/lib.rs b/compiler/rustc_passes/src/lib.rs
index 497c0931c2182..7b2f83958af85 100644
--- a/compiler/rustc_passes/src/lib.rs
+++ b/compiler/rustc_passes/src/lib.rs
@@ -7,8 +7,8 @@
 #![allow(rustc::potential_query_instability)]
 #![doc(html_root_url = "https://doc.rust-lang.org/nightly/nightly-rustc/")]
 #![feature(iter_intersperse)]
-#![feature(let_else)]
 #![feature(let_chains)]
+#![feature(let_else)]
 #![feature(map_try_insert)]
 #![feature(min_specialization)]
 #![feature(try_blocks)]
@@ -27,6 +27,7 @@ pub mod dead;
 mod debugger_visualizer;
 mod diagnostic_items;
 pub mod entry;
+mod errors;
 pub mod hir_id_validator;
 pub mod hir_stats;
 mod lang_items;
diff --git a/src/test/rustdoc-ui/invalid-doc-attr.stderr b/src/test/rustdoc-ui/invalid-doc-attr.stderr
index 55006b2087eb0..a4fa3817905c7 100644
--- a/src/test/rustdoc-ui/invalid-doc-attr.stderr
+++ b/src/test/rustdoc-ui/invalid-doc-attr.stderr
@@ -12,7 +12,7 @@ LL | #![deny(warnings)]
    = note: `#[deny(invalid_doc_attributes)]` implied by `#[deny(warnings)]`
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
 help: to apply to the crate, use an inner attribute
    |
 LL | #![doc(test(no_crate_inject))]
@@ -29,7 +29,7 @@ LL | pub fn foo() {}
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline> for more information
 
 error: this attribute can only be applied at the crate level
   --> $DIR/invalid-doc-attr.rs:15:12
@@ -39,7 +39,7 @@ LL |     #![doc(test(no_crate_inject))]
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
 
 error: conflicting doc inlining attributes
   --> $DIR/invalid-doc-attr.rs:28:7
@@ -59,7 +59,7 @@ LL |     #[doc(test(no_crate_inject))]
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
 
 error: this attribute can only be applied to a `use` item
   --> $DIR/invalid-doc-attr.rs:22:11
@@ -72,7 +72,7 @@ LL |     pub fn baz() {}
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline> for more information
 
 error: aborting due to 6 previous errors
 
diff --git a/src/test/ui/attributes/invalid-doc-attr.stderr b/src/test/ui/attributes/invalid-doc-attr.stderr
index 55006b2087eb0..a4fa3817905c7 100644
--- a/src/test/ui/attributes/invalid-doc-attr.stderr
+++ b/src/test/ui/attributes/invalid-doc-attr.stderr
@@ -12,7 +12,7 @@ LL | #![deny(warnings)]
    = note: `#[deny(invalid_doc_attributes)]` implied by `#[deny(warnings)]`
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
 help: to apply to the crate, use an inner attribute
    |
 LL | #![doc(test(no_crate_inject))]
@@ -29,7 +29,7 @@ LL | pub fn foo() {}
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline> for more information
 
 error: this attribute can only be applied at the crate level
   --> $DIR/invalid-doc-attr.rs:15:12
@@ -39,7 +39,7 @@ LL |     #![doc(test(no_crate_inject))]
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
 
 error: conflicting doc inlining attributes
   --> $DIR/invalid-doc-attr.rs:28:7
@@ -59,7 +59,7 @@ LL |     #[doc(test(no_crate_inject))]
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
 
 error: this attribute can only be applied to a `use` item
   --> $DIR/invalid-doc-attr.rs:22:11
@@ -72,7 +72,7 @@ LL |     pub fn baz() {}
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#inline-and-no_inline> for more information
 
 error: aborting due to 6 previous errors
 
diff --git a/src/test/ui/attributes/multiple-invalid.stderr b/src/test/ui/attributes/multiple-invalid.stderr
index 9bd29f15dbcca..a8dba0ba37d3a 100644
--- a/src/test/ui/attributes/multiple-invalid.stderr
+++ b/src/test/ui/attributes/multiple-invalid.stderr
@@ -7,14 +7,14 @@ LL | #[inline]
 LL | const FOO: u8 = 0;
    | ------------------ not a function or closure
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/multiple-invalid.rs:6:1
    |
 LL | #[target_feature(enable = "sse2")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 LL |
 LL | const FOO: u8 = 0;
-   | ------------------ not a function
+   | ------------------ not a function definition
 
 error: aborting due to 2 previous errors
 
diff --git a/src/test/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr b/src/test/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr
index 3f838fcf52355..5d6796b49448a 100644
--- a/src/test/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr
+++ b/src/test/ui/feature-gates/issue-43106-gating-of-builtin-attrs.stderr
@@ -259,7 +259,7 @@ warning: crate-level attribute should be an inner attribute: add an exclamation
 LL | #[no_std]
    | ^^^^^^^^^
 
-warning: attribute should be applied to a function
+warning: attribute should be applied to a function definition
   --> $DIR/issue-43106-gating-of-builtin-attrs.rs:453:1
    |
 LL |   #[cold]
@@ -272,7 +272,7 @@ LL | |     mod inner { #![cold] }
 ...  |
 LL | |
 LL | | }
-   | |_- not a function
+   | |_- not a function definition
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 
@@ -399,7 +399,7 @@ warning: `#[proc_macro_derive]` only has an effect on functions
 LL | #![proc_macro_derive()]
    | ^^^^^^^^^^^^^^^^^^^^^^^
 
-warning: attribute should be applied to a function
+warning: attribute should be applied to a function definition
   --> $DIR/issue-43106-gating-of-builtin-attrs.rs:62:1
    |
 LL | #![cold]
@@ -743,35 +743,35 @@ warning: crate-level attribute should be an inner attribute: add an exclamation
 LL |     #[no_std] impl S { }
    |     ^^^^^^^^^
 
-warning: attribute should be applied to a function
+warning: attribute should be applied to a function definition
   --> $DIR/issue-43106-gating-of-builtin-attrs.rs:459:17
    |
 LL |     mod inner { #![cold] }
-   |     ------------^^^^^^^^-- not a function
+   |     ------------^^^^^^^^-- not a function definition
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 
-warning: attribute should be applied to a function
+warning: attribute should be applied to a function definition
   --> $DIR/issue-43106-gating-of-builtin-attrs.rs:466:5
    |
 LL |     #[cold] struct S;
-   |     ^^^^^^^ --------- not a function
+   |     ^^^^^^^ --------- not a function definition
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 
-warning: attribute should be applied to a function
+warning: attribute should be applied to a function definition
   --> $DIR/issue-43106-gating-of-builtin-attrs.rs:471:5
    |
 LL |     #[cold] type T = S;
-   |     ^^^^^^^ ----------- not a function
+   |     ^^^^^^^ ----------- not a function definition
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 
-warning: attribute should be applied to a function
+warning: attribute should be applied to a function definition
   --> $DIR/issue-43106-gating-of-builtin-attrs.rs:476:5
    |
 LL |     #[cold] impl S { }
-   |     ^^^^^^^ ---------- not a function
+   |     ^^^^^^^ ---------- not a function definition
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 
diff --git a/src/test/ui/future-incompatible-lint-group.stderr b/src/test/ui/future-incompatible-lint-group.stderr
index d822847a7a589..8f6dde665e628 100644
--- a/src/test/ui/future-incompatible-lint-group.stderr
+++ b/src/test/ui/future-incompatible-lint-group.stderr
@@ -22,7 +22,7 @@ LL | #![deny(future_incompatible)]
    = note: `#[deny(invalid_doc_attributes)]` implied by `#[deny(future_incompatible)]`
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
    = note: for more information, see issue #82730 <https://github.com/rust-lang/rust/issues/82730>
-   = note: read https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level for more information
+   = note: read <https://doc.rust-lang.org/nightly/rustdoc/the-doc-attribute.html#at-the-crate-level> for more information
 
 error: aborting due to previous error; 1 warning emitted
 
diff --git a/src/test/ui/issues/issue-54044.stderr b/src/test/ui/issues/issue-54044.stderr
index 0200a6a629d8f..100965de1aa84 100644
--- a/src/test/ui/issues/issue-54044.stderr
+++ b/src/test/ui/issues/issue-54044.stderr
@@ -1,11 +1,11 @@
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/issue-54044.rs:3:1
    |
 LL | #[cold]
    | ^^^^^^^
 ...
 LL | struct Foo;
-   | ----------- not a function
+   | ----------- not a function definition
    |
 note: the lint level is defined here
   --> $DIR/issue-54044.rs:1:9
@@ -14,14 +14,14 @@ LL | #![deny(unused_attributes)]
    |         ^^^^^^^^^^^^^^^^^
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/issue-54044.rs:9:5
    |
 LL |     #[cold]
    |     ^^^^^^^
 ...
 LL |     5;
-   |     - not a function
+   |     - not a function definition
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 
diff --git a/src/test/ui/issues/issue-78957.stderr b/src/test/ui/issues/issue-78957.stderr
index fa2eaab5b417b..45fa69d6fd708 100644
--- a/src/test/ui/issues/issue-78957.stderr
+++ b/src/test/ui/issues/issue-78957.stderr
@@ -4,11 +4,11 @@ error[E0518]: attribute should be applied to function or closure
 LL | pub struct Foo<#[inline] const N: usize>;
    |                ^^^^^^^^^ -------------- not a function or closure
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/issue-78957.rs:7:16
    |
 LL | pub struct Bar<#[cold] const N: usize>;
-   |                ^^^^^^^ -------------- not a function
+   |                ^^^^^^^ -------------- not a function definition
    |
 note: the lint level is defined here
   --> $DIR/issue-78957.rs:1:9
@@ -29,11 +29,11 @@ error[E0518]: attribute should be applied to function or closure
 LL | pub struct Foo2<#[inline] 'a>(PhantomData<&'a ()>);
    |                 ^^^^^^^^^ -- not a function or closure
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/issue-78957.rs:15:17
    |
 LL | pub struct Bar2<#[cold] 'a>(PhantomData<&'a ()>);
-   |                 ^^^^^^^ -- not a function
+   |                 ^^^^^^^ -- not a function definition
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 
@@ -49,11 +49,11 @@ error[E0518]: attribute should be applied to function or closure
 LL | pub struct Foo3<#[inline] T>(PhantomData<T>);
    |                 ^^^^^^^^^ - not a function or closure
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/issue-78957.rs:23:17
    |
 LL | pub struct Bar3<#[cold] T>(PhantomData<T>);
-   |                 ^^^^^^^ - not a function
+   |                 ^^^^^^^ - not a function definition
    |
    = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
 
diff --git a/src/test/ui/macros/issue-68060.stderr b/src/test/ui/macros/issue-68060.stderr
index 1b58cf9c4ede5..b13e418e664a6 100644
--- a/src/test/ui/macros/issue-68060.stderr
+++ b/src/test/ui/macros/issue-68060.stderr
@@ -1,11 +1,11 @@
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/issue-68060.rs:4:13
    |
 LL |             #[target_feature(enable = "")]
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 ...
 LL |             |_| (),
-   |             ------ not a function
+   |             ------ not a function definition
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/marker_trait_attr/marker-attribute-on-non-trait.rs b/src/test/ui/marker_trait_attr/marker-attribute-on-non-trait.rs
index 66156b6e53bab..0bf620934ec7b 100644
--- a/src/test/ui/marker_trait_attr/marker-attribute-on-non-trait.rs
+++ b/src/test/ui/marker_trait_attr/marker-attribute-on-non-trait.rs
@@ -1,23 +1,23 @@
 #![feature(marker_trait_attr)]
 
-#[marker] //~ ERROR attribute can only be applied to a trait
+#[marker] //~ ERROR attribute should be applied to a trait
 struct Struct {}
 
-#[marker] //~ ERROR attribute can only be applied to a trait
+#[marker] //~ ERROR attribute should be applied to a trait
 impl Struct {}
 
-#[marker] //~ ERROR attribute can only be applied to a trait
+#[marker] //~ ERROR attribute should be applied to a trait
 union Union {
     x: i32,
 }
 
-#[marker] //~ ERROR attribute can only be applied to a trait
+#[marker] //~ ERROR attribute should be applied to a trait
 const CONST: usize = 10;
 
-#[marker] //~ ERROR attribute can only be applied to a trait
+#[marker] //~ ERROR attribute should be applied to a trait
 fn function() {}
 
-#[marker] //~ ERROR attribute can only be applied to a trait
+#[marker] //~ ERROR attribute should be applied to a trait
 type Type = ();
 
 fn main() {}
diff --git a/src/test/ui/marker_trait_attr/marker-attribute-on-non-trait.stderr b/src/test/ui/marker_trait_attr/marker-attribute-on-non-trait.stderr
index d30b990caac2b..19a5290dd7eb6 100644
--- a/src/test/ui/marker_trait_attr/marker-attribute-on-non-trait.stderr
+++ b/src/test/ui/marker_trait_attr/marker-attribute-on-non-trait.stderr
@@ -1,4 +1,4 @@
-error: attribute can only be applied to a trait
+error: attribute should be applied to a trait
   --> $DIR/marker-attribute-on-non-trait.rs:3:1
    |
 LL | #[marker]
@@ -6,7 +6,7 @@ LL | #[marker]
 LL | struct Struct {}
    | ---------------- not a trait
 
-error: attribute can only be applied to a trait
+error: attribute should be applied to a trait
   --> $DIR/marker-attribute-on-non-trait.rs:6:1
    |
 LL | #[marker]
@@ -14,7 +14,7 @@ LL | #[marker]
 LL | impl Struct {}
    | -------------- not a trait
 
-error: attribute can only be applied to a trait
+error: attribute should be applied to a trait
   --> $DIR/marker-attribute-on-non-trait.rs:9:1
    |
 LL |   #[marker]
@@ -24,7 +24,7 @@ LL | |     x: i32,
 LL | | }
    | |_- not a trait
 
-error: attribute can only be applied to a trait
+error: attribute should be applied to a trait
   --> $DIR/marker-attribute-on-non-trait.rs:14:1
    |
 LL | #[marker]
@@ -32,7 +32,7 @@ LL | #[marker]
 LL | const CONST: usize = 10;
    | ------------------------ not a trait
 
-error: attribute can only be applied to a trait
+error: attribute should be applied to a trait
   --> $DIR/marker-attribute-on-non-trait.rs:17:1
    |
 LL | #[marker]
@@ -40,7 +40,7 @@ LL | #[marker]
 LL | fn function() {}
    | ---------------- not a trait
 
-error: attribute can only be applied to a trait
+error: attribute should be applied to a trait
   --> $DIR/marker-attribute-on-non-trait.rs:20:1
    |
 LL | #[marker]
diff --git a/src/test/ui/rfc-2008-non-exhaustive/invalid-attribute.rs b/src/test/ui/rfc-2008-non-exhaustive/invalid-attribute.rs
index 3c4a09fafd2db..143f9a3009b61 100644
--- a/src/test/ui/rfc-2008-non-exhaustive/invalid-attribute.rs
+++ b/src/test/ui/rfc-2008-non-exhaustive/invalid-attribute.rs
@@ -3,11 +3,11 @@
 struct Foo;
 
 #[non_exhaustive]
-//~^ ERROR attribute can only be applied to a struct or enum [E0701]
+//~^ ERROR attribute should be applied to a struct or enum [E0701]
 trait Bar { }
 
 #[non_exhaustive]
-//~^ ERROR attribute can only be applied to a struct or enum [E0701]
+//~^ ERROR attribute should be applied to a struct or enum [E0701]
 union Baz {
     f1: u16,
     f2: u16
diff --git a/src/test/ui/rfc-2008-non-exhaustive/invalid-attribute.stderr b/src/test/ui/rfc-2008-non-exhaustive/invalid-attribute.stderr
index 76d9e2d8205b7..136cd763b05c1 100644
--- a/src/test/ui/rfc-2008-non-exhaustive/invalid-attribute.stderr
+++ b/src/test/ui/rfc-2008-non-exhaustive/invalid-attribute.stderr
@@ -4,7 +4,7 @@ error: malformed `non_exhaustive` attribute input
 LL | #[non_exhaustive(anything)]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[non_exhaustive]`
 
-error[E0701]: attribute can only be applied to a struct or enum
+error[E0701]: attribute should be applied to a struct or enum
   --> $DIR/invalid-attribute.rs:5:1
    |
 LL | #[non_exhaustive]
@@ -13,7 +13,7 @@ LL |
 LL | trait Bar { }
    | ------------- not a struct or enum
 
-error[E0701]: attribute can only be applied to a struct or enum
+error[E0701]: attribute should be applied to a struct or enum
   --> $DIR/invalid-attribute.rs:9:1
    |
 LL |   #[non_exhaustive]
diff --git a/src/test/ui/rfc-2091-track-caller/only-for-fns.rs b/src/test/ui/rfc-2091-track-caller/only-for-fns.rs
index bc0ca9552806f..2d2b01b6f947a 100644
--- a/src/test/ui/rfc-2091-track-caller/only-for-fns.rs
+++ b/src/test/ui/rfc-2091-track-caller/only-for-fns.rs
@@ -1,5 +1,5 @@
 #[track_caller]
 struct S;
-//~^^ ERROR attribute should be applied to function
+//~^^ ERROR attribute should be applied to a function definition
 
 fn main() {}
diff --git a/src/test/ui/rfc-2091-track-caller/only-for-fns.stderr b/src/test/ui/rfc-2091-track-caller/only-for-fns.stderr
index 6666dcfa6e599..b36597bded941 100644
--- a/src/test/ui/rfc-2091-track-caller/only-for-fns.stderr
+++ b/src/test/ui/rfc-2091-track-caller/only-for-fns.stderr
@@ -1,10 +1,10 @@
-error[E0739]: attribute should be applied to function
+error[E0739]: attribute should be applied to a function definition
   --> $DIR/only-for-fns.rs:1:1
    |
 LL | #[track_caller]
    | ^^^^^^^^^^^^^^^
 LL | struct S;
-   | --------- not a function
+   | --------- not a function definition
 
 error: aborting due to previous error
 
diff --git a/src/test/ui/rustdoc/doc_keyword.rs b/src/test/ui/rustdoc/doc_keyword.rs
index 43b84e5018cda..68a8802b2f645 100644
--- a/src/test/ui/rustdoc/doc_keyword.rs
+++ b/src/test/ui/rustdoc/doc_keyword.rs
@@ -15,6 +15,6 @@ fn foo() {}
 // Regression test for the ICE described in #83512.
 trait Foo {
     #[doc(keyword = "match")]
-    //~^ ERROR: `#[doc(keyword = "...")]` can only be used on modules
+    //~^ ERROR: `#[doc(keyword = "...")]` should be used on modules
     fn quux() {}
 }
diff --git a/src/test/ui/rustdoc/doc_keyword.stderr b/src/test/ui/rustdoc/doc_keyword.stderr
index 6ba7034d54122..a1d0e4ffc0938 100644
--- a/src/test/ui/rustdoc/doc_keyword.stderr
+++ b/src/test/ui/rustdoc/doc_keyword.stderr
@@ -1,16 +1,16 @@
-error: `#[doc(keyword = "...")]` can only be used on empty modules
+error: `#[doc(keyword = "...")]` should be used on empty modules
   --> $DIR/doc_keyword.rs:6:7
    |
 LL | #[doc(keyword = "hell")]
    |       ^^^^^^^^^^^^^^^^
 
-error: `#[doc(keyword = "...")]` can only be used on modules
+error: `#[doc(keyword = "...")]` should be used on modules
   --> $DIR/doc_keyword.rs:11:7
    |
 LL | #[doc(keyword = "hall")]
    |       ^^^^^^^^^^^^^^^^
 
-error: `#[doc(keyword = "...")]` can only be used on modules
+error: `#[doc(keyword = "...")]` should be used on modules
   --> $DIR/doc_keyword.rs:17:11
    |
 LL |     #[doc(keyword = "match")]
diff --git a/src/test/ui/target-feature/invalid-attribute.stderr b/src/test/ui/target-feature/invalid-attribute.stderr
index 25a2c1975e7b2..889ced9752bd4 100644
--- a/src/test/ui/target-feature/invalid-attribute.stderr
+++ b/src/test/ui/target-feature/invalid-attribute.stderr
@@ -34,43 +34,43 @@ LL | fn bar() {}
    = note: see issue #69098 <https://github.com/rust-lang/rust/issues/69098> for more information
    = help: add `#![feature(target_feature_11)]` to the crate attributes to enable
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/invalid-attribute.rs:34:1
    |
 LL | #[target_feature(enable = "sse2")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 LL |
 LL | mod another {}
-   | -------------- not a function
+   | -------------- not a function definition
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/invalid-attribute.rs:39:1
    |
 LL | #[target_feature(enable = "sse2")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 LL |
 LL | const FOO: usize = 7;
-   | --------------------- not a function
+   | --------------------- not a function definition
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/invalid-attribute.rs:44:1
    |
 LL | #[target_feature(enable = "sse2")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 LL |
 LL | struct Foo;
-   | ----------- not a function
+   | ----------- not a function definition
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/invalid-attribute.rs:49:1
    |
 LL | #[target_feature(enable = "sse2")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 LL |
 LL | enum Bar {}
-   | ----------- not a function
+   | ----------- not a function definition
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/invalid-attribute.rs:54:1
    |
 LL |   #[target_feature(enable = "sse2")]
@@ -81,16 +81,16 @@ LL | |
 LL | |     f1: u16,
 LL | |     f2: u16,
 LL | | }
-   | |_- not a function
+   | |_- not a function definition
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/invalid-attribute.rs:62:1
    |
 LL | #[target_feature(enable = "sse2")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 LL |
 LL | trait Baz {}
-   | ------------ not a function
+   | ------------ not a function definition
 
 error: cannot use `#[inline(always)]` with `#[target_feature]`
   --> $DIR/invalid-attribute.rs:67:1
@@ -98,7 +98,7 @@ error: cannot use `#[inline(always)]` with `#[target_feature]`
 LL | #[inline(always)]
    | ^^^^^^^^^^^^^^^^^
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/invalid-attribute.rs:85:5
    |
 LL |       #[target_feature(enable = "sse2")]
@@ -108,16 +108,16 @@ LL | /     unsafe {
 LL | |         foo();
 LL | |         bar();
 LL | |     }
-   | |_____- not a function
+   | |_____- not a function definition
 
-error: attribute should be applied to a function
+error: attribute should be applied to a function definition
   --> $DIR/invalid-attribute.rs:93:5
    |
 LL |     #[target_feature(enable = "sse2")]
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 LL |
 LL |     || {};
-   |     ----- not a function
+   |     ----- not a function definition
 
 error[E0658]: `#[target_feature(..)]` can only be applied to `unsafe` functions
   --> $DIR/invalid-attribute.rs:77:5
diff --git a/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.rs b/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.rs
index d9de6d5edb9b4..1f896da94db57 100644
--- a/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.rs
+++ b/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.rs
@@ -36,11 +36,11 @@ trait Tr5 {
 }
 
 #[rustc_must_implement_one_of(abc, xyz)]
-//~^ attribute can only be applied to a trait
+//~^ attribute should be applied to a trait
 fn function() {}
 
 #[rustc_must_implement_one_of(abc, xyz)]
-//~^ attribute can only be applied to a trait
+//~^ attribute should be applied to a trait
 struct Struct {}
 
 fn main() {}
diff --git a/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.stderr b/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.stderr
index bc28dc2c4f40d..869184f0d1a69 100644
--- a/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.stderr
+++ b/src/test/ui/traits/default-method/rustc_must_implement_one_of_misuse.stderr
@@ -4,7 +4,7 @@ error: malformed `rustc_must_implement_one_of` attribute input
 LL | #[rustc_must_implement_one_of]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[rustc_must_implement_one_of(function1, function2, ...)]`
 
-error: attribute can only be applied to a trait
+error: attribute should be applied to a trait
   --> $DIR/rustc_must_implement_one_of_misuse.rs:38:1
    |
 LL | #[rustc_must_implement_one_of(abc, xyz)]
@@ -13,7 +13,7 @@ LL |
 LL | fn function() {}
    | ---------------- not a trait
 
-error: attribute can only be applied to a trait
+error: attribute should be applied to a trait
   --> $DIR/rustc_must_implement_one_of_misuse.rs:42:1
    |
 LL | #[rustc_must_implement_one_of(abc, xyz)]