about summary refs log tree commit diff
diff options
context:
space:
mode:
authorfee1-dead <ent3rm4n@gmail.com>2022-09-26 09:27:36 +0800
committerGitHub <noreply@github.com>2022-09-26 09:27:36 +0800
commit1a93028bcc4a9052f0956e924fed0c9ac61efba1 (patch)
tree8e90f4153ad1f65b157bf8381fc0301fcd80a1ba
parentff40f2ec95923c4d45366e85bcff17d75df68d68 (diff)
parent336a72a8daea236a89787f16931611310315340c (diff)
downloadrust-1a93028bcc4a9052f0956e924fed0c9ac61efba1.tar.gz
rust-1a93028bcc4a9052f0956e924fed0c9ac61efba1.zip
Rollup merge of #101851 - Xiretza:diagnostic-derive-cleanups, r=davidtwco
Clean up (sub)diagnostic derives

The biggest chunk of this is unifying the parsing of subdiagnostic attributes (`#[error]`, `#[suggestion(...)]`, `#[label(...)]`, etc) between `Subdiagnostic` and `Diagnostic` type attributes as well as `Diagnostic` field attributes.

It also improves a number of proc macro diagnostics.

Waiting for #101558.
-rw-r--r--compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs488
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs328
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs270
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs8
-rw-r--r--compiler/rustc_passes/src/errors.rs4
-rw-r--r--src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs88
-rw-r--r--src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr300
-rw-r--r--src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs16
-rw-r--r--src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr172
9 files changed, 912 insertions, 762 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
index 32d6ba62a0d..2aa292bbce2 100644
--- a/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
+++ b/compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs
@@ -1,17 +1,17 @@
 #![deny(unused_must_use)]
 
+use super::error::throw_invalid_nested_attr;
+use super::utils::{SpannedOption, SubdiagnosticKind};
 use crate::diagnostics::error::{
-    invalid_nested_attr, span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err,
-    DiagnosticDeriveError,
+    invalid_nested_attr, span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError,
 };
 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,
+    FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
 };
 use proc_macro2::{Ident, Span, TokenStream};
 use quote::{format_ident, quote};
 use std::collections::HashMap;
-use std::str::FromStr;
 use syn::{
     parse_quote, spanned::Spanned, Attribute, Field, Meta, MetaList, MetaNameValue, NestedMeta,
     Path, Type,
@@ -40,10 +40,10 @@ pub(crate) struct DiagnosticDeriveBuilder {
     pub kind: DiagnosticDeriveKind,
     /// Slug is a mandatory part of the struct attribute as corresponds to the Fluent message that
     /// has the actual diagnostic message.
-    pub slug: Option<(Path, proc_macro::Span)>,
+    pub slug: SpannedOption<Path>,
     /// Error codes are a optional part of the struct attribute - this is only set to detect
     /// multiple specifications.
-    pub code: Option<(String, proc_macro::Span)>,
+    pub code: SpannedOption<()>,
 }
 
 impl HasFieldMap for DiagnosticDeriveBuilder {
@@ -127,6 +127,30 @@ impl DiagnosticDeriveBuilder {
             || is_subdiagnostic
     }
 
+    fn parse_subdiag_attribute(
+        &self,
+        attr: &Attribute,
+    ) -> Result<(SubdiagnosticKind, Path), DiagnosticDeriveError> {
+        let (subdiag, slug) = SubdiagnosticKind::from_attr(attr, self)?;
+
+        if let SubdiagnosticKind::MultipartSuggestion { .. } = subdiag {
+            let meta = attr.parse_meta()?;
+            throw_invalid_attr!(attr, &meta, |diag| diag
+                .help("consider creating a `Subdiagnostic` instead"));
+        }
+
+        let slug = slug.unwrap_or_else(|| match subdiag {
+            SubdiagnosticKind::Label => parse_quote! { _subdiag::label },
+            SubdiagnosticKind::Note => parse_quote! { _subdiag::note },
+            SubdiagnosticKind::Help => parse_quote! { _subdiag::help },
+            SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn },
+            SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion },
+            SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
+        });
+
+        Ok((subdiag, slug))
+    }
+
     /// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
     /// attributes like `#[diag(..)]`, such as the slug and error code. Generates
     /// diagnostic builder calls for setting error code and creating note/help messages.
@@ -135,98 +159,64 @@ impl DiagnosticDeriveBuilder {
         attr: &Attribute,
     ) -> Result<TokenStream, DiagnosticDeriveError> {
         let diag = &self.diag;
-        let span = attr.span().unwrap();
 
         let name = attr.path.segments.last().unwrap().ident.to_string();
         let name = name.as_str();
         let meta = attr.parse_meta()?;
 
-        let is_diag = name == "diag";
-
-        let nested = match meta {
-            // Most attributes are lists, like `#[diag(..)]` for most cases or
-            // `#[help(..)]`/`#[note(..)]` when the user is specifying a alternative slug.
-            Meta::List(MetaList { ref nested, .. }) => nested,
-            // Subdiagnostics without spans can be applied to the type too, and these are just
-            // paths: `#[help]`, `#[note]` and `#[warning]`
-            Meta::Path(_) if !is_diag => {
-                let fn_name = if name == "warning" {
-                    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),
-        };
-
-        // Check the kind before doing any further processing so that there aren't misleading
-        // "no kind specified" errors if there are failures later.
-        match name {
-            "error" | "lint" => throw_invalid_attr!(attr, &meta, |diag| {
-                diag.help("`error` and `lint` have been replaced by `diag`")
-            }),
-            "warn_" => throw_invalid_attr!(attr, &meta, |diag| {
-                diag.help("`warn_` have been replaced by `warning`")
-            }),
-            "diag" | "help" | "note" | "warning" => (),
-            _ => throw_invalid_attr!(attr, &meta, |diag| {
-                diag.help("only `diag`, `help`, `note` and `warning` are valid attributes")
-            }),
-        }
+        if name == "diag" {
+            let Meta::List(MetaList { ref nested, .. }) = meta else {
+                throw_invalid_attr!(
+                    attr,
+                    &meta
+                );
+            };
 
-        // First nested element should always be the path, e.g. `#[diag(typeck::invalid)]` or
-        // `#[help(typeck::another_help)]`.
-        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_diag && nested_iter.next().is_some() {
-                throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                    diag.help(
-                        "`help`, `note` and `warning` struct attributes can only have one argument",
-                    )
-                });
-            }
+            let mut nested_iter = nested.into_iter().peekable();
 
-            match nested_attr {
-                NestedMeta::Meta(Meta::Path(path)) => {
-                    if is_diag {
-                        self.slug.set_once((path.clone(), span));
-                    } else {
-                        let fn_name = proc_macro2::Ident::new(name, attr.span());
-                        return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); });
-                    }
-                }
-                NestedMeta::Meta(meta @ Meta::NameValue(_))
-                    if is_diag && meta.path().segments.last().unwrap().ident == "code" =>
-                {
-                    // don't error for valid follow-up attributes
+            match nested_iter.peek() {
+                Some(NestedMeta::Meta(Meta::Path(slug))) => {
+                    self.slug.set_once(slug.clone(), slug.span().unwrap());
+                    nested_iter.next();
                 }
-                nested_attr => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                    diag.help("first argument of the attribute should be the diagnostic slug")
-                }),
+                Some(NestedMeta::Meta(Meta::NameValue { .. })) => {}
+                Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| diag
+                    .help("a diagnostic slug is required as the first argument")),
+                None => throw_invalid_attr!(attr, &meta, |diag| diag
+                    .help("a diagnostic slug is required as the first argument")),
             };
-        }
 
-        // Remaining attributes are optional, only `code = ".."` at the moment.
-        let mut tokens = Vec::new();
-        for nested_attr in nested_iter {
-            let meta = match nested_attr {
-                syn::NestedMeta::Meta(meta) => meta,
-                _ => throw_invalid_nested_attr!(attr, &nested_attr),
-            };
+            // Remaining attributes are optional, only `code = ".."` at the moment.
+            let mut tokens = TokenStream::new();
+            for nested_attr in nested_iter {
+                let (value, path) = match nested_attr {
+                    NestedMeta::Meta(Meta::NameValue(MetaNameValue {
+                        lit: syn::Lit::Str(value),
+                        path,
+                        ..
+                    })) => (value, path),
+                    NestedMeta::Meta(Meta::Path(_)) => {
+                        invalid_nested_attr(attr, &nested_attr)
+                            .help("diagnostic slug must be the first argument")
+                            .emit();
+                        continue;
+                    }
+                    _ => {
+                        invalid_nested_attr(attr, &nested_attr).emit();
+                        continue;
+                    }
+                };
 
-            let path = meta.path();
-            let nested_name = path.segments.last().unwrap().ident.to_string();
-            // Struct attributes are only allowed to be applied once, and the diagnostic
-            // changes will be set in the initialisation code.
-            if let Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) = &meta {
-                let span = s.span().unwrap();
+                let nested_name = path.segments.last().unwrap().ident.to_string();
+                // Struct attributes are only allowed to be applied once, and the diagnostic
+                // changes will be set in the initialisation code.
+                let span = value.span().unwrap();
                 match nested_name.as_str() {
                     "code" => {
-                        self.code.set_once((s.value(), span));
-                        let code = &self.code.as_ref().map(|(v, _)| v);
-                        tokens.push(quote! {
+                        self.code.set_once((), span);
+
+                        let code = value.value();
+                        tokens.extend(quote! {
                             #diag.code(rustc_errors::DiagnosticId::Error(#code.to_string()));
                         });
                     }
@@ -234,12 +224,22 @@ impl DiagnosticDeriveBuilder {
                         .help("only `code` is a valid nested attributes following the slug")
                         .emit(),
                 }
-            } else {
-                invalid_nested_attr(attr, &nested_attr).emit()
             }
+            return Ok(tokens);
         }
 
-        Ok(tokens.into_iter().collect())
+        let (subdiag, slug) = self.parse_subdiag_attribute(attr)?;
+        let fn_ident = format_ident!("{}", subdiag);
+        match subdiag {
+            SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
+                Ok(self.add_subdiagnostic(&fn_ident, slug))
+            }
+            SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => {
+                throw_invalid_attr!(attr, &meta, |diag| diag
+                    .help("`#[label]` and `#[suggestion]` can only be applied to fields"));
+            }
+            SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
+        }
     }
 
     fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
@@ -303,232 +303,83 @@ impl DiagnosticDeriveBuilder {
         info: FieldInfo<'_>,
         binding: TokenStream,
     ) -> Result<TokenStream, DiagnosticDeriveError> {
-        let meta = attr.parse_meta()?;
-        match meta {
-            Meta::Path(_) => self.generate_inner_field_code_path(attr, info, binding),
-            Meta::List(MetaList { .. }) => self.generate_inner_field_code_list(attr, info, binding),
-            _ => throw_invalid_attr!(attr, &meta),
-        }
-    }
-
-    fn generate_inner_field_code_path(
-        &mut self,
-        attr: &Attribute,
-        info: FieldInfo<'_>,
-        binding: TokenStream,
-    ) -> Result<TokenStream, DiagnosticDeriveError> {
-        assert!(matches!(attr.parse_meta()?, Meta::Path(_)));
         let diag = &self.diag;
-
         let meta = attr.parse_meta()?;
 
-        let ident = &attr.path.segments.last().unwrap().ident;
-        let name = ident.to_string();
-        let name = name.as_str();
-        match name {
-            "skip_arg" => {
-                // Don't need to do anything - by virtue of the attribute existing, the
-                // `set_arg` call will not be generated.
-                Ok(quote! {})
-            }
-            "primary_span" => {
-                match self.kind {
+        if let Meta::Path(_) = meta {
+            let ident = &attr.path.segments.last().unwrap().ident;
+            let name = ident.to_string();
+            let name = name.as_str();
+            match name {
+                "skip_arg" => {
+                    // Don't need to do anything - by virtue of the attribute existing, the
+                    // `set_arg` call will not be generated.
+                    return Ok(quote! {});
+                }
+                "primary_span" => match self.kind {
                     DiagnosticDeriveKind::Diagnostic => {
                         report_error_if_not_applied_to_span(attr, &info)?;
 
-                        Ok(quote! {
+                        return Ok(quote! {
                             #diag.set_span(#binding);
-                        })
+                        });
                     }
                     DiagnosticDeriveKind::LintDiagnostic => {
                         throw_invalid_attr!(attr, &meta, |diag| {
                             diag.help("the `primary_span` field attribute is not valid for lint diagnostics")
                         })
                     }
-                }
+                },
+                "subdiagnostic" => return Ok(quote! { #diag.subdiagnostic(#binding); }),
+                _ => {}
             }
-            "label" => {
+        }
+
+        let (subdiag, slug) = self.parse_subdiag_attribute(attr)?;
+
+        let fn_ident = format_ident!("{}", subdiag);
+        match subdiag {
+            SubdiagnosticKind::Label => {
                 report_error_if_not_applied_to_span(attr, &info)?;
-                Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label }))
+                Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
             }
-            "note" | "help" | "warning" => {
-                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 }),
-                    "warning" => (&warn_ident, parse_quote! { _subdiag::warn }),
-                    _ => unreachable!(),
-                };
+            SubdiagnosticKind::Note | SubdiagnosticKind::Help | SubdiagnosticKind::Warn => {
                 if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
-                    Ok(self.add_spanned_subdiagnostic(binding, ident, path))
+                    Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug))
                 } else if type_is_unit(&info.ty) {
-                    Ok(self.add_subdiagnostic(ident, path))
+                    Ok(self.add_subdiagnostic(&fn_ident, slug))
                 } else {
                     report_type_error(attr, "`Span` or `()`")?
                 }
             }
-            "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }),
-            _ => throw_invalid_attr!(attr, &meta, |diag| {
-                diag.help(
-                    "only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` \
-                     are valid field attributes",
-                )
-            }),
-        }
-    }
-
-    fn generate_inner_field_code_list(
-        &mut self,
-        attr: &Attribute,
-        info: FieldInfo<'_>,
-        binding: TokenStream,
-    ) -> Result<TokenStream, DiagnosticDeriveError> {
-        let meta = attr.parse_meta()?;
-        let Meta::List(MetaList { ref path, ref nested, .. }) = meta  else { unreachable!() };
-
-        let ident = &attr.path.segments.last().unwrap().ident;
-        let name = path.segments.last().unwrap().ident.to_string();
-        let name = name.as_ref();
-        match name {
-            "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => {
-                return self.generate_inner_field_code_suggestion(attr, info);
-            }
-            "label" | "help" | "note" | "warning" => (),
-            _ => throw_invalid_attr!(attr, &meta, |diag| {
-                diag.help(
-                    "only `label`, `help`, `note`, `warn` or `suggestion{,_short,_hidden,_verbose}` are \
-                     valid field attributes",
-                )
-            }),
-        }
-
-        // For `#[label(..)]`, `#[note(..)]` and `#[help(..)]`, the first nested element must be a
-        // path, e.g. `#[label(typeck::label)]`.
-        let mut nested_iter = nested.into_iter();
-        let msg = match nested_iter.next() {
-            Some(NestedMeta::Meta(Meta::Path(path))) => path.clone(),
-            Some(nested_attr) => throw_invalid_nested_attr!(attr, &nested_attr),
-            None => throw_invalid_attr!(attr, &meta),
-        };
-
-        // None of these attributes should have anything following the slug.
-        if nested_iter.next().is_some() {
-            throw_invalid_attr!(attr, &meta);
-        }
-
-        match name {
-            "label" => {
-                report_error_if_not_applied_to_span(attr, &info)?;
-                Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
-            }
-            "note" | "help" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => {
-                Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
-            }
-            "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)),
-            // `warning` must be special-cased because the attribute `warn` already has meaning and
-            // so isn't used, despite the diagnostic API being named `warn`.
-            "warning" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => Ok(self
-                .add_spanned_subdiagnostic(binding, &Ident::new("warn", Span::call_site()), msg)),
-            "warning" if type_is_unit(&info.ty) => {
-                Ok(self.add_subdiagnostic(&Ident::new("warn", Span::call_site()), msg))
-            }
-            "note" | "help" | "warning" => report_type_error(attr, "`Span` or `()`")?,
-            _ => unreachable!(),
-        }
-    }
-
-    fn generate_inner_field_code_suggestion(
-        &mut self,
-        attr: &Attribute,
-        info: FieldInfo<'_>,
-    ) -> Result<TokenStream, DiagnosticDeriveError> {
-        let diag = &self.diag;
-
-        let mut meta = attr.parse_meta()?;
-        let Meta::List(MetaList { ref path, ref mut nested, .. }) = meta  else { unreachable!() };
-
-        let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
-
-        let mut msg = None;
-        let mut code = None;
-
-        let mut nested_iter = nested.into_iter().peekable();
-        if let Some(nested_attr) = nested_iter.peek() {
-            if let NestedMeta::Meta(Meta::Path(path)) = nested_attr {
-                msg = Some(path.clone());
-            }
-        };
-        // Move the iterator forward if a path was found (don't otherwise so that
-        // code/applicability can be found or an error emitted).
-        if msg.is_some() {
-            let _ = nested_iter.next();
-        }
-
-        for nested_attr in nested_iter {
-            let meta = match nested_attr {
-                syn::NestedMeta::Meta(ref meta) => meta,
-                syn::NestedMeta::Lit(_) => throw_invalid_nested_attr!(attr, &nested_attr),
-            };
-
-            let nested_name = meta.path().segments.last().unwrap().ident.to_string();
-            let nested_name = nested_name.as_str();
-            match meta {
-                Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
-                    let span = meta.span().unwrap();
-                    match nested_name {
-                        "code" => {
-                            let formatted_str = self.build_format(&s.value(), s.span());
-                            code = Some(formatted_str);
-                        }
-                        "applicability" => {
-                            applicability = match applicability {
-                                Some(v) => {
-                                    span_err(
-                                        span,
-                                        "applicability cannot be set in both the field and \
-                                         attribute",
-                                    )
-                                    .emit();
-                                    Some(v)
-                                }
-                                None => match Applicability::from_str(&s.value()) {
-                                    Ok(v) => Some(quote! { #v }),
-                                    Err(()) => {
-                                        span_err(span, "invalid applicability").emit();
-                                        None
-                                    }
-                                },
-                            }
-                        }
-                        _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                            diag.help(
-                                "only `message`, `code` and `applicability` are valid field \
-                                 attributes",
-                            )
-                        }),
-                    }
+            SubdiagnosticKind::Suggestion {
+                suggestion_kind,
+                applicability: static_applicability,
+                code,
+            } => {
+                let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
+
+                if let Some((static_applicability, span)) = static_applicability {
+                    applicability.set_once(quote! { #static_applicability }, span);
                 }
-                _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                    if matches!(meta, Meta::Path(_)) {
-                        diag.help("a diagnostic slug must be the first argument to the attribute")
-                    } else {
-                        diag
-                    }
-                }),
+
+                let applicability = applicability
+                    .value()
+                    .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
+                let style = suggestion_kind.to_suggestion_style();
+
+                Ok(quote! {
+                    #diag.span_suggestion_with_style(
+                        #span_field,
+                        rustc_errors::fluent::#slug,
+                        #code,
+                        #applicability,
+                        #style
+                    );
+                })
             }
+            SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
         }
-
-        let applicability =
-            applicability.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
-
-        let name = path.segments.last().unwrap().ident.to_string();
-        let method = format_ident!("span_{}", name);
-
-        let msg = msg.unwrap_or_else(|| parse_quote! { _subdiag::suggestion });
-        let msg = quote! { rustc_errors::fluent::#msg };
-        let code = code.unwrap_or_else(|| quote! { String::new() });
-
-        Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
     }
 
     /// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
@@ -561,7 +412,7 @@ impl DiagnosticDeriveBuilder {
     fn span_and_applicability_of_ty(
         &self,
         info: FieldInfo<'_>,
-    ) -> Result<(TokenStream, Option<TokenStream>), DiagnosticDeriveError> {
+    ) -> Result<(TokenStream, SpannedOption<TokenStream>), DiagnosticDeriveError> {
         match &info.ty {
             // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
             ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
@@ -573,46 +424,37 @@ impl DiagnosticDeriveBuilder {
                 let mut span_idx = None;
                 let mut applicability_idx = None;
 
+                fn type_err(span: &Span) -> Result<!, DiagnosticDeriveError> {
+                    span_err(span.unwrap(), "wrong types for suggestion")
+                        .help(
+                            "`#[suggestion(...)]` on a tuple field must be applied to fields \
+                             of type `(Span, Applicability)`",
+                        )
+                        .emit();
+                    Err(DiagnosticDeriveError::ErrorHandled)
+                }
+
                 for (idx, elem) in tup.elems.iter().enumerate() {
                     if type_matches_path(elem, &["rustc_span", "Span"]) {
-                        if span_idx.is_none() {
-                            span_idx = Some(syn::Index::from(idx));
-                        } else {
-                            throw_span_err!(
-                                info.span.unwrap(),
-                                "type of field annotated with `#[suggestion(...)]` contains more \
-                                 than one `Span`"
-                            );
-                        }
+                        span_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
                     } else if type_matches_path(elem, &["rustc_errors", "Applicability"]) {
-                        if applicability_idx.is_none() {
-                            applicability_idx = Some(syn::Index::from(idx));
-                        } else {
-                            throw_span_err!(
-                                info.span.unwrap(),
-                                "type of field annotated with `#[suggestion(...)]` contains more \
-                                 than one Applicability"
-                            );
-                        }
+                        applicability_idx.set_once(syn::Index::from(idx), elem.span().unwrap());
+                    } else {
+                        type_err(&elem.span())?;
                     }
                 }
 
-                if let Some(span_idx) = span_idx {
-                    let binding = &info.binding.binding;
-                    let span = quote!(#binding.#span_idx);
-                    let applicability = applicability_idx
-                        .map(|applicability_idx| quote!(#binding.#applicability_idx))
-                        .unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
-
-                    return Ok((span, Some(applicability)));
-                }
+                let Some((span_idx, _)) = span_idx else {
+                    type_err(&tup.span())?;
+                };
+                let Some((applicability_idx, applicability_span)) = applicability_idx else {
+                    type_err(&tup.span())?;
+                };
+                let binding = &info.binding.binding;
+                let span = quote!(#binding.#span_idx);
+                let applicability = quote!(#binding.#applicability_idx);
 
-                throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {
-                    diag.help(
-                        "`#[suggestion(...)]` on a tuple field must be applied to fields of type \
-                         `(Span, Applicability)`",
-                    )
-                });
+                Ok((span, Some((applicability, applicability_span))))
             }
             // If `ty` isn't a `Span` or `(Span, Applicability)` then emit an error.
             _ => throw_span_err!(info.span.unwrap(), "wrong field type for suggestion", |diag| {
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index bdeca3420bc..6545ae086b1 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -4,98 +4,17 @@ use crate::diagnostics::error::{
     span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
 };
 use crate::diagnostics::utils::{
-    report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
-    Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
+    report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span, FieldInfo,
+    FieldInnerTy, HasFieldMap, SetOnce,
 };
 use proc_macro2::TokenStream;
 use quote::{format_ident, quote};
 use std::collections::HashMap;
-use std::fmt;
-use std::str::FromStr;
 use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path};
 use synstructure::{BindingInfo, Structure, VariantInfo};
 
-/// Which kind of suggestion is being created?
-#[derive(Clone, Copy)]
-enum SubdiagnosticSuggestionKind {
-    /// `#[suggestion]`
-    Normal,
-    /// `#[suggestion_short]`
-    Short,
-    /// `#[suggestion_hidden]`
-    Hidden,
-    /// `#[suggestion_verbose]`
-    Verbose,
-}
-
-impl FromStr for SubdiagnosticSuggestionKind {
-    type Err = ();
-
-    fn from_str(s: &str) -> Result<Self, Self::Err> {
-        match s {
-            "" => Ok(SubdiagnosticSuggestionKind::Normal),
-            "_short" => Ok(SubdiagnosticSuggestionKind::Short),
-            "_hidden" => Ok(SubdiagnosticSuggestionKind::Hidden),
-            "_verbose" => Ok(SubdiagnosticSuggestionKind::Verbose),
-            _ => Err(()),
-        }
-    }
-}
-
-impl SubdiagnosticSuggestionKind {
-    pub fn to_suggestion_style(&self) -> TokenStream {
-        match self {
-            SubdiagnosticSuggestionKind::Normal => {
-                quote! { rustc_errors::SuggestionStyle::ShowCode }
-            }
-            SubdiagnosticSuggestionKind::Short => {
-                quote! { rustc_errors::SuggestionStyle::HideCodeInline }
-            }
-            SubdiagnosticSuggestionKind::Hidden => {
-                quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
-            }
-            SubdiagnosticSuggestionKind::Verbose => {
-                quote! { rustc_errors::SuggestionStyle::ShowAlways }
-            }
-        }
-    }
-}
-
-/// Which kind of subdiagnostic is being created from a variant?
-#[derive(Clone)]
-enum SubdiagnosticKind {
-    /// `#[label(...)]`
-    Label,
-    /// `#[note(...)]`
-    Note,
-    /// `#[help(...)]`
-    Help,
-    /// `#[warning(...)]`
-    Warn,
-    /// `#[suggestion{,_short,_hidden,_verbose}]`
-    Suggestion { suggestion_kind: SubdiagnosticSuggestionKind, code: TokenStream },
-    /// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
-    MultipartSuggestion { suggestion_kind: SubdiagnosticSuggestionKind },
-}
-
-impl quote::IdentFragment for SubdiagnosticKind {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        match self {
-            SubdiagnosticKind::Label => write!(f, "label"),
-            SubdiagnosticKind::Note => write!(f, "note"),
-            SubdiagnosticKind::Help => write!(f, "help"),
-            SubdiagnosticKind::Warn => write!(f, "warn"),
-            SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestion_with_style"),
-            SubdiagnosticKind::MultipartSuggestion { .. } => {
-                write!(f, "multipart_suggestion_with_style")
-            }
-        }
-    }
-
-    fn span(&self) -> Option<proc_macro2::Span> {
-        None
-    }
-}
+use super::error::invalid_attr;
+use super::utils::{SpannedOption, SubdiagnosticKind};
 
 /// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
 pub(crate) struct SubdiagnosticDerive<'a> {
@@ -195,10 +114,10 @@ struct SubdiagnosticDeriveBuilder<'a> {
     fields: HashMap<String, TokenStream>,
 
     /// Identifier for the binding to the `#[primary_span]` field.
-    span_field: Option<(proc_macro2::Ident, proc_macro::Span)>,
-    /// If a suggestion, the identifier for the binding to the `#[applicability]` field or a
-    /// `rustc_errors::Applicability::*` variant directly.
-    applicability: Option<(TokenStream, proc_macro::Span)>,
+    span_field: SpannedOption<proc_macro2::Ident>,
+
+    /// The binding to the `#[applicability]` field, if present.
+    applicability: SpannedOption<TokenStream>,
 
     /// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error
     /// during finalization if still `false`.
@@ -217,6 +136,7 @@ struct KindsStatistics {
     has_multipart_suggestion: bool,
     all_multipart_suggestions: bool,
     has_normal_suggestion: bool,
+    all_applicabilities_static: bool,
 }
 
 impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
@@ -225,8 +145,15 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
             has_multipart_suggestion: false,
             all_multipart_suggestions: true,
             has_normal_suggestion: false,
+            all_applicabilities_static: true,
         };
+
         for kind in kinds {
+            if let SubdiagnosticKind::MultipartSuggestion { applicability: None, .. }
+            | SubdiagnosticKind::Suggestion { applicability: None, .. } = kind
+            {
+                ret.all_applicabilities_static = false;
+            }
             if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
                 ret.has_multipart_suggestion = true;
             } else {
@@ -246,129 +173,14 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
         let mut kind_slugs = vec![];
 
         for attr in self.variant.ast().attrs {
-            let span = attr.span().unwrap();
-
-            let name = attr.path.segments.last().unwrap().ident.to_string();
-            let name = name.as_str();
-
-            let meta = attr.parse_meta()?;
-            let Meta::List(MetaList { ref nested, .. }) = meta else {
-                throw_invalid_attr!(attr, &meta);
-            };
-
-            let mut kind = match name {
-                "label" => SubdiagnosticKind::Label,
-                "note" => SubdiagnosticKind::Note,
-                "help" => SubdiagnosticKind::Help,
-                "warning" => SubdiagnosticKind::Warn,
-                _ => {
-                    if let Some(suggestion_kind) =
-                        name.strip_prefix("suggestion").and_then(|s| s.parse().ok())
-                    {
-                        SubdiagnosticKind::Suggestion { suggestion_kind, code: TokenStream::new() }
-                    } else if let Some(suggestion_kind) =
-                        name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
-                    {
-                        SubdiagnosticKind::MultipartSuggestion { suggestion_kind }
-                    } else {
-                        throw_invalid_attr!(attr, &meta);
-                    }
-                }
-            };
+            let (kind, slug) = SubdiagnosticKind::from_attr(attr, self)?;
 
-            let mut slug = None;
-            let mut code = None;
+            let Some(slug) = slug else {
+                let name = attr.path.segments.last().unwrap().ident.to_string();
+                let name = name.as_str();
 
-            let mut nested_iter = nested.into_iter();
-            if let Some(nested_attr) = nested_iter.next() {
-                match nested_attr {
-                    NestedMeta::Meta(Meta::Path(path)) => {
-                        slug.set_once((path.clone(), span));
-                    }
-                    NestedMeta::Meta(meta @ Meta::NameValue(_))
-                        if matches!(
-                            meta.path().segments.last().unwrap().ident.to_string().as_str(),
-                            "code" | "applicability"
-                        ) =>
-                    {
-                        // Don't error for valid follow-up attributes.
-                    }
-                    nested_attr => {
-                        throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                            diag.help(
-                                "first argument of the attribute should be the diagnostic \
-                                 slug",
-                            )
-                        })
-                    }
-                };
-            }
-
-            for nested_attr in nested_iter {
-                let meta = match nested_attr {
-                    NestedMeta::Meta(ref meta) => meta,
-                    _ => throw_invalid_nested_attr!(attr, &nested_attr),
-                };
-
-                let span = meta.span().unwrap();
-                let nested_name = meta.path().segments.last().unwrap().ident.to_string();
-                let nested_name = nested_name.as_str();
-
-                let value = match meta {
-                    Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => value,
-                    Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                        diag.help("a diagnostic slug must be the first argument to the attribute")
-                    }),
-                    _ => throw_invalid_nested_attr!(attr, &nested_attr),
-                };
-
-                match nested_name {
-                    "code" => {
-                        if matches!(kind, SubdiagnosticKind::Suggestion { .. }) {
-                            let formatted_str = self.build_format(&value.value(), value.span());
-                            code.set_once((formatted_str, span));
-                        } else {
-                            span_err(
-                                span,
-                                &format!(
-                                    "`code` is not a valid nested attribute of a `{}` attribute",
-                                    name
-                                ),
-                            )
-                            .emit();
-                        }
-                    }
-                    "applicability" => {
-                        if matches!(
-                            kind,
-                            SubdiagnosticKind::Suggestion { .. }
-                                | SubdiagnosticKind::MultipartSuggestion { .. }
-                        ) {
-                            let value =
-                                Applicability::from_str(&value.value()).unwrap_or_else(|()| {
-                                    span_err(span, "invalid applicability").emit();
-                                    Applicability::Unspecified
-                                });
-                            self.applicability.set_once((quote! { #value }, span));
-                        } else {
-                            span_err(
-                                span,
-                                &format!(
-                                    "`applicability` is not a valid nested attribute of a `{}` attribute",
-                                    name
-                                )
-                            ).emit();
-                        }
-                    }
-                    _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
-                        diag.help("only `code` and `applicability` are valid nested attributes")
-                    }),
-                }
-            }
-
-            let Some((slug, _)) = slug else {
                 throw_span_err!(
-                    span,
+                    attr.span().unwrap(),
                     &format!(
                         "diagnostic slug must be first argument of a `#[{}(...)]` attribute",
                         name
@@ -376,21 +188,7 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
                 );
             };
 
-            match kind {
-                SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
-                    let Some((code, _)) = code else {
-                        throw_span_err!(span, "suggestion without `code = \"...\"`");
-                    };
-                    *code_field = code;
-                }
-                SubdiagnosticKind::Label
-                | SubdiagnosticKind::Note
-                | SubdiagnosticKind::Help
-                | SubdiagnosticKind::Warn
-                | SubdiagnosticKind::MultipartSuggestion { .. } => {}
-            }
-
-            kind_slugs.push((kind, slug))
+            kind_slugs.push((kind, slug));
         }
 
         Ok(kind_slugs)
@@ -474,18 +272,18 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
             "skip_arg" => Ok(quote! {}),
             "primary_span" => {
                 if kind_stats.has_multipart_suggestion {
-                    throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
-                        diag.help(
+                    invalid_attr(attr, &Meta::Path(path))
+                        .help(
                             "multipart suggestions use one or more `#[suggestion_part]`s rather \
                             than one `#[primary_span]`",
                         )
-                    })
-                }
-
-                report_error_if_not_applied_to_span(attr, &info)?;
+                        .emit();
+                } else {
+                    report_error_if_not_applied_to_span(attr, &info)?;
 
-                let binding = info.binding.binding.clone();
-                self.span_field.set_once((binding, span));
+                    let binding = info.binding.binding.clone();
+                    self.span_field.set_once(binding, span);
+                }
 
                 Ok(quote! {})
             }
@@ -495,28 +293,39 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
                 if kind_stats.has_multipart_suggestion {
                     span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
                         .emit();
-                    Ok(quote! {})
                 } else {
-                    throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
-                        diag.help(
-                                "`#[suggestion_part(...)]` is only valid in multipart suggestions, use `#[primary_span]` instead",
-                            )
-                    });
+                    invalid_attr(attr, &Meta::Path(path))
+                        .help(
+                            "`#[suggestion_part(...)]` is only valid in multipart suggestions, \
+                             use `#[primary_span]` instead",
+                        )
+                        .emit();
                 }
+
+                Ok(quote! {})
             }
             "applicability" => {
                 if kind_stats.has_multipart_suggestion || kind_stats.has_normal_suggestion {
                     report_error_if_not_applied_to_applicability(attr, &info)?;
 
+                    if kind_stats.all_applicabilities_static {
+                        span_err(
+                            span,
+                            "`#[applicability]` has no effect if all `#[suggestion]`/\
+                             `#[multipart_suggestion]` attributes have a static \
+                             `applicability = \"...\"`",
+                        )
+                        .emit();
+                    }
                     let binding = info.binding.binding.clone();
-                    self.applicability.set_once((quote! { #binding }, span));
+                    self.applicability.set_once(quote! { #binding }, span);
                 } else {
                     span_err(span, "`#[applicability]` is only valid on suggestions").emit();
                 }
 
                 Ok(quote! {})
             }
-            _ => throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
+            _ => {
                 let mut span_attrs = vec![];
                 if kind_stats.has_multipart_suggestion {
                     span_attrs.push("suggestion_part");
@@ -524,11 +333,16 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
                 if !kind_stats.all_multipart_suggestions {
                     span_attrs.push("primary_span")
                 }
-                diag.help(format!(
-                    "only `{}`, `applicability` and `skip_arg` are valid field attributes",
-                    span_attrs.join(", ")
-                ))
-            }),
+
+                invalid_attr(attr, &Meta::Path(path))
+                    .help(format!(
+                        "only `{}`, `applicability` and `skip_arg` are valid field attributes",
+                        span_attrs.join(", ")
+                    ))
+                    .emit();
+
+                Ok(quote! {})
+            }
         }
     }
 
@@ -577,7 +391,7 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
                     match nested_name {
                         "code" => {
                             let formatted_str = self.build_format(&value.value(), value.span());
-                            code.set_once((formatted_str, span));
+                            code.set_once(formatted_str, span);
                         }
                         _ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
                             diag.help("`code` is the only valid nested attribute")
@@ -635,11 +449,7 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
             .map(|binding| self.generate_field_attr_code(binding, kind_stats))
             .collect();
 
-        let span_field = self.span_field.as_ref().map(|(span, _)| span);
-        let applicability = self.applicability.take().map_or_else(
-            || quote! { rustc_errors::Applicability::Unspecified },
-            |(applicability, _)| applicability,
-        );
+        let span_field = self.span_field.value_ref();
 
         let diag = &self.diag;
         let mut calls = TokenStream::new();
@@ -647,7 +457,13 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
             let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
             let message = quote! { rustc_errors::fluent::#slug };
             let call = match kind {
-                SubdiagnosticKind::Suggestion { suggestion_kind, code } => {
+                SubdiagnosticKind::Suggestion { suggestion_kind, applicability, code } => {
+                    let applicability = applicability
+                        .value()
+                        .map(|a| quote! { #a })
+                        .or_else(|| self.applicability.take().value())
+                        .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
+
                     if let Some(span) = span_field {
                         let style = suggestion_kind.to_suggestion_style();
 
@@ -657,7 +473,13 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
                         quote! { unreachable!(); }
                     }
                 }
-                SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => {
+                SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability } => {
+                    let applicability = applicability
+                        .value()
+                        .map(|a| quote! { #a })
+                        .or_else(|| self.applicability.take().value())
+                        .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
+
                     if !self.has_suggestion_parts {
                         span_err(
                             self.span,
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
index ad9ecd39b9e..a31bda9ca0d 100644
--- a/compiler/rustc_macros/src/diagnostics/utils.rs
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -1,12 +1,18 @@
-use crate::diagnostics::error::{span_err, throw_span_err, DiagnosticDeriveError};
+use crate::diagnostics::error::{
+    span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
+};
 use proc_macro::Span;
 use proc_macro2::TokenStream;
 use quote::{format_ident, quote, ToTokens};
 use std::collections::{BTreeSet, HashMap};
+use std::fmt;
 use std::str::FromStr;
 use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
+use syn::{MetaList, MetaNameValue, NestedMeta, Path};
 use synstructure::{BindingInfo, Structure};
 
+use super::error::invalid_nested_attr;
+
 /// Checks whether the type name of `ty` matches `name`.
 ///
 /// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
@@ -172,13 +178,17 @@ pub(crate) struct FieldInfo<'a> {
 /// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
 /// for error reporting if they are set more than once.
 pub(crate) trait SetOnce<T> {
-    fn set_once(&mut self, _: (T, Span));
+    fn set_once(&mut self, value: T, span: Span);
 
     fn value(self) -> Option<T>;
+    fn value_ref(&self) -> Option<&T>;
 }
 
-impl<T> SetOnce<T> for Option<(T, Span)> {
-    fn set_once(&mut self, (value, span): (T, Span)) {
+/// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`].
+pub(super) type SpannedOption<T> = Option<(T, Span)>;
+
+impl<T> SetOnce<T> for SpannedOption<T> {
+    fn set_once(&mut self, value: T, span: Span) {
         match self {
             None => {
                 *self = Some((value, span));
@@ -194,6 +204,10 @@ impl<T> SetOnce<T> for Option<(T, Span)> {
     fn value(self) -> Option<T> {
         self.map(|(v, _)| v)
     }
+
+    fn value_ref(&self) -> Option<&T> {
+        self.as_ref().map(|(v, _)| v)
+    }
 }
 
 pub(crate) trait HasFieldMap {
@@ -303,6 +317,7 @@ pub(crate) trait HasFieldMap {
 
 /// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
 /// the user's selection of applicability if specified in an attribute.
+#[derive(Clone, Copy)]
 pub(crate) enum Applicability {
     MachineApplicable,
     MaybeIncorrect,
@@ -359,3 +374,250 @@ pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap<Stri
 
     fields_map
 }
+
+/// Possible styles for suggestion subdiagnostics.
+#[derive(Clone, Copy)]
+pub(super) enum SuggestionKind {
+    /// `#[suggestion]`
+    Normal,
+    /// `#[suggestion_short]`
+    Short,
+    /// `#[suggestion_hidden]`
+    Hidden,
+    /// `#[suggestion_verbose]`
+    Verbose,
+}
+
+impl FromStr for SuggestionKind {
+    type Err = ();
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "" => Ok(SuggestionKind::Normal),
+            "_short" => Ok(SuggestionKind::Short),
+            "_hidden" => Ok(SuggestionKind::Hidden),
+            "_verbose" => Ok(SuggestionKind::Verbose),
+            _ => Err(()),
+        }
+    }
+}
+
+impl SuggestionKind {
+    pub fn to_suggestion_style(&self) -> TokenStream {
+        match self {
+            SuggestionKind::Normal => {
+                quote! { rustc_errors::SuggestionStyle::ShowCode }
+            }
+            SuggestionKind::Short => {
+                quote! { rustc_errors::SuggestionStyle::HideCodeInline }
+            }
+            SuggestionKind::Hidden => {
+                quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
+            }
+            SuggestionKind::Verbose => {
+                quote! { rustc_errors::SuggestionStyle::ShowAlways }
+            }
+        }
+    }
+}
+
+/// Types of subdiagnostics that can be created using attributes
+#[derive(Clone)]
+pub(super) enum SubdiagnosticKind {
+    /// `#[label(...)]`
+    Label,
+    /// `#[note(...)]`
+    Note,
+    /// `#[help(...)]`
+    Help,
+    /// `#[warning(...)]`
+    Warn,
+    /// `#[suggestion{,_short,_hidden,_verbose}]`
+    Suggestion {
+        suggestion_kind: SuggestionKind,
+        applicability: SpannedOption<Applicability>,
+        code: TokenStream,
+    },
+    /// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
+    MultipartSuggestion {
+        suggestion_kind: SuggestionKind,
+        applicability: SpannedOption<Applicability>,
+    },
+}
+
+impl SubdiagnosticKind {
+    /// Constructs a `SubdiagnosticKind` from a field or type attribute such as `#[note]`,
+    /// `#[error(parser::add_paren)]` or `#[suggestion(code = "...")]`. Returns the
+    /// `SubdiagnosticKind` and the diagnostic slug, if specified.
+    pub(super) fn from_attr(
+        attr: &Attribute,
+        fields: &impl HasFieldMap,
+    ) -> Result<(SubdiagnosticKind, Option<Path>), DiagnosticDeriveError> {
+        let span = attr.span().unwrap();
+
+        let name = attr.path.segments.last().unwrap().ident.to_string();
+        let name = name.as_str();
+
+        let meta = attr.parse_meta()?;
+        let mut kind = match name {
+            "label" => SubdiagnosticKind::Label,
+            "note" => SubdiagnosticKind::Note,
+            "help" => SubdiagnosticKind::Help,
+            "warning" => SubdiagnosticKind::Warn,
+            _ => {
+                if let Some(suggestion_kind) =
+                    name.strip_prefix("suggestion").and_then(|s| s.parse().ok())
+                {
+                    SubdiagnosticKind::Suggestion {
+                        suggestion_kind,
+                        applicability: None,
+                        code: TokenStream::new(),
+                    }
+                } else if let Some(suggestion_kind) =
+                    name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
+                {
+                    SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability: None }
+                } else {
+                    throw_invalid_attr!(attr, &meta);
+                }
+            }
+        };
+
+        let nested = match meta {
+            Meta::List(MetaList { ref nested, .. }) => {
+                // An attribute with properties, such as `#[suggestion(code = "...")]` or
+                // `#[error(some::slug)]`
+                nested
+            }
+            Meta::Path(_) => {
+                // An attribute without a slug or other properties, such as `#[note]` - return
+                // without further processing.
+                //
+                // Only allow this if there are no mandatory properties, such as `code = "..."` in
+                // `#[suggestion(...)]`
+                match kind {
+                    SubdiagnosticKind::Label
+                    | SubdiagnosticKind::Note
+                    | SubdiagnosticKind::Help
+                    | SubdiagnosticKind::Warn
+                    | SubdiagnosticKind::MultipartSuggestion { .. } => return Ok((kind, None)),
+                    SubdiagnosticKind::Suggestion { .. } => {
+                        throw_span_err!(span, "suggestion without `code = \"...\"`")
+                    }
+                }
+            }
+            _ => {
+                throw_invalid_attr!(attr, &meta)
+            }
+        };
+
+        let mut code = None;
+
+        let mut nested_iter = nested.into_iter().peekable();
+
+        // Peek at the first nested attribute: if it's a slug path, consume it.
+        let slug = if let Some(NestedMeta::Meta(Meta::Path(path))) = nested_iter.peek() {
+            let path = path.clone();
+            // Advance the iterator.
+            nested_iter.next();
+            Some(path)
+        } else {
+            None
+        };
+
+        for nested_attr in nested_iter {
+            let meta = match nested_attr {
+                NestedMeta::Meta(ref meta) => meta,
+                NestedMeta::Lit(_) => {
+                    invalid_nested_attr(attr, &nested_attr).emit();
+                    continue;
+                }
+            };
+
+            let span = meta.span().unwrap();
+            let nested_name = meta.path().segments.last().unwrap().ident.to_string();
+            let nested_name = nested_name.as_str();
+
+            let value = match meta {
+                Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => value,
+                Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+                    diag.help("a diagnostic slug must be the first argument to the attribute")
+                }),
+                _ => {
+                    invalid_nested_attr(attr, &nested_attr).emit();
+                    continue;
+                }
+            };
+
+            match (nested_name, &mut kind) {
+                ("code", SubdiagnosticKind::Suggestion { .. }) => {
+                    let formatted_str = fields.build_format(&value.value(), value.span());
+                    code.set_once(formatted_str, span);
+                }
+                (
+                    "applicability",
+                    SubdiagnosticKind::Suggestion { ref mut applicability, .. }
+                    | SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. },
+                ) => {
+                    let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| {
+                        span_err(span, "invalid applicability").emit();
+                        Applicability::Unspecified
+                    });
+                    applicability.set_once(value, span);
+                }
+
+                // Invalid nested attribute
+                (_, SubdiagnosticKind::Suggestion { .. }) => {
+                    invalid_nested_attr(attr, &nested_attr)
+                        .help("only `code` and `applicability` are valid nested attributes")
+                        .emit();
+                }
+                (_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
+                    invalid_nested_attr(attr, &nested_attr)
+                        .help("only `applicability` is a valid nested attributes")
+                        .emit()
+                }
+                _ => {
+                    invalid_nested_attr(attr, &nested_attr).emit();
+                }
+            }
+        }
+
+        match kind {
+            SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
+                *code_field = if let Some((code, _)) = code {
+                    code
+                } else {
+                    span_err(span, "suggestion without `code = \"...\"`").emit();
+                    quote! { "" }
+                }
+            }
+            SubdiagnosticKind::Label
+            | SubdiagnosticKind::Note
+            | SubdiagnosticKind::Help
+            | SubdiagnosticKind::Warn
+            | SubdiagnosticKind::MultipartSuggestion { .. } => {}
+        }
+
+        Ok((kind, slug))
+    }
+}
+
+impl quote::IdentFragment for SubdiagnosticKind {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            SubdiagnosticKind::Label => write!(f, "label"),
+            SubdiagnosticKind::Note => write!(f, "note"),
+            SubdiagnosticKind::Help => write!(f, "help"),
+            SubdiagnosticKind::Warn => write!(f, "warn"),
+            SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestion_with_style"),
+            SubdiagnosticKind::MultipartSuggestion { .. } => {
+                write!(f, "multipart_suggestion_with_style")
+            }
+        }
+    }
+
+    fn span(&self) -> Option<proc_macro2::Span> {
+        None
+    }
+}
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index dcea11eadcb..0a81cde93be 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -289,7 +289,7 @@ pub enum BadTypePlusSub {
 #[diag(parser::maybe_recover_from_bad_qpath_stage_2)]
 struct BadQPathStage2 {
     #[primary_span]
-    #[suggestion(applicability = "maybe-incorrect")]
+    #[suggestion(code = "", applicability = "maybe-incorrect")]
     span: Span,
     ty: String,
 }
@@ -298,7 +298,7 @@ struct BadQPathStage2 {
 #[diag(parser::incorrect_semicolon)]
 struct IncorrectSemicolon<'a> {
     #[primary_span]
-    #[suggestion_short(applicability = "machine-applicable")]
+    #[suggestion_short(code = "", applicability = "machine-applicable")]
     span: Span,
     #[help]
     opt_help: Option<()>,
@@ -309,7 +309,7 @@ struct IncorrectSemicolon<'a> {
 #[diag(parser::incorrect_use_of_await)]
 struct IncorrectUseOfAwait {
     #[primary_span]
-    #[suggestion(parser::parentheses_suggestion, applicability = "machine-applicable")]
+    #[suggestion(parser::parentheses_suggestion, code = "", applicability = "machine-applicable")]
     span: Span,
 }
 
@@ -329,7 +329,7 @@ struct IncorrectAwait {
 struct InInTypo {
     #[primary_span]
     span: Span,
-    #[suggestion(applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable")]
     sugg_span: Span,
 }
 
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index cdfa7cf7f93..cc231af71a2 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -462,7 +462,7 @@ pub struct LinkSection {
 pub struct NoMangleForeign {
     #[label]
     pub span: Span,
-    #[suggestion(applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable")]
     pub attr_span: Span,
     pub foreign_item_kind: &'static str,
 }
@@ -596,7 +596,7 @@ pub enum UnusedNote {
 #[derive(LintDiagnostic)]
 #[diag(passes::unused)]
 pub struct Unused {
-    #[suggestion(applicability = "machine-applicable")]
+    #[suggestion(code = "", applicability = "machine-applicable")]
     pub attr_span: Span,
     #[subdiagnostic]
     pub note: UnusedNote,
diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
index 80ea9082881..ad481c14bab 100644
--- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
+++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs
@@ -76,13 +76,15 @@ struct InvalidNestedStructAttr1 {}
 #[derive(Diagnostic)]
 #[diag(nonsense = "...", code = "E0123", slug = "foo")]
 //~^ ERROR `#[diag(nonsense = ...)]` is not a valid attribute
-//~^^ ERROR diagnostic slug not specified
+//~| ERROR `#[diag(slug = ...)]` is not a valid attribute
+//~| ERROR diagnostic slug not specified
 struct InvalidNestedStructAttr2 {}
 
 #[derive(Diagnostic)]
 #[diag(nonsense = 4, code = "E0123", slug = "foo")]
 //~^ ERROR `#[diag(nonsense = ...)]` is not a valid attribute
-//~^^ ERROR diagnostic slug not specified
+//~| ERROR `#[diag(slug = ...)]` is not a valid attribute
+//~| ERROR diagnostic slug not specified
 struct InvalidNestedStructAttr3 {}
 
 #[derive(Diagnostic)]
@@ -217,6 +219,7 @@ struct Suggest {
 #[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
 struct SuggestWithoutCode {
     #[suggestion(typeck::suggestion)]
+    //~^ ERROR suggestion without `code = "..."`
     suggestion: (Span, Applicability),
 }
 
@@ -225,6 +228,7 @@ struct SuggestWithoutCode {
 struct SuggestWithBadKey {
     #[suggestion(nonsense = "bar")]
     //~^ ERROR `#[suggestion(nonsense = ...)]` is not a valid attribute
+    //~| ERROR suggestion without `code = "..."`
     suggestion: (Span, Applicability),
 }
 
@@ -233,6 +237,7 @@ struct SuggestWithBadKey {
 struct SuggestWithShorthandMsg {
     #[suggestion(msg = "bar")]
     //~^ ERROR `#[suggestion(msg = ...)]` is not a valid attribute
+    //~| ERROR suggestion without `code = "..."`
     suggestion: (Span, Applicability),
 }
 
@@ -269,16 +274,16 @@ struct SuggestWithSpanOnly {
 #[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
 struct SuggestWithDuplicateSpanAndApplicability {
     #[suggestion(typeck::suggestion, code = "This is suggested code")]
-    //~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one `Span`
     suggestion: (Span, Span, Applicability),
+    //~^ ERROR specified multiple times
 }
 
 #[derive(Diagnostic)]
 #[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
 struct SuggestWithDuplicateApplicabilityAndSpan {
     #[suggestion(typeck::suggestion, code = "This is suggested code")]
-    //~^ ERROR type of field annotated with `#[suggestion(...)]` contains more than one
     suggestion: (Applicability, Applicability, Span),
+    //~^ ERROR specified multiple times
 }
 
 #[derive(Diagnostic)]
@@ -294,7 +299,7 @@ struct WrongKindOfAnnotation {
 struct OptionsInErrors {
     #[label(typeck::label)]
     label: Option<Span>,
-    #[suggestion(typeck::suggestion)]
+    #[suggestion(typeck::suggestion, code = "...")]
     opt_sugg: Option<(Span, Applicability)>,
 }
 
@@ -436,7 +441,7 @@ struct ErrorWithNoteCustomWrongOrder {
 #[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
 struct ApplicabilityInBoth {
     #[suggestion(typeck::suggestion, code = "...", applicability = "maybe-incorrect")]
-    //~^ ERROR applicability cannot be set in both the field and attribute
+    //~^ ERROR specified multiple times
     suggestion: (Span, Applicability),
 }
 
@@ -507,7 +512,7 @@ struct OptUnitField {
 #[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
 struct LabelWithTrailingPath {
     #[label(typeck::label, foo)]
-    //~^ ERROR `#[label(...)]` is not a valid attribute
+    //~^ ERROR `#[label(foo)]` is not a valid attribute
     span: Span,
 }
 
@@ -515,7 +520,7 @@ struct LabelWithTrailingPath {
 #[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
 struct LabelWithTrailingNameValue {
     #[label(typeck::label, foo = "...")]
-    //~^ ERROR `#[label(...)]` is not a valid attribute
+    //~^ ERROR `#[label(foo = ...)]` is not a valid attribute
     span: Span,
 }
 
@@ -523,7 +528,7 @@ struct LabelWithTrailingNameValue {
 #[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
 struct LabelWithTrailingList {
     #[label(typeck::label, foo("..."))]
-    //~^ ERROR `#[label(...)]` is not a valid attribute
+    //~^ ERROR `#[label(foo(...))]` is not a valid attribute
     span: Span,
 }
 
@@ -581,3 +586,68 @@ struct LintAttributeOnSessionDiag {}
 //~| ERROR diagnostic slug not specified
 //~| ERROR cannot find attribute `lint` in this scope
 struct LintAttributeOnLintDiag {}
+
+#[derive(Diagnostic)]
+#[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
+struct DuplicatedSuggestionCode {
+    #[suggestion(typeck::suggestion, code = "...", code = ",,,")]
+    //~^ ERROR specified multiple times
+    suggestion: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
+struct InvalidTypeInSuggestionTuple {
+    #[suggestion(typeck::suggestion, code = "...")]
+    suggestion: (Span, usize),
+    //~^ ERROR wrong types for suggestion
+}
+
+#[derive(Diagnostic)]
+#[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
+struct MissingApplicabilityInSuggestionTuple {
+    #[suggestion(typeck::suggestion, code = "...")]
+    suggestion: (Span,),
+    //~^ ERROR wrong types for suggestion
+}
+
+#[derive(Diagnostic)]
+#[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
+struct MissingCodeInSuggestion {
+    #[suggestion(typeck::suggestion)]
+    //~^ ERROR suggestion without `code = "..."`
+    suggestion: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
+#[multipart_suggestion(typeck::suggestion)]
+//~^ ERROR `#[multipart_suggestion(...)]` is not a valid attribute
+//~| ERROR cannot find attribute `multipart_suggestion` in this scope
+#[multipart_suggestion()]
+//~^ ERROR `#[multipart_suggestion(...)]` is not a valid attribute
+//~| ERROR cannot find attribute `multipart_suggestion` in this scope
+struct MultipartSuggestion {
+    #[multipart_suggestion(typeck::suggestion)]
+    //~^ ERROR `#[multipart_suggestion(...)]` is not a valid attribute
+    //~| ERROR cannot find attribute `multipart_suggestion` in this scope
+    suggestion: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
+#[suggestion(typeck::suggestion, code = "...")]
+//~^ ERROR `#[suggestion(...)]` is not a valid attribute
+struct SuggestionOnStruct {
+    #[primary_span]
+    suggestion: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
+#[label]
+//~^ ERROR `#[label]` is not a valid attribute
+struct LabelOnStruct {
+    #[primary_span]
+    suggestion: Span,
+}
diff --git a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
index c3972beb512..9919b12beaf 100644
--- a/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
+++ b/src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr
@@ -20,8 +20,6 @@ error: `#[nonsense(...)]` is not a valid attribute
    |
 LL | #[nonsense(typeck::ambiguous_lifetime_bound, code = "E0123")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: only `diag`, `help`, `note` and `warning` are valid attributes
 
 error: diagnostic slug not specified
   --> $DIR/diagnostic-derive.rs:53:1
@@ -41,7 +39,7 @@ error: `#[diag("...")]` is not a valid attribute
 LL | #[diag("E0123")]
    |        ^^^^^^^
    |
-   = help: first argument of the attribute should be the diagnostic slug
+   = help: a diagnostic slug is required as the first argument
 
 error: diagnostic slug not specified
   --> $DIR/diagnostic-derive.rs:60:1
@@ -60,7 +58,7 @@ error: `#[diag(nonsense(...))]` is not a valid attribute
 LL | #[diag(nonsense("foo"), code = "E0123", slug = "foo")]
    |        ^^^^^^^^^^^^^^^
    |
-   = help: first argument of the attribute should be the diagnostic slug
+   = help: a diagnostic slug is required as the first argument
 
 error: diagnostic slug not specified
   --> $DIR/diagnostic-derive.rs:71:1
@@ -79,7 +77,15 @@ error: `#[diag(nonsense = ...)]` is not a valid attribute
 LL | #[diag(nonsense = "...", code = "E0123", slug = "foo")]
    |        ^^^^^^^^^^^^^^^^
    |
-   = help: first argument of the attribute should be the diagnostic slug
+   = help: only `code` is a valid nested attributes following the slug
+
+error: `#[diag(slug = ...)]` is not a valid attribute
+  --> $DIR/diagnostic-derive.rs:77:42
+   |
+LL | #[diag(nonsense = "...", code = "E0123", slug = "foo")]
+   |                                          ^^^^^^^^^^^^
+   |
+   = help: only `code` is a valid nested attributes following the slug
 
 error: diagnostic slug not specified
   --> $DIR/diagnostic-derive.rs:77:1
@@ -87,32 +93,40 @@ error: diagnostic slug not specified
 LL | / #[diag(nonsense = "...", code = "E0123", slug = "foo")]
 LL | |
 LL | |
+LL | |
 LL | | struct InvalidNestedStructAttr2 {}
    | |__________________________________^
    |
    = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(typeck::example_error)]`
 
 error: `#[diag(nonsense = ...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:83:8
+  --> $DIR/diagnostic-derive.rs:84:8
    |
 LL | #[diag(nonsense = 4, code = "E0123", slug = "foo")]
    |        ^^^^^^^^^^^^
+
+error: `#[diag(slug = ...)]` is not a valid attribute
+  --> $DIR/diagnostic-derive.rs:84:38
    |
-   = help: first argument of the attribute should be the diagnostic slug
+LL | #[diag(nonsense = 4, code = "E0123", slug = "foo")]
+   |                                      ^^^^^^^^^^^^
+   |
+   = help: only `code` is a valid nested attributes following the slug
 
 error: diagnostic slug not specified
-  --> $DIR/diagnostic-derive.rs:83:1
+  --> $DIR/diagnostic-derive.rs:84:1
    |
 LL | / #[diag(nonsense = 4, code = "E0123", slug = "foo")]
 LL | |
 LL | |
+LL | |
 LL | | struct InvalidNestedStructAttr3 {}
    | |__________________________________^
    |
    = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(typeck::example_error)]`
 
 error: `#[diag(slug = ...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:89:58
+  --> $DIR/diagnostic-derive.rs:91:58
    |
 LL | #[diag(typeck::ambiguous_lifetime_bound, code = "E0123", slug = "foo")]
    |                                                          ^^^^^^^^^^^^
@@ -120,55 +134,57 @@ LL | #[diag(typeck::ambiguous_lifetime_bound, code = "E0123", slug = "foo")]
    = help: only `code` is a valid nested attributes following the slug
 
 error: `#[suggestion = ...]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:96:5
+  --> $DIR/diagnostic-derive.rs:98:5
    |
 LL |     #[suggestion = "bar"]
    |     ^^^^^^^^^^^^^^^^^^^^^
 
 error: specified multiple times
-  --> $DIR/diagnostic-derive.rs:103:1
+  --> $DIR/diagnostic-derive.rs:105:8
    |
 LL | #[diag(typeck::ambiguous_lifetime_bound, code = "E0456")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    |
 note: previously specified here
-  --> $DIR/diagnostic-derive.rs:102:1
+  --> $DIR/diagnostic-derive.rs:104:8
    |
 LL | #[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
-   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: specified multiple times
-  --> $DIR/diagnostic-derive.rs:103:49
+  --> $DIR/diagnostic-derive.rs:105:49
    |
 LL | #[diag(typeck::ambiguous_lifetime_bound, code = "E0456")]
    |                                                 ^^^^^^^
    |
 note: previously specified here
-  --> $DIR/diagnostic-derive.rs:102:49
+  --> $DIR/diagnostic-derive.rs:104:49
    |
 LL | #[diag(typeck::ambiguous_lifetime_bound, code = "E0123")]
    |                                                 ^^^^^^^
 
 error: specified multiple times
-  --> $DIR/diagnostic-derive.rs:109:65
+  --> $DIR/diagnostic-derive.rs:111:65
    |
 LL | #[diag(typeck::ambiguous_lifetime_bound, code = "E0456", code = "E0457")]
    |                                                                 ^^^^^^^
    |
 note: previously specified here
-  --> $DIR/diagnostic-derive.rs:109:49
+  --> $DIR/diagnostic-derive.rs:111:49
    |
 LL | #[diag(typeck::ambiguous_lifetime_bound, code = "E0456", code = "E0457")]
    |                                                 ^^^^^^^
 
 error: `#[diag(typeck::ambiguous_lifetime_bound)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:114:42
+  --> $DIR/diagnostic-derive.rs:116:42
    |
 LL | #[diag(typeck::ambiguous_lifetime_bound, typeck::ambiguous_lifetime_bound, code = "E0456")]
    |                                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: diagnostic slug must be the first argument
 
 error: diagnostic slug not specified
-  --> $DIR/diagnostic-derive.rs:119:1
+  --> $DIR/diagnostic-derive.rs:121:1
    |
 LL | struct KindNotProvided {}
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -176,7 +192,7 @@ LL | struct KindNotProvided {}
    = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(typeck::example_error)]`
 
 error: diagnostic slug not specified
-  --> $DIR/diagnostic-derive.rs:122:1
+  --> $DIR/diagnostic-derive.rs:124:1
    |
 LL | / #[diag(code = "E0456")]
 LL | |
@@ -186,33 +202,31 @@ LL | | struct SlugNotProvided {}
    = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(typeck::example_error)]`
 
 error: the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
-  --> $DIR/diagnostic-derive.rs:133:5
+  --> $DIR/diagnostic-derive.rs:135:5
    |
 LL |     #[primary_span]
    |     ^^^^^^^^^^^^^^^
 
 error: `#[nonsense]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:141:5
+  --> $DIR/diagnostic-derive.rs:143:5
    |
 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` or `MultiSpan`
-  --> $DIR/diagnostic-derive.rs:158:5
+  --> $DIR/diagnostic-derive.rs:160:5
    |
 LL |     #[label(typeck::label)]
    |     ^^^^^^^^^^^^^^^^^^^^^^^
 
 error: `name` doesn't refer to a field on this type
-  --> $DIR/diagnostic-derive.rs:166:45
+  --> $DIR/diagnostic-derive.rs:168:45
    |
 LL |     #[suggestion(typeck::suggestion, code = "{name}")]
    |                                             ^^^^^^^^
 
 error: invalid format string: expected `'}'` but string was terminated
-  --> $DIR/diagnostic-derive.rs:171:16
+  --> $DIR/diagnostic-derive.rs:173:16
    |
 LL | #[derive(Diagnostic)]
    |           -    ^ expected `'}'` in format string
@@ -223,7 +237,7 @@ LL | #[derive(Diagnostic)]
    = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: invalid format string: unmatched `}` found
-  --> $DIR/diagnostic-derive.rs:181:15
+  --> $DIR/diagnostic-derive.rs:183:15
    |
 LL | #[derive(Diagnostic)]
    |               ^ unmatched `}` in format string
@@ -232,29 +246,47 @@ LL | #[derive(Diagnostic)]
    = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
 
 error: the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
-  --> $DIR/diagnostic-derive.rs:201:5
+  --> $DIR/diagnostic-derive.rs:203:5
    |
 LL |     #[label(typeck::label)]
    |     ^^^^^^^^^^^^^^^^^^^^^^^
 
+error: suggestion without `code = "..."`
+  --> $DIR/diagnostic-derive.rs:221:5
+   |
+LL |     #[suggestion(typeck::suggestion)]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
 error: `#[suggestion(nonsense = ...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:226:18
+  --> $DIR/diagnostic-derive.rs:229:18
    |
 LL |     #[suggestion(nonsense = "bar")]
    |                  ^^^^^^^^^^^^^^^^
    |
-   = help: only `message`, `code` and `applicability` are valid field attributes
+   = help: only `code` and `applicability` are valid nested attributes
+
+error: suggestion without `code = "..."`
+  --> $DIR/diagnostic-derive.rs:229:5
+   |
+LL |     #[suggestion(nonsense = "bar")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: `#[suggestion(msg = ...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:234:18
+  --> $DIR/diagnostic-derive.rs:238:18
    |
 LL |     #[suggestion(msg = "bar")]
    |                  ^^^^^^^^^^^
    |
-   = help: only `message`, `code` and `applicability` are valid field attributes
+   = help: only `code` and `applicability` are valid nested attributes
+
+error: suggestion without `code = "..."`
+  --> $DIR/diagnostic-derive.rs:238:5
+   |
+LL |     #[suggestion(msg = "bar")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: wrong field type for suggestion
-  --> $DIR/diagnostic-derive.rs:256:5
+  --> $DIR/diagnostic-derive.rs:261:5
    |
 LL | /     #[suggestion(typeck::suggestion, code = "This is suggested code")]
 LL | |
@@ -263,60 +295,76 @@ LL | |     suggestion: Applicability,
    |
    = help: `#[suggestion(...)]` should be applied to fields of type `Span` or `(Span, Applicability)`
 
-error: type of field annotated with `#[suggestion(...)]` contains more than one `Span`
-  --> $DIR/diagnostic-derive.rs:271:5
+error: specified multiple times
+  --> $DIR/diagnostic-derive.rs:277:24
    |
-LL | /     #[suggestion(typeck::suggestion, code = "This is suggested code")]
-LL | |
-LL | |     suggestion: (Span, Span, Applicability),
-   | |___________________________________________^
+LL |     suggestion: (Span, Span, Applicability),
+   |                        ^^^^
+   |
+note: previously specified here
+  --> $DIR/diagnostic-derive.rs:277:18
+   |
+LL |     suggestion: (Span, Span, Applicability),
+   |                  ^^^^
 
-error: type of field annotated with `#[suggestion(...)]` contains more than one Applicability
-  --> $DIR/diagnostic-derive.rs:279:5
+error: specified multiple times
+  --> $DIR/diagnostic-derive.rs:285:33
    |
-LL | /     #[suggestion(typeck::suggestion, code = "This is suggested code")]
-LL | |
-LL | |     suggestion: (Applicability, Applicability, Span),
-   | |____________________________________________________^
+LL |     suggestion: (Applicability, Applicability, Span),
+   |                                 ^^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/diagnostic-derive.rs:285:18
+   |
+LL |     suggestion: (Applicability, Applicability, Span),
+   |                  ^^^^^^^^^^^^^
 
 error: `#[label = ...]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:287:5
+  --> $DIR/diagnostic-derive.rs:292:5
    |
 LL |     #[label = "bar"]
    |     ^^^^^^^^^^^^^^^^
 
-error: applicability cannot be set in both the field and attribute
-  --> $DIR/diagnostic-derive.rs:438:52
+error: specified multiple times
+  --> $DIR/diagnostic-derive.rs:443:52
    |
 LL |     #[suggestion(typeck::suggestion, code = "...", applicability = "maybe-incorrect")]
    |                                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/diagnostic-derive.rs:445:24
+   |
+LL |     suggestion: (Span, Applicability),
+   |                        ^^^^^^^^^^^^^
 
 error: invalid applicability
-  --> $DIR/diagnostic-derive.rs:446:52
+  --> $DIR/diagnostic-derive.rs:451:52
    |
 LL |     #[suggestion(typeck::suggestion, code = "...", applicability = "batman")]
    |                                                    ^^^^^^^^^^^^^^^^^^^^^^^^
 
-error: `#[label(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:509:5
+error: `#[label(foo)]` is not a valid attribute
+  --> $DIR/diagnostic-derive.rs:514:28
    |
 LL |     #[label(typeck::label, foo)]
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                            ^^^
+   |
+   = help: a diagnostic slug must be the first argument to the attribute
 
-error: `#[label(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:517:5
+error: `#[label(foo = ...)]` is not a valid attribute
+  --> $DIR/diagnostic-derive.rs:522:28
    |
 LL |     #[label(typeck::label, foo = "...")]
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                            ^^^^^^^^^^^
 
-error: `#[label(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:525:5
+error: `#[label(foo(...))]` is not a valid attribute
+  --> $DIR/diagnostic-derive.rs:530:28
    |
 LL |     #[label(typeck::label, foo("..."))]
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |                            ^^^^^^^^^^
 
 error: `#[primary_span]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:538:5
+  --> $DIR/diagnostic-derive.rs:543:5
    |
 LL |     #[primary_span]
    |     ^^^^^^^^^^^^^^^
@@ -324,15 +372,13 @@ LL |     #[primary_span]
    = help: the `primary_span` field attribute is not valid for lint diagnostics
 
 error: `#[error(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:558:1
+  --> $DIR/diagnostic-derive.rs:563:1
    |
 LL | #[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: `error` and `lint` have been replaced by `diag`
 
 error: diagnostic slug not specified
-  --> $DIR/diagnostic-derive.rs:558:1
+  --> $DIR/diagnostic-derive.rs:563:1
    |
 LL | / #[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
 LL | |
@@ -344,15 +390,13 @@ LL | | struct ErrorAttribute {}
    = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(typeck::example_error)]`
 
 error: `#[warn_(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:565:1
+  --> $DIR/diagnostic-derive.rs:570:1
    |
 LL | #[warn_(typeck::ambiguous_lifetime_bound, code = "E0123")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: `warn_` have been replaced by `warning`
 
 error: diagnostic slug not specified
-  --> $DIR/diagnostic-derive.rs:565:1
+  --> $DIR/diagnostic-derive.rs:570:1
    |
 LL | / #[warn_(typeck::ambiguous_lifetime_bound, code = "E0123")]
 LL | |
@@ -364,15 +408,13 @@ LL | | struct WarnAttribute {}
    = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(typeck::example_error)]`
 
 error: `#[lint(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:572:1
+  --> $DIR/diagnostic-derive.rs:577:1
    |
 LL | #[lint(typeck::ambiguous_lifetime_bound, code = "E0123")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: `error` and `lint` have been replaced by `diag`
 
 error: diagnostic slug not specified
-  --> $DIR/diagnostic-derive.rs:572:1
+  --> $DIR/diagnostic-derive.rs:577:1
    |
 LL | / #[lint(typeck::ambiguous_lifetime_bound, code = "E0123")]
 LL | |
@@ -384,15 +426,13 @@ LL | | struct LintAttributeOnSessionDiag {}
    = help: specify the slug as the first argument to the `#[diag(...)]` attribute, such as `#[diag(typeck::example_error)]`
 
 error: `#[lint(...)]` is not a valid attribute
-  --> $DIR/diagnostic-derive.rs:579:1
+  --> $DIR/diagnostic-derive.rs:584:1
    |
 LL | #[lint(typeck::ambiguous_lifetime_bound, code = "E0123")]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: `error` and `lint` have been replaced by `diag`
 
 error: diagnostic slug not specified
-  --> $DIR/diagnostic-derive.rs:579:1
+  --> $DIR/diagnostic-derive.rs:584:1
    |
 LL | / #[lint(typeck::ambiguous_lifetime_bound, code = "E0123")]
 LL | |
@@ -403,6 +443,80 @@ LL | | struct LintAttributeOnLintDiag {}
    |
    = help: specify the slug as the first argument to the attribute, such as `#[diag(typeck::example_error)]`
 
+error: specified multiple times
+  --> $DIR/diagnostic-derive.rs:593:52
+   |
+LL |     #[suggestion(typeck::suggestion, code = "...", code = ",,,")]
+   |                                                    ^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/diagnostic-derive.rs:593:38
+   |
+LL |     #[suggestion(typeck::suggestion, code = "...", code = ",,,")]
+   |                                      ^^^^^^^^^^^^
+
+error: wrong types for suggestion
+  --> $DIR/diagnostic-derive.rs:602:24
+   |
+LL |     suggestion: (Span, usize),
+   |                        ^^^^^
+   |
+   = help: `#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`
+
+error: wrong types for suggestion
+  --> $DIR/diagnostic-derive.rs:610:17
+   |
+LL |     suggestion: (Span,),
+   |                 ^^^^^^^
+   |
+   = help: `#[suggestion(...)]` on a tuple field must be applied to fields of type `(Span, Applicability)`
+
+error: suggestion without `code = "..."`
+  --> $DIR/diagnostic-derive.rs:617:5
+   |
+LL |     #[suggestion(typeck::suggestion)]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: `#[multipart_suggestion(...)]` is not a valid attribute
+  --> $DIR/diagnostic-derive.rs:624:1
+   |
+LL | #[multipart_suggestion(typeck::suggestion)]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: consider creating a `Subdiagnostic` instead
+
+error: `#[multipart_suggestion(...)]` is not a valid attribute
+  --> $DIR/diagnostic-derive.rs:627:1
+   |
+LL | #[multipart_suggestion()]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: consider creating a `Subdiagnostic` instead
+
+error: `#[multipart_suggestion(...)]` is not a valid attribute
+  --> $DIR/diagnostic-derive.rs:631:5
+   |
+LL |     #[multipart_suggestion(typeck::suggestion)]
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: consider creating a `Subdiagnostic` instead
+
+error: `#[suggestion(...)]` is not a valid attribute
+  --> $DIR/diagnostic-derive.rs:639:1
+   |
+LL | #[suggestion(typeck::suggestion, code = "...")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = help: `#[label]` and `#[suggestion]` can only be applied to fields
+
+error: `#[label]` is not a valid attribute
+  --> $DIR/diagnostic-derive.rs:648:1
+   |
+LL | #[label]
+   | ^^^^^^^^
+   |
+   = help: `#[label]` and `#[suggestion]` can only be applied to fields
+
 error: cannot find attribute `nonsense` in this scope
   --> $DIR/diagnostic-derive.rs:53:3
    |
@@ -410,35 +524,53 @@ LL | #[nonsense(typeck::ambiguous_lifetime_bound, code = "E0123")]
    |   ^^^^^^^^
 
 error: cannot find attribute `nonsense` in this scope
-  --> $DIR/diagnostic-derive.rs:141:7
+  --> $DIR/diagnostic-derive.rs:143:7
    |
 LL |     #[nonsense]
    |       ^^^^^^^^
 
 error: cannot find attribute `error` in this scope
-  --> $DIR/diagnostic-derive.rs:558:3
+  --> $DIR/diagnostic-derive.rs:563:3
    |
 LL | #[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
    |   ^^^^^
 
 error: cannot find attribute `warn_` in this scope
-  --> $DIR/diagnostic-derive.rs:565:3
+  --> $DIR/diagnostic-derive.rs:570:3
    |
 LL | #[warn_(typeck::ambiguous_lifetime_bound, code = "E0123")]
    |   ^^^^^ help: a built-in attribute with a similar name exists: `warn`
 
 error: cannot find attribute `lint` in this scope
-  --> $DIR/diagnostic-derive.rs:572:3
+  --> $DIR/diagnostic-derive.rs:577:3
    |
 LL | #[lint(typeck::ambiguous_lifetime_bound, code = "E0123")]
    |   ^^^^ help: a built-in attribute with a similar name exists: `link`
 
 error: cannot find attribute `lint` in this scope
-  --> $DIR/diagnostic-derive.rs:579:3
+  --> $DIR/diagnostic-derive.rs:584:3
    |
 LL | #[lint(typeck::ambiguous_lifetime_bound, code = "E0123")]
    |   ^^^^ help: a built-in attribute with a similar name exists: `link`
 
+error: cannot find attribute `multipart_suggestion` in this scope
+  --> $DIR/diagnostic-derive.rs:624:3
+   |
+LL | #[multipart_suggestion(typeck::suggestion)]
+   |   ^^^^^^^^^^^^^^^^^^^^
+
+error: cannot find attribute `multipart_suggestion` in this scope
+  --> $DIR/diagnostic-derive.rs:627:3
+   |
+LL | #[multipart_suggestion()]
+   |   ^^^^^^^^^^^^^^^^^^^^
+
+error: cannot find attribute `multipart_suggestion` in this scope
+  --> $DIR/diagnostic-derive.rs:631:7
+   |
+LL |     #[multipart_suggestion(typeck::suggestion)]
+   |       ^^^^^^^^^^^^^^^^^^^^
+
 error[E0425]: cannot find value `nonsense` in module `rustc_errors::fluent`
   --> $DIR/diagnostic-derive.rs:66:8
    |
@@ -446,7 +578,7 @@ LL | #[diag(nonsense, code = "E0123")]
    |        ^^^^^^^^ not found in `rustc_errors::fluent`
 
 error[E0277]: the trait bound `Hello: IntoDiagnosticArg` is not satisfied
-  --> $DIR/diagnostic-derive.rs:331:10
+  --> $DIR/diagnostic-derive.rs:336:10
    |
 LL | #[derive(Diagnostic)]
    |          ^^^^^^^^^^ the trait `IntoDiagnosticArg` is not implemented for `Hello`
@@ -459,7 +591,7 @@ LL |         arg: impl IntoDiagnosticArg,
    |                   ^^^^^^^^^^^^^^^^^ required by this bound in `DiagnosticBuilder::<'a, G>::set_arg`
    = note: this error originates in the derive macro `Diagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
 
-error: aborting due to 55 previous errors
+error: aborting due to 72 previous errors
 
 Some errors have detailed explanations: E0277, E0425.
 For more information about an error, try `rustc --explain E0277`.
diff --git a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
index 9fbe7b1f4c8..606b3b5e5eb 100644
--- a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
+++ b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
@@ -52,7 +52,7 @@ struct C {
 
 #[derive(Subdiagnostic)]
 #[label]
-//~^ ERROR `#[label]` is not a valid attribute
+//~^ ERROR diagnostic slug must be first argument
 struct D {
     #[primary_span]
     span: Span,
@@ -81,6 +81,7 @@ struct F {
 #[derive(Subdiagnostic)]
 #[label(bug = "...")]
 //~^ ERROR `#[label(bug = ...)]` is not a valid attribute
+//~| ERROR diagnostic slug must be first argument
 struct G {
     #[primary_span]
     span: Span,
@@ -90,6 +91,7 @@ struct G {
 #[derive(Subdiagnostic)]
 #[label("...")]
 //~^ ERROR `#[label("...")]` is not a valid attribute
+//~| ERROR diagnostic slug must be first argument
 struct H {
     #[primary_span]
     span: Span,
@@ -99,6 +101,7 @@ struct H {
 #[derive(Subdiagnostic)]
 #[label(slug = 4)]
 //~^ ERROR `#[label(slug = ...)]` is not a valid attribute
+//~| ERROR diagnostic slug must be first argument
 struct J {
     #[primary_span]
     span: Span,
@@ -108,6 +111,7 @@ struct J {
 #[derive(Subdiagnostic)]
 #[label(slug("..."))]
 //~^ ERROR `#[label(slug(...))]` is not a valid attribute
+//~| ERROR diagnostic slug must be first argument
 struct K {
     #[primary_span]
     span: Span,
@@ -135,7 +139,7 @@ struct M {
 
 #[derive(Subdiagnostic)]
 #[label(parser::add_paren, code = "...")]
-//~^ ERROR `code` is not a valid nested attribute of a `label` attribute
+//~^ ERROR `#[label(code = ...)]` is not a valid attribute
 struct N {
     #[primary_span]
     span: Span,
@@ -144,7 +148,7 @@ struct N {
 
 #[derive(Subdiagnostic)]
 #[label(parser::add_paren, applicability = "machine-applicable")]
-//~^ ERROR `applicability` is not a valid nested attribute of a `label` attribute
+//~^ ERROR `#[label(applicability = ...)]` is not a valid attribute
 struct O {
     #[primary_span]
     span: Span,
@@ -216,6 +220,7 @@ enum T {
 enum U {
     #[label(code = "...")]
     //~^ ERROR diagnostic slug must be first argument of a `#[label(...)]` attribute
+    //~| ERROR `#[label(code = ...)]` is not a valid attribute
     A {
         #[primary_span]
         span: Span,
@@ -531,7 +536,7 @@ struct BA {
 #[derive(Subdiagnostic)]
 #[multipart_suggestion(parser::add_paren, code = "...", applicability = "machine-applicable")]
 //~^ ERROR multipart suggestion without any `#[suggestion_part(...)]` fields
-//~| ERROR `code` is not a valid nested attribute of a `multipart_suggestion` attribute
+//~| ERROR `#[multipart_suggestion(code = ...)]` is not a valid attribute
 struct BBa {
     var: String,
 }
@@ -612,10 +617,9 @@ struct BG {
 
 #[derive(Subdiagnostic)]
 #[multipart_suggestion(parser::add_paren, applicability = "machine-applicable")]
-//~^ NOTE previously specified here
 struct BH {
     #[applicability]
-    //~^ ERROR specified multiple times
+    //~^ ERROR `#[applicability]` has no effect
     appl: Applicability,
     #[suggestion_part(code = "(")]
     first: Span,
diff --git a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr
index 0a0247e8980..171b89e657d 100644
--- a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr
+++ b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr
@@ -8,7 +8,7 @@ LL | |     var: String,
 LL | | }
    | |_^
 
-error: `#[label]` is not a valid attribute
+error: diagnostic slug must be first argument of a `#[label(...)]` attribute
   --> $DIR/subdiagnostic-derive.rs:54:1
    |
 LL | #[label]
@@ -31,101 +31,123 @@ error: `#[label(bug = ...)]` is not a valid attribute
    |
 LL | #[label(bug = "...")]
    |         ^^^^^^^^^^^
+
+error: diagnostic slug must be first argument of a `#[label(...)]` attribute
+  --> $DIR/subdiagnostic-derive.rs:82:1
    |
-   = help: first argument of the attribute should be the diagnostic slug
+LL | #[label(bug = "...")]
+   | ^^^^^^^^^^^^^^^^^^^^^
 
 error: `#[label("...")]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:91:9
+  --> $DIR/subdiagnostic-derive.rs:92:9
    |
 LL | #[label("...")]
    |         ^^^^^
+
+error: diagnostic slug must be first argument of a `#[label(...)]` attribute
+  --> $DIR/subdiagnostic-derive.rs:92:1
    |
-   = help: first argument of the attribute should be the diagnostic slug
+LL | #[label("...")]
+   | ^^^^^^^^^^^^^^^
 
 error: `#[label(slug = ...)]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:100:9
+  --> $DIR/subdiagnostic-derive.rs:102:9
    |
 LL | #[label(slug = 4)]
    |         ^^^^^^^^
+
+error: diagnostic slug must be first argument of a `#[label(...)]` attribute
+  --> $DIR/subdiagnostic-derive.rs:102:1
    |
-   = help: first argument of the attribute should be the diagnostic slug
+LL | #[label(slug = 4)]
+   | ^^^^^^^^^^^^^^^^^^
 
 error: `#[label(slug(...))]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:109:9
+  --> $DIR/subdiagnostic-derive.rs:112:9
    |
 LL | #[label(slug("..."))]
    |         ^^^^^^^^^^^
+
+error: diagnostic slug must be first argument of a `#[label(...)]` attribute
+  --> $DIR/subdiagnostic-derive.rs:112:1
    |
-   = help: first argument of the attribute should be the diagnostic slug
+LL | #[label(slug("..."))]
+   | ^^^^^^^^^^^^^^^^^^^^^
 
 error: diagnostic slug must be first argument of a `#[label(...)]` attribute
-  --> $DIR/subdiagnostic-derive.rs:128:1
+  --> $DIR/subdiagnostic-derive.rs:132:1
    |
 LL | #[label()]
    | ^^^^^^^^^^
 
-error: `code` is not a valid nested attribute of a `label` attribute
-  --> $DIR/subdiagnostic-derive.rs:137:28
+error: `#[label(code = ...)]` is not a valid attribute
+  --> $DIR/subdiagnostic-derive.rs:141:28
    |
 LL | #[label(parser::add_paren, code = "...")]
    |                            ^^^^^^^^^^^^
 
-error: `applicability` is not a valid nested attribute of a `label` attribute
-  --> $DIR/subdiagnostic-derive.rs:146:28
+error: `#[label(applicability = ...)]` is not a valid attribute
+  --> $DIR/subdiagnostic-derive.rs:150:28
    |
 LL | #[label(parser::add_paren, applicability = "machine-applicable")]
    |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: unsupported type attribute for subdiagnostic enum
-  --> $DIR/subdiagnostic-derive.rs:155:1
+  --> $DIR/subdiagnostic-derive.rs:159:1
    |
 LL | #[foo]
    | ^^^^^^
 
 error: `#[bar]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:169:5
+  --> $DIR/subdiagnostic-derive.rs:173:5
    |
 LL |     #[bar]
    |     ^^^^^^
 
 error: `#[bar = ...]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:181:5
+  --> $DIR/subdiagnostic-derive.rs:185:5
    |
 LL |     #[bar = "..."]
    |     ^^^^^^^^^^^^^^
 
 error: `#[bar = ...]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:193:5
+  --> $DIR/subdiagnostic-derive.rs:197:5
    |
 LL |     #[bar = 4]
    |     ^^^^^^^^^^
 
 error: `#[bar(...)]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:205:5
+  --> $DIR/subdiagnostic-derive.rs:209:5
    |
 LL |     #[bar("...")]
    |     ^^^^^^^^^^^^^
 
+error: `#[label(code = ...)]` is not a valid attribute
+  --> $DIR/subdiagnostic-derive.rs:221:13
+   |
+LL |     #[label(code = "...")]
+   |             ^^^^^^^^^^^^
+
 error: diagnostic slug must be first argument of a `#[label(...)]` attribute
-  --> $DIR/subdiagnostic-derive.rs:217:5
+  --> $DIR/subdiagnostic-derive.rs:221:5
    |
 LL |     #[label(code = "...")]
    |     ^^^^^^^^^^^^^^^^^^^^^^
 
 error: subdiagnostic kind not specified
-  --> $DIR/subdiagnostic-derive.rs:234:5
+  --> $DIR/subdiagnostic-derive.rs:239:5
    |
 LL |     B {
    |     ^
 
 error: the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
-  --> $DIR/subdiagnostic-derive.rs:246:5
+  --> $DIR/subdiagnostic-derive.rs:251:5
    |
 LL |     #[primary_span]
    |     ^^^^^^^^^^^^^^^
 
 error: label without `#[primary_span]` field
-  --> $DIR/subdiagnostic-derive.rs:243:1
+  --> $DIR/subdiagnostic-derive.rs:248:1
    |
 LL | / #[label(parser::add_paren)]
 LL | |
@@ -137,13 +159,13 @@ LL | | }
    | |_^
 
 error: `#[applicability]` is only valid on suggestions
-  --> $DIR/subdiagnostic-derive.rs:256:5
+  --> $DIR/subdiagnostic-derive.rs:261:5
    |
 LL |     #[applicability]
    |     ^^^^^^^^^^^^^^^^
 
 error: `#[bar]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:266:5
+  --> $DIR/subdiagnostic-derive.rs:271:5
    |
 LL |     #[bar]
    |     ^^^^^^
@@ -151,13 +173,13 @@ LL |     #[bar]
    = help: only `primary_span`, `applicability` and `skip_arg` are valid field attributes
 
 error: `#[bar = ...]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:277:5
+  --> $DIR/subdiagnostic-derive.rs:282:5
    |
 LL |     #[bar = "..."]
    |     ^^^^^^^^^^^^^^
 
 error: `#[bar(...)]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:288:5
+  --> $DIR/subdiagnostic-derive.rs:293:5
    |
 LL |     #[bar("...")]
    |     ^^^^^^^^^^^^^
@@ -165,7 +187,7 @@ LL |     #[bar("...")]
    = help: only `primary_span`, `applicability` and `skip_arg` are valid field attributes
 
 error: unexpected unsupported untagged union
-  --> $DIR/subdiagnostic-derive.rs:304:1
+  --> $DIR/subdiagnostic-derive.rs:309:1
    |
 LL | / union AC {
 LL | |
@@ -175,7 +197,7 @@ LL | | }
    | |_^
 
 error: `#[label(parser::add_paren)]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:319:28
+  --> $DIR/subdiagnostic-derive.rs:324:28
    |
 LL | #[label(parser::add_paren, parser::add_paren)]
    |                            ^^^^^^^^^^^^^^^^^
@@ -183,67 +205,67 @@ LL | #[label(parser::add_paren, parser::add_paren)]
    = help: a diagnostic slug must be the first argument to the attribute
 
 error: specified multiple times
-  --> $DIR/subdiagnostic-derive.rs:332:5
+  --> $DIR/subdiagnostic-derive.rs:337:5
    |
 LL |     #[primary_span]
    |     ^^^^^^^^^^^^^^^
    |
 note: previously specified here
-  --> $DIR/subdiagnostic-derive.rs:329:5
+  --> $DIR/subdiagnostic-derive.rs:334:5
    |
 LL |     #[primary_span]
    |     ^^^^^^^^^^^^^^^
 
 error: subdiagnostic kind not specified
-  --> $DIR/subdiagnostic-derive.rs:338:8
+  --> $DIR/subdiagnostic-derive.rs:343:8
    |
 LL | struct AG {
    |        ^^
 
 error: specified multiple times
-  --> $DIR/subdiagnostic-derive.rs:375:47
+  --> $DIR/subdiagnostic-derive.rs:380:47
    |
 LL | #[suggestion(parser::add_paren, code = "...", code = "...")]
    |                                               ^^^^^^^^^^^^
    |
 note: previously specified here
-  --> $DIR/subdiagnostic-derive.rs:375:33
+  --> $DIR/subdiagnostic-derive.rs:380:33
    |
 LL | #[suggestion(parser::add_paren, code = "...", code = "...")]
    |                                 ^^^^^^^^^^^^
 
 error: specified multiple times
-  --> $DIR/subdiagnostic-derive.rs:393:5
+  --> $DIR/subdiagnostic-derive.rs:398:5
    |
 LL |     #[applicability]
    |     ^^^^^^^^^^^^^^^^
    |
 note: previously specified here
-  --> $DIR/subdiagnostic-derive.rs:390:5
+  --> $DIR/subdiagnostic-derive.rs:395:5
    |
 LL |     #[applicability]
    |     ^^^^^^^^^^^^^^^^
 
 error: the `#[applicability]` attribute can only be applied to fields of type `Applicability`
-  --> $DIR/subdiagnostic-derive.rs:403:5
+  --> $DIR/subdiagnostic-derive.rs:408:5
    |
 LL |     #[applicability]
    |     ^^^^^^^^^^^^^^^^
 
 error: suggestion without `code = "..."`
-  --> $DIR/subdiagnostic-derive.rs:416:1
+  --> $DIR/subdiagnostic-derive.rs:421:1
    |
 LL | #[suggestion(parser::add_paren)]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: invalid applicability
-  --> $DIR/subdiagnostic-derive.rs:426:46
+  --> $DIR/subdiagnostic-derive.rs:431:46
    |
 LL | #[suggestion(parser::add_paren, code ="...", applicability = "foo")]
    |                                              ^^^^^^^^^^^^^^^^^^^^^
 
 error: suggestion without `#[primary_span]` field
-  --> $DIR/subdiagnostic-derive.rs:444:1
+  --> $DIR/subdiagnostic-derive.rs:449:1
    |
 LL | / #[suggestion(parser::add_paren, code = "...")]
 LL | |
@@ -253,25 +275,25 @@ LL | | }
    | |_^
 
 error: unsupported type attribute for subdiagnostic enum
-  --> $DIR/subdiagnostic-derive.rs:458:1
+  --> $DIR/subdiagnostic-derive.rs:463:1
    |
 LL | #[label]
    | ^^^^^^^^
 
 error: `var` doesn't refer to a field on this type
-  --> $DIR/subdiagnostic-derive.rs:478:39
+  --> $DIR/subdiagnostic-derive.rs:483:39
    |
 LL | #[suggestion(parser::add_paren, code ="{var}", applicability = "machine-applicable")]
    |                                       ^^^^^^^
 
 error: `var` doesn't refer to a field on this type
-  --> $DIR/subdiagnostic-derive.rs:497:43
+  --> $DIR/subdiagnostic-derive.rs:502:43
    |
 LL |     #[suggestion(parser::add_paren, code ="{var}", applicability = "machine-applicable")]
    |                                           ^^^^^^^
 
 error: `#[suggestion_part]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:520:5
+  --> $DIR/subdiagnostic-derive.rs:525:5
    |
 LL |     #[suggestion_part]
    |     ^^^^^^^^^^^^^^^^^^
@@ -279,7 +301,7 @@ LL |     #[suggestion_part]
    = help: `#[suggestion_part(...)]` is only valid in multipart suggestions, use `#[primary_span]` instead
 
 error: `#[suggestion_part(...)]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:523:5
+  --> $DIR/subdiagnostic-derive.rs:528:5
    |
 LL |     #[suggestion_part(code = "...")]
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -287,7 +309,7 @@ LL |     #[suggestion_part(code = "...")]
    = help: `#[suggestion_part(...)]` is only valid in multipart suggestions
 
 error: suggestion without `#[primary_span]` field
-  --> $DIR/subdiagnostic-derive.rs:517:1
+  --> $DIR/subdiagnostic-derive.rs:522:1
    |
 LL | / #[suggestion(parser::add_paren, code = "...")]
 LL | |
@@ -298,14 +320,16 @@ LL | |     var: String,
 LL | | }
    | |_^
 
-error: `code` is not a valid nested attribute of a `multipart_suggestion` attribute
-  --> $DIR/subdiagnostic-derive.rs:532:43
+error: `#[multipart_suggestion(code = ...)]` is not a valid attribute
+  --> $DIR/subdiagnostic-derive.rs:537:43
    |
 LL | #[multipart_suggestion(parser::add_paren, code = "...", applicability = "machine-applicable")]
    |                                           ^^^^^^^^^^^^
+   |
+   = help: only `applicability` is a valid nested attributes
 
 error: multipart suggestion without any `#[suggestion_part(...)]` fields
-  --> $DIR/subdiagnostic-derive.rs:532:1
+  --> $DIR/subdiagnostic-derive.rs:537:1
    |
 LL | / #[multipart_suggestion(parser::add_paren, code = "...", applicability = "machine-applicable")]
 LL | |
@@ -316,19 +340,19 @@ LL | | }
    | |_^
 
 error: `#[suggestion_part(...)]` attribute without `code = "..."`
-  --> $DIR/subdiagnostic-derive.rs:542:5
+  --> $DIR/subdiagnostic-derive.rs:547:5
    |
 LL |     #[suggestion_part]
    |     ^^^^^^^^^^^^^^^^^^
 
 error: `#[suggestion_part(...)]` attribute without `code = "..."`
-  --> $DIR/subdiagnostic-derive.rs:550:5
+  --> $DIR/subdiagnostic-derive.rs:555:5
    |
 LL |     #[suggestion_part()]
    |     ^^^^^^^^^^^^^^^^^^^^
 
 error: `#[primary_span]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:559:5
+  --> $DIR/subdiagnostic-derive.rs:564:5
    |
 LL |     #[primary_span]
    |     ^^^^^^^^^^^^^^^
@@ -336,7 +360,7 @@ LL |     #[primary_span]
    = help: multipart suggestions use one or more `#[suggestion_part]`s rather than one `#[primary_span]`
 
 error: multipart suggestion without any `#[suggestion_part(...)]` fields
-  --> $DIR/subdiagnostic-derive.rs:556:1
+  --> $DIR/subdiagnostic-derive.rs:561:1
    |
 LL | / #[multipart_suggestion(parser::add_paren)]
 LL | |
@@ -348,19 +372,19 @@ LL | | }
    | |_^
 
 error: `#[suggestion_part(...)]` attribute without `code = "..."`
-  --> $DIR/subdiagnostic-derive.rs:567:5
+  --> $DIR/subdiagnostic-derive.rs:572:5
    |
 LL |     #[suggestion_part]
    |     ^^^^^^^^^^^^^^^^^^
 
 error: `#[suggestion_part(...)]` attribute without `code = "..."`
-  --> $DIR/subdiagnostic-derive.rs:570:5
+  --> $DIR/subdiagnostic-derive.rs:575:5
    |
 LL |     #[suggestion_part()]
    |     ^^^^^^^^^^^^^^^^^^^^
 
 error: `#[suggestion_part(foo = ...)]` is not a valid attribute
-  --> $DIR/subdiagnostic-derive.rs:573:23
+  --> $DIR/subdiagnostic-derive.rs:578:23
    |
 LL |     #[suggestion_part(foo = "bar")]
    |                       ^^^^^^^^^^^
@@ -368,40 +392,34 @@ LL |     #[suggestion_part(foo = "bar")]
    = help: `code` is the only valid nested attribute
 
 error: the `#[suggestion_part(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
-  --> $DIR/subdiagnostic-derive.rs:576:5
+  --> $DIR/subdiagnostic-derive.rs:581:5
    |
 LL |     #[suggestion_part(code = "...")]
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: the `#[suggestion_part(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
-  --> $DIR/subdiagnostic-derive.rs:579:5
+  --> $DIR/subdiagnostic-derive.rs:584:5
    |
 LL |     #[suggestion_part()]
    |     ^^^^^^^^^^^^^^^^^^^^
 
 error: specified multiple times
-  --> $DIR/subdiagnostic-derive.rs:587:37
+  --> $DIR/subdiagnostic-derive.rs:592:37
    |
 LL |     #[suggestion_part(code = "...", code = ",,,")]
    |                                     ^^^^^^^^^^^^
    |
 note: previously specified here
-  --> $DIR/subdiagnostic-derive.rs:587:23
+  --> $DIR/subdiagnostic-derive.rs:592:23
    |
 LL |     #[suggestion_part(code = "...", code = ",,,")]
    |                       ^^^^^^^^^^^^
 
-error: specified multiple times
-  --> $DIR/subdiagnostic-derive.rs:617:5
+error: `#[applicability]` has no effect if all `#[suggestion]`/`#[multipart_suggestion]` attributes have a static `applicability = "..."`
+  --> $DIR/subdiagnostic-derive.rs:621:5
    |
 LL |     #[applicability]
    |     ^^^^^^^^^^^^^^^^
-   |
-note: previously specified here
-  --> $DIR/subdiagnostic-derive.rs:614:43
-   |
-LL | #[multipart_suggestion(parser::add_paren, applicability = "machine-applicable")]
-   |                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 error: cannot find attribute `foo` in this scope
   --> $DIR/subdiagnostic-derive.rs:63:3
@@ -410,59 +428,59 @@ LL | #[foo]
    |   ^^^
 
 error: cannot find attribute `foo` in this scope
-  --> $DIR/subdiagnostic-derive.rs:155:3
+  --> $DIR/subdiagnostic-derive.rs:159:3
    |
 LL | #[foo]
    |   ^^^
 
 error: cannot find attribute `bar` in this scope
-  --> $DIR/subdiagnostic-derive.rs:169:7
+  --> $DIR/subdiagnostic-derive.rs:173:7
    |
 LL |     #[bar]
    |       ^^^
 
 error: cannot find attribute `bar` in this scope
-  --> $DIR/subdiagnostic-derive.rs:181:7
+  --> $DIR/subdiagnostic-derive.rs:185:7
    |
 LL |     #[bar = "..."]
    |       ^^^
 
 error: cannot find attribute `bar` in this scope
-  --> $DIR/subdiagnostic-derive.rs:193:7
+  --> $DIR/subdiagnostic-derive.rs:197:7
    |
 LL |     #[bar = 4]
    |       ^^^
 
 error: cannot find attribute `bar` in this scope
-  --> $DIR/subdiagnostic-derive.rs:205:7
+  --> $DIR/subdiagnostic-derive.rs:209:7
    |
 LL |     #[bar("...")]
    |       ^^^
 
 error: cannot find attribute `bar` in this scope
-  --> $DIR/subdiagnostic-derive.rs:266:7
+  --> $DIR/subdiagnostic-derive.rs:271:7
    |
 LL |     #[bar]
    |       ^^^
 
 error: cannot find attribute `bar` in this scope
-  --> $DIR/subdiagnostic-derive.rs:277:7
+  --> $DIR/subdiagnostic-derive.rs:282:7
    |
 LL |     #[bar = "..."]
    |       ^^^
 
 error: cannot find attribute `bar` in this scope
-  --> $DIR/subdiagnostic-derive.rs:288:7
+  --> $DIR/subdiagnostic-derive.rs:293:7
    |
 LL |     #[bar("...")]
    |       ^^^
 
 error[E0425]: cannot find value `slug` in module `rustc_errors::fluent`
-  --> $DIR/subdiagnostic-derive.rs:118:9
+  --> $DIR/subdiagnostic-derive.rs:122:9
    |
 LL | #[label(slug)]
    |         ^^^^ not found in `rustc_errors::fluent`
 
-error: aborting due to 63 previous errors
+error: aborting due to 68 previous errors
 
 For more information about this error, try `rustc --explain E0425`.