about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDavid Wood <david.wood@huawei.com>2022-04-26 11:59:45 +0100
committerDavid Wood <david.wood@huawei.com>2022-04-29 02:05:20 +0100
commit49ec909ca7d649d73115f7e0e894b0ffb0740b66 (patch)
treefaf3bd8990a20a1a662701627d3f98d4f047e422
parentaa2abc9d1236b3a74f49f932c02ac9b30f7f36c9 (diff)
downloadrust-49ec909ca7d649d73115f7e0e894b0ffb0740b66.tar.gz
rust-49ec909ca7d649d73115f7e0e894b0ffb0740b66.zip
macros: subdiagnostic derive
Add a new derive, `#[derive(SessionSubdiagnostic)]`, which enables
deriving structs for labels, notes, helps and suggestions.

Signed-off-by: David Wood <david.wood@huawei.com>
-rw-r--r--compiler/rustc_error_messages/locales/en-US/typeck.ftl8
-rw-r--r--compiler/rustc_errors/src/diagnostic.rs14
-rw-r--r--compiler/rustc_errors/src/diagnostic_builder.rs5
-rw-r--r--compiler/rustc_errors/src/lib.rs4
-rw-r--r--compiler/rustc_macros/src/lib.rs18
-rw-r--r--compiler/rustc_macros/src/session_diagnostic.rs886
-rw-r--r--compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs23
-rw-r--r--compiler/rustc_typeck/src/errors.rs40
-rw-r--r--src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs501
-rw-r--r--src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr387
10 files changed, 1735 insertions, 151 deletions
diff --git a/compiler/rustc_error_messages/locales/en-US/typeck.ftl b/compiler/rustc_error_messages/locales/en-US/typeck.ftl
index 721201d9312..6a3235fc772 100644
--- a/compiler/rustc_error_messages/locales/en-US/typeck.ftl
+++ b/compiler/rustc_error_messages/locales/en-US/typeck.ftl
@@ -82,3 +82,11 @@ typeck-value-of-associated-struct-already-specified =
 
 typeck-address-of-temporary-taken = cannot take address of a temporary
     .label = temporary value
+
+typeck-add-return-type-add = try adding a return type
+
+typeck-add-return-type-missing-here = a return type might be missing here
+
+typeck-expected-default-return-type = expected `()` because of default return type
+
+typeck-expected-return-type = expected `{$expected}` because of return type
diff --git a/compiler/rustc_errors/src/diagnostic.rs b/compiler/rustc_errors/src/diagnostic.rs
index 39cb71848eb..83e6a751394 100644
--- a/compiler/rustc_errors/src/diagnostic.rs
+++ b/compiler/rustc_errors/src/diagnostic.rs
@@ -78,6 +78,13 @@ impl<'source> Into<FluentValue<'source>> for DiagnosticArgValue<'source> {
     }
 }
 
+/// Trait implemented by error types. This should not be implemented manually. Instead, use
+/// `#[derive(SessionSubdiagnostic)]` -- see [rustc_macros::SessionSubdiagnostic].
+pub trait AddSubdiagnostic {
+    /// Add a subdiagnostic to an existing diagnostic.
+    fn add_to_diagnostic(self, diag: &mut Diagnostic);
+}
+
 #[must_use]
 #[derive(Clone, Debug, Encodable, Decodable)]
 pub struct Diagnostic {
@@ -768,6 +775,13 @@ impl Diagnostic {
         self
     }
 
+    /// Add a subdiagnostic from a type that implements `SessionSubdiagnostic` - see
+    /// [rustc_macros::SessionSubdiagnostic].
+    pub fn subdiagnostic(&mut self, subdiagnostic: impl AddSubdiagnostic) -> &mut Self {
+        subdiagnostic.add_to_diagnostic(self);
+        self
+    }
+
     pub fn set_span<S: Into<MultiSpan>>(&mut self, sp: S) -> &mut Self {
         self.span = sp.into();
         if let Some(span) = self.span.primary_span() {
diff --git a/compiler/rustc_errors/src/diagnostic_builder.rs b/compiler/rustc_errors/src/diagnostic_builder.rs
index efc7693459f..96b730c2baa 100644
--- a/compiler/rustc_errors/src/diagnostic_builder.rs
+++ b/compiler/rustc_errors/src/diagnostic_builder.rs
@@ -530,6 +530,11 @@ impl<'a, G: EmissionGuarantee> DiagnosticBuilder<'a, G> {
         name: impl Into<Cow<'static, str>>,
         arg: DiagnosticArgValue<'static>,
     ) -> &mut Self);
+
+    forward!(pub fn subdiagnostic(
+        &mut self,
+        subdiagnostic: impl crate::AddSubdiagnostic
+    ) -> &mut Self);
 }
 
 impl<G: EmissionGuarantee> Debug for DiagnosticBuilder<'_, G> {
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index a64133bb7f4..df41fc00714 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -370,8 +370,8 @@ impl fmt::Display for ExplicitBug {
 impl error::Error for ExplicitBug {}
 
 pub use diagnostic::{
-    Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId, DiagnosticStyledString,
-    IntoDiagnosticArg, SubDiagnostic,
+    AddSubdiagnostic, Diagnostic, DiagnosticArg, DiagnosticArgValue, DiagnosticId,
+    DiagnosticStyledString, IntoDiagnosticArg, SubDiagnostic,
 };
 pub use diagnostic_builder::{DiagnosticBuilder, EmissionGuarantee};
 use std::backtrace::Backtrace;
diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs
index b53ef816135..8026288202e 100644
--- a/compiler/rustc_macros/src/lib.rs
+++ b/compiler/rustc_macros/src/lib.rs
@@ -1,5 +1,6 @@
-#![feature(proc_macro_diagnostic)]
 #![feature(allow_internal_unstable)]
+#![feature(let_else)]
+#![feature(proc_macro_diagnostic)]
 #![allow(rustc::default_hash_types)]
 #![recursion_limit = "128"]
 
@@ -77,3 +78,18 @@ decl_derive!(
         suggestion_hidden,
         suggestion_verbose)] => session_diagnostic::session_diagnostic_derive
 );
+decl_derive!(
+    [SessionSubdiagnostic, attributes(
+        // struct/variant attributes
+        label,
+        help,
+        note,
+        suggestion,
+        suggestion_short,
+        suggestion_hidden,
+        suggestion_verbose,
+        // field attributes
+        skip_arg,
+        primary_span,
+        applicability)] => session_diagnostic::session_subdiagnostic_derive
+);
diff --git a/compiler/rustc_macros/src/session_diagnostic.rs b/compiler/rustc_macros/src/session_diagnostic.rs
index 9466d0f34bc..27c94f3e306 100644
--- a/compiler/rustc_macros/src/session_diagnostic.rs
+++ b/compiler/rustc_macros/src/session_diagnostic.rs
@@ -1,9 +1,12 @@
 #![deny(unused_must_use)]
-use proc_macro::Diagnostic;
+use proc_macro::{Diagnostic, Level, MultiSpan};
+use proc_macro2::TokenStream;
 use quote::{format_ident, quote};
-use syn::spanned::Spanned;
-
 use std::collections::{BTreeSet, HashMap};
+use std::fmt;
+use std::str::FromStr;
+use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type, Visibility};
+use synstructure::{BindingInfo, Structure, VariantInfo};
 
 /// Implements `#[derive(SessionDiagnostic)]`, which allows for errors to be specified as a struct,
 /// independent from the actual diagnostics emitting code.
@@ -51,7 +54,7 @@ use std::collections::{BTreeSet, HashMap};
 ///
 /// See rustc dev guide for more examples on using the `#[derive(SessionDiagnostic)]`:
 /// <https://rustc-dev-guide.rust-lang.org/diagnostics/sessiondiagnostic.html>
-pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
+pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
     // Names for the diagnostic we build and the session we build it from.
     let diag = format_ident!("diag");
     let sess = format_ident!("sess");
@@ -59,12 +62,62 @@ pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2:
     SessionDiagnosticDerive::new(diag, sess, s).into_tokens()
 }
 
+/// Implements `#[derive(SessionSubdiagnostic)]`, which allows for labels, notes, helps and
+/// suggestions to be specified as a structs or enums, independent from the actual diagnostics
+/// emitting code or diagnostic derives.
+///
+/// ```ignore (pseudo-rust)
+/// #[derive(SessionSubdiagnostic)]
+/// pub enum ExpectedIdentifierLabel<'tcx> {
+///     #[label(slug = "parser-expected-identifier")]
+///     WithoutFound {
+///         #[primary_span]
+///         span: Span,
+///     }
+///     #[label(slug = "parser-expected-identifier-found")]
+///     WithFound {
+///         #[primary_span]
+///         span: Span,
+///         found: String,
+///     }
+/// }
+///
+/// #[derive(SessionSubdiagnostic)]
+/// #[suggestion_verbose(slug = "parser-raw-identifier")]
+/// pub struct RawIdentifierSuggestion<'tcx> {
+///     #[primary_span]
+///     span: Span,
+///     #[applicability]
+///     applicability: Applicability,
+///     ident: Ident,
+/// }
+/// ```
+///
+/// ```fluent
+/// parser-expected-identifier = expected identifier
+///
+/// parser-expected-identifier-found = expected identifier, found {$found}
+///
+/// parser-raw-identifier = escape `{$ident}` to use it as an identifier
+/// ```
+///
+/// Then, later, to add the subdiagnostic:
+///
+/// ```ignore (pseudo-rust)
+/// diag.subdiagnostic(ExpectedIdentifierLabel::WithoutFound { span });
+///
+/// diag.subdiagnostic(RawIdentifierSuggestion { span, applicability, ident });
+/// ```
+pub fn session_subdiagnostic_derive(s: Structure<'_>) -> TokenStream {
+    SessionSubdiagnosticDerive::new(s).into_tokens()
+}
+
 /// 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
 /// `a::b::c::Foo`. This reasonably allows qualified names to be used in the macro.
-fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool {
-    if let syn::Type::Path(ty) = ty {
+fn type_matches_path(ty: &Type, name: &[&str]) -> bool {
+    if let Type::Path(ty) = ty {
         ty.path
             .segments
             .iter()
@@ -79,8 +132,8 @@ fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool {
 
 /// The central struct for constructing the `as_error` method from an annotated struct.
 struct SessionDiagnosticDerive<'a> {
-    structure: synstructure::Structure<'a>,
-    builder: SessionDiagnosticDeriveBuilder<'a>,
+    structure: Structure<'a>,
+    builder: SessionDiagnosticDeriveBuilder,
 }
 
 impl std::convert::From<syn::Error> for SessionDiagnosticDeriveError {
@@ -96,7 +149,7 @@ enum SessionDiagnosticDeriveError {
 }
 
 impl SessionDiagnosticDeriveError {
-    fn to_compile_error(self) -> proc_macro2::TokenStream {
+    fn to_compile_error(self) -> TokenStream {
         match self {
             SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(),
             SessionDiagnosticDeriveError::ErrorHandled => {
@@ -110,8 +163,8 @@ impl SessionDiagnosticDeriveError {
     }
 }
 
-fn span_err(span: impl proc_macro::MultiSpan, msg: &str) -> proc_macro::Diagnostic {
-    Diagnostic::spanned(span, proc_macro::Level::Error, msg)
+fn span_err(span: impl MultiSpan, msg: &str) -> Diagnostic {
+    Diagnostic::spanned(span, Level::Error, msg)
 }
 
 /// For methods that return a `Result<_, SessionDiagnosticDeriveError>`:
@@ -128,9 +181,9 @@ macro_rules! throw_span_err {
 /// When possible, prefer using `throw_span_err!` over using this function directly. This only
 /// exists as a function to constrain `f` to an `impl FnOnce`.
 fn _throw_span_err(
-    span: impl proc_macro::MultiSpan,
+    span: impl MultiSpan,
     msg: &str,
-    f: impl FnOnce(proc_macro::Diagnostic) -> proc_macro::Diagnostic,
+    f: impl FnOnce(Diagnostic) -> Diagnostic,
 ) -> SessionDiagnosticDeriveError {
     let diag = span_err(span, msg);
     f(diag).emit();
@@ -138,7 +191,7 @@ fn _throw_span_err(
 }
 
 impl<'a> SessionDiagnosticDerive<'a> {
-    fn new(diag: syn::Ident, sess: syn::Ident, structure: synstructure::Structure<'a>) -> Self {
+    fn new(diag: syn::Ident, sess: syn::Ident, structure: Structure<'a>) -> Self {
         // Build the mapping of field names to fields. This allows attributes to peek values from
         // other fields.
         let mut fields_map = HashMap::new();
@@ -149,7 +202,7 @@ impl<'a> SessionDiagnosticDerive<'a> {
         if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
             for field in fields.iter() {
                 if let Some(ident) = &field.ident {
-                    fields_map.insert(ident.to_string(), field);
+                    fields_map.insert(ident.to_string(), quote! { &self.#ident });
                 }
             }
         }
@@ -167,7 +220,7 @@ impl<'a> SessionDiagnosticDerive<'a> {
         }
     }
 
-    fn into_tokens(self) -> proc_macro2::TokenStream {
+    fn into_tokens(self) -> TokenStream {
         let SessionDiagnosticDerive { mut structure, mut builder } = self;
 
         let ast = structure.ast();
@@ -321,9 +374,9 @@ impl<'a> SessionDiagnosticDerive<'a> {
 /// Field information passed to the builder. Deliberately omits attrs to discourage the
 /// `generate_*` methods from walking the attributes themselves.
 struct FieldInfo<'a> {
-    vis: &'a syn::Visibility,
-    binding: &'a synstructure::BindingInfo<'a>,
-    ty: &'a syn::Type,
+    vis: &'a Visibility,
+    binding: &'a BindingInfo<'a>,
+    ty: &'a Type,
     span: &'a proc_macro2::Span,
 }
 
@@ -350,7 +403,7 @@ impl SessionDiagnosticKind {
 /// methods for the final generated method. This is a separate struct to `SessionDiagnosticDerive`
 /// only to be able to destructure and split `self.builder` and the `self.structure` up to avoid a
 /// double mut borrow later on.
-struct SessionDiagnosticDeriveBuilder<'a> {
+struct SessionDiagnosticDeriveBuilder {
     /// Name of the session parameter that's passed in to the `as_error` method.
     sess: syn::Ident,
     /// The identifier to use for the generated `DiagnosticBuilder` instance.
@@ -358,7 +411,7 @@ struct SessionDiagnosticDeriveBuilder<'a> {
 
     /// Store a map of field name to its corresponding field. This is built on construction of the
     /// derive builder.
-    fields: HashMap<String, &'a syn::Field>,
+    fields: HashMap<String, TokenStream>,
 
     /// Kind of diagnostic requested via the struct attribute.
     kind: Option<(SessionDiagnosticKind, proc_macro::Span)>,
@@ -370,23 +423,21 @@ struct SessionDiagnosticDeriveBuilder<'a> {
     code: Option<proc_macro::Span>,
 }
 
-impl<'a> SessionDiagnosticDeriveBuilder<'a> {
+impl SessionDiagnosticDeriveBuilder {
     /// Establishes state in the `SessionDiagnosticDeriveBuilder` resulting from the struct
     /// attributes like `#[error(..)#`, such as the diagnostic kind and slug. Generates
     /// diagnostic builder calls for setting error code and creating note/help messages.
     fn generate_structure_code(
         &mut self,
-        attr: &syn::Attribute,
-    ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
+        attr: &Attribute,
+    ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
         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()?;
 
-        if matches!(name, "help" | "note")
-            && matches!(meta, syn::Meta::Path(_) | syn::Meta::NameValue(_))
-        {
+        if matches!(name, "help" | "note") && matches!(meta, Meta::Path(_) | Meta::NameValue(_)) {
             let diag = &self.diag;
             let slug = match &self.slug {
                 Some((slug, _)) => slug.as_str(),
@@ -396,16 +447,16 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
                         "`#[{}{}]` must come after `#[error(..)]` or `#[warn(..)]`",
                         name,
                         match meta {
-                            syn::Meta::Path(_) => "",
-                            syn::Meta::NameValue(_) => " = ...",
+                            Meta::Path(_) => "",
+                            Meta::NameValue(_) => " = ...",
                             _ => unreachable!(),
                         }
                     )
                 ),
             };
             let id = match meta {
-                syn::Meta::Path(..) => quote! { #name },
-                syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
+                Meta::Path(..) => quote! { #name },
+                Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
                     quote! { #s }
                 }
                 _ => unreachable!(),
@@ -418,12 +469,12 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
         }
 
         let nested = match meta {
-            syn::Meta::List(syn::MetaList { nested, .. }) => nested,
-            syn::Meta::Path(..) => throw_span_err!(
+            Meta::List(MetaList { nested, .. }) => nested,
+            Meta::Path(..) => throw_span_err!(
                 span,
                 &format!("`#[{}]` is not a valid `SessionDiagnostic` struct attribute", name)
             ),
-            syn::Meta::NameValue(..) => throw_span_err!(
+            Meta::NameValue(..) => throw_span_err!(
                 span,
                 &format!("`#[{} = ...]` is not a valid `SessionDiagnostic` struct attribute", name)
             ),
@@ -458,7 +509,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
             match &meta {
                 // Struct attributes are only allowed to be applied once, and the diagnostic
                 // changes will be set in the initialisation code.
-                syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
+                Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
                     match nested_name.as_str() {
                         "slug" => {
                             self.set_slug_once(s.value(), s.span().unwrap());
@@ -478,7 +529,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
                         }
                     }
                 }
-                syn::Meta::NameValue(..) => {
+                Meta::NameValue(..) => {
                     span_err(
                         span,
                         &format!(
@@ -489,7 +540,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
                     .help("value must be a string")
                     .emit();
                 }
-                syn::Meta::Path(..) => {
+                Meta::Path(..) => {
                     span_err(
                         span,
                         &format!(
@@ -499,7 +550,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
                     )
                     .emit();
                 }
-                syn::Meta::List(..) => {
+                Meta::List(..) => {
                     span_err(
                         span,
                         &format!(
@@ -541,7 +592,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
         }
     }
 
-    fn set_code_once(&mut self, code: String, span: proc_macro::Span) -> proc_macro2::TokenStream {
+    fn set_code_once(&mut self, code: String, span: proc_macro::Span) -> TokenStream {
         match self.code {
             None => {
                 self.code = Some(span);
@@ -574,7 +625,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
         &mut self,
         attr: &syn::Attribute,
         info: FieldInfo<'_>,
-    ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
+    ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
         let field_binding = &info.binding.binding;
         let option_ty = option_inner_ty(&info.ty);
         let generated_code = self.generate_non_option_field_code(
@@ -600,9 +651,9 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
 
     fn generate_non_option_field_code(
         &mut self,
-        attr: &syn::Attribute,
+        attr: &Attribute,
         info: FieldInfo<'_>,
-    ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
+    ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
         let diag = &self.diag;
         let span = attr.span().unwrap();
         let field_binding = &info.binding.binding;
@@ -612,20 +663,20 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
 
         let meta = attr.parse_meta()?;
         match meta {
-            syn::Meta::Path(_) => match name {
+            Meta::Path(_) => 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" => {
-                    self.report_error_if_not_applied_to_span(attr, info)?;
+                    report_error_if_not_applied_to_span(attr, &info)?;
                     Ok(quote! {
                         #diag.set_span(*#field_binding);
                     })
                 }
                 "label" | "note" | "help" => {
-                    self.report_error_if_not_applied_to_span(attr, info)?;
+                    report_error_if_not_applied_to_span(attr, &info)?;
                     Ok(self.add_subdiagnostic(field_binding, name, name))
                 }
                 other => throw_span_err!(
@@ -633,9 +684,9 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
                     &format!("`#[{}]` is not a valid `SessionDiagnostic` field attribute", other)
                 ),
             },
-            syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => match name {
+            Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => match name {
                 "label" | "note" | "help" => {
-                    self.report_error_if_not_applied_to_span(attr, info)?;
+                    report_error_if_not_applied_to_span(attr, &info)?;
                     Ok(self.add_subdiagnostic(field_binding, name, &s.value()))
                 }
                 other => throw_span_err!(
@@ -646,12 +697,12 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
                     )
                 ),
             },
-            syn::Meta::NameValue(_) => throw_span_err!(
+            Meta::NameValue(_) => throw_span_err!(
                 span,
                 &format!("`#[{} = ...]` is not a valid `SessionDiagnostic` field attribute", name),
                 |diag| diag.help("value must be a string")
             ),
-            syn::Meta::List(syn::MetaList { path, nested, .. }) => {
+            Meta::List(MetaList { path, nested, .. }) => {
                 let name = path.segments.last().unwrap().ident.to_string();
                 let name = name.as_ref();
 
@@ -689,25 +740,25 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
                     let nested_name = nested_name.as_str();
 
                     match meta {
-                        syn::Meta::NameValue(syn::MetaNameValue {
-                            lit: syn::Lit::Str(s), ..
-                        }) => match nested_name {
-                            "message" => {
-                                msg = Some(s.value());
-                            }
-                            "code" => {
-                                let formatted_str = self.build_format(&s.value(), s.span());
-                                code = Some(formatted_str);
+                        Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
+                            match nested_name {
+                                "message" => {
+                                    msg = Some(s.value());
+                                }
+                                "code" => {
+                                    let formatted_str = self.build_format(&s.value(), s.span());
+                                    code = Some(formatted_str);
+                                }
+                                other => throw_span_err!(
+                                    span,
+                                    &format!(
+                                        "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` field attribute",
+                                        name, other
+                                    )
+                                ),
                             }
-                            other => throw_span_err!(
-                                span,
-                                &format!(
-                                    "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` field attribute",
-                                    name, other
-                                )
-                            ),
-                        },
-                        syn::Meta::NameValue(..) => throw_span_err!(
+                        }
+                        Meta::NameValue(..) => throw_span_err!(
                             span,
                             &format!(
                                 "`#[{}({} = ...)]` is not a valid `SessionDiagnostic` struct attribute",
@@ -715,14 +766,14 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
                             ),
                             |diag| diag.help("value must be a string")
                         ),
-                        syn::Meta::Path(..) => throw_span_err!(
+                        Meta::Path(..) => throw_span_err!(
                             span,
                             &format!(
                                 "`#[{}({})]` is not a valid `SessionDiagnostic` struct attribute",
                                 name, nested_name
                             )
                         ),
-                        syn::Meta::List(..) => throw_span_err!(
+                        Meta::List(..) => throw_span_err!(
                             span,
                             &format!(
                                 "`#[{}({}(...))]` is not a valid `SessionDiagnostic` struct attribute",
@@ -748,34 +799,6 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
         }
     }
 
-    /// Reports an error if the field's type is not `Span`.
-    fn report_error_if_not_applied_to_span(
-        &self,
-        attr: &syn::Attribute,
-        info: FieldInfo<'_>,
-    ) -> Result<(), SessionDiagnosticDeriveError> {
-        if !type_matches_path(&info.ty, &["rustc_span", "Span"]) {
-            let name = attr.path.segments.last().unwrap().ident.to_string();
-            let name = name.as_str();
-            let meta = attr.parse_meta()?;
-
-            throw_span_err!(
-                attr.span().unwrap(),
-                &format!(
-                    "the `#[{}{}]` attribute can only be applied to fields of type `Span`",
-                    name,
-                    match meta {
-                        syn::Meta::Path(_) => "",
-                        syn::Meta::NameValue(_) => " = ...",
-                        syn::Meta::List(_) => "(...)",
-                    }
-                )
-            );
-        }
-
-        Ok(())
-    }
-
     /// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug and
     /// `fluent_attr_identifier`.
     fn add_subdiagnostic(
@@ -783,7 +806,7 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
         field_binding: &proc_macro2::Ident,
         kind: &str,
         fluent_attr_identifier: &str,
-    ) -> proc_macro2::TokenStream {
+    ) -> TokenStream {
         let diag = &self.diag;
 
         let slug =
@@ -800,16 +823,15 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
     fn span_and_applicability_of_ty(
         &self,
         info: FieldInfo<'_>,
-    ) -> Result<(proc_macro2::TokenStream, proc_macro2::TokenStream), SessionDiagnosticDeriveError>
-    {
+    ) -> Result<(TokenStream, TokenStream), SessionDiagnosticDeriveError> {
         match &info.ty {
             // If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
-            ty @ syn::Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
+            ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
                 let binding = &info.binding.binding;
                 Ok((quote!(*#binding), quote!(rustc_errors::Applicability::Unspecified)))
             }
             // If `ty` is `(Span, Applicability)` then return tokens accessing those.
-            syn::Type::Tuple(tup) => {
+            Type::Tuple(tup) => {
                 let mut span_idx = None;
                 let mut applicability_idx = None;
 
@@ -855,43 +877,53 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
             }),
         }
     }
+}
+
+trait HasFieldMap {
+    fn get_field_binding(&self, field: &String) -> Option<&TokenStream>;
 
     /// In the strings in the attributes supplied to this macro, we want callers to be able to
     /// reference fields in the format string. For example:
     ///
     /// ```ignore (not-usage-example)
-    /// struct Point {
-    ///     #[error = "Expected a point greater than ({x}, {y})"]
-    ///     x: i32,
-    ///     y: i32,
+    /// /// Suggest `==` when users wrote `===`.
+    /// #[suggestion(slug = "parser-not-javascript-eq", code = "{lhs} == {rhs}")]
+    /// struct NotJavaScriptEq {
+    ///     #[primary_span]
+    ///     span: Span,
+    ///     lhs: Ident,
+    ///     rhs: Ident,
     /// }
     /// ```
     ///
-    /// We want to automatically pick up that `{x}` refers `self.x` and `{y}` refers to `self.y`,
-    /// then generate this call to `format!`:
+    /// We want to automatically pick up that `{lhs}` refers `self.lhs` and `{rhs}` refers to
+    /// `self.rhs`, then generate this call to `format!`:
     ///
     /// ```ignore (not-usage-example)
-    /// format!("Expected a point greater than ({x}, {y})", x = self.x, y = self.y)
+    /// format!("{lhs} == {rhs}", lhs = self.lhs, rhs = self.rhs)
     /// ```
     ///
     /// This function builds the entire call to `format!`.
-    fn build_format(&self, input: &str, span: proc_macro2::Span) -> proc_macro2::TokenStream {
+    fn build_format(&self, input: &str, span: proc_macro2::Span) -> TokenStream {
         // This set is used later to generate the final format string. To keep builds reproducible,
-        // the iteration order needs to be deterministic, hence why we use a BTreeSet here instead
-        // of a HashSet.
+        // the iteration order needs to be deterministic, hence why we use a `BTreeSet` here
+        // instead of a `HashSet`.
         let mut referenced_fields: BTreeSet<String> = BTreeSet::new();
 
         // At this point, we can start parsing the format string.
         let mut it = input.chars().peekable();
+
         // Once the start of a format string has been found, process the format string and spit out
-        // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so the
-        // next call to `it.next()` retrieves the next character.
+        // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so
+        // the next call to `it.next()` retrieves the next character.
         while let Some(c) = it.next() {
             if c == '{' && *it.peek().unwrap_or(&'\0') != '{' {
                 let mut eat_argument = || -> Option<String> {
                     let mut result = String::new();
-                    // Format specifiers look like
-                    // format   := '{' [ argument ] [ ':' format_spec ] '}' .
+                    // Format specifiers look like:
+                    //
+                    //   format   := '{' [ argument ] [ ':' format_spec ] '}' .
+                    //
                     // Therefore, we only need to eat until ':' or '}' to find the argument.
                     while let Some(c) = it.next() {
                         result.push(c);
@@ -916,25 +948,25 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
                 }
             }
         }
+
         // At this point, `referenced_fields` contains a set of the unique fields that were
         // referenced in the format string. Generate the corresponding "x = self.x" format
         // string parameters:
         let args = referenced_fields.into_iter().map(|field: String| {
             let field_ident = format_ident!("{}", field);
-            let value = if self.fields.contains_key(&field) {
-                quote! {
-                    &self.#field_ident
-                }
-            } else {
+            let value = match self.get_field_binding(&field) {
+                Some(value) => value.clone(),
                 // This field doesn't exist. Emit a diagnostic.
-                Diagnostic::spanned(
-                    span.unwrap(),
-                    proc_macro::Level::Error,
-                    format!("`{}` doesn't refer to a field on this type", field),
-                )
-                .emit();
-                quote! {
-                    "{#field}"
+                None => {
+                    Diagnostic::spanned(
+                        span.unwrap(),
+                        Level::Error,
+                        format!("`{}` doesn't refer to a field on this type", field),
+                    )
+                    .emit();
+                    quote! {
+                        "{#field}"
+                    }
                 }
             };
             quote! {
@@ -947,10 +979,67 @@ impl<'a> SessionDiagnosticDeriveBuilder<'a> {
     }
 }
 
+impl HasFieldMap for SessionDiagnosticDeriveBuilder {
+    fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
+        self.fields.get(field)
+    }
+}
+
+/// Reports an error if the field's type is not `Applicability`.
+fn report_error_if_not_applied_to_ty(
+    attr: &Attribute,
+    info: &FieldInfo<'_>,
+    path: &[&str],
+    ty_name: &str,
+) -> Result<(), SessionDiagnosticDeriveError> {
+    if !type_matches_path(&info.ty, path) {
+        let name = attr.path.segments.last().unwrap().ident.to_string();
+        let name = name.as_str();
+        let meta = attr.parse_meta()?;
+
+        throw_span_err!(
+            attr.span().unwrap(),
+            &format!(
+                "the `#[{}{}]` attribute can only be applied to fields of type `{}`",
+                name,
+                match meta {
+                    Meta::Path(_) => "",
+                    Meta::NameValue(_) => " = ...",
+                    Meta::List(_) => "(...)",
+                },
+                ty_name
+            )
+        );
+    }
+
+    Ok(())
+}
+
+/// Reports an error if the field's type is not `Applicability`.
+fn report_error_if_not_applied_to_applicability(
+    attr: &Attribute,
+    info: &FieldInfo<'_>,
+) -> Result<(), SessionDiagnosticDeriveError> {
+    report_error_if_not_applied_to_ty(
+        attr,
+        info,
+        &["rustc_errors", "Applicability"],
+        "Applicability",
+    )
+}
+
+/// Reports an error if the field's type is not `Span`.
+fn report_error_if_not_applied_to_span(
+    attr: &Attribute,
+    info: &FieldInfo<'_>,
+) -> Result<(), SessionDiagnosticDeriveError> {
+    report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "Span")
+}
+
 /// If `ty` is an Option, returns `Some(inner type)`, otherwise returns `None`.
-fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> {
+fn option_inner_ty(ty: &Type) -> Option<&Type> {
     if type_matches_path(ty, &["std", "option", "Option"]) {
-        if let syn::Type::Path(ty_path) = ty {
+        if let Type::Path(ty_path) = ty {
             let path = &ty_path.path;
             let ty = path.segments.iter().last().unwrap();
             if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments {
@@ -964,3 +1053,538 @@ fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> {
     }
     None
 }
+
+trait SetOnce<T> {
+    fn set_once(&mut self, value: T);
+}
+
+impl<T> SetOnce<(T, proc_macro::Span)> for Option<(T, proc_macro::Span)> {
+    fn set_once(&mut self, (value, span): (T, proc_macro::Span)) {
+        match self {
+            None => {
+                *self = Some((value, span));
+            }
+            Some((_, prev_span)) => {
+                span_err(span, "specified multiple times")
+                    .span_note(*prev_span, "previously specified here")
+                    .emit();
+            }
+        }
+    }
+}
+
+enum Applicability {
+    MachineApplicable,
+    MaybeIncorrect,
+    HasPlaceholders,
+    Unspecified,
+}
+
+impl FromStr for Applicability {
+    type Err = ();
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "machine-applicable" => Ok(Applicability::MachineApplicable),
+            "maybe-incorrect" => Ok(Applicability::MaybeIncorrect),
+            "has-placeholders" => Ok(Applicability::HasPlaceholders),
+            "unspecified" => Ok(Applicability::Unspecified),
+            _ => Err(()),
+        }
+    }
+}
+
+impl quote::ToTokens for Applicability {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        tokens.extend(match self {
+            Applicability::MachineApplicable => {
+                quote! { rustc_errors::Applicability::MachineApplicable }
+            }
+            Applicability::MaybeIncorrect => {
+                quote! { rustc_errors::Applicability::MaybeIncorrect }
+            }
+            Applicability::HasPlaceholders => {
+                quote! { rustc_errors::Applicability::HasPlaceholders }
+            }
+            Applicability::Unspecified => {
+                quote! { rustc_errors::Applicability::Unspecified }
+            }
+        });
+    }
+}
+
+#[derive(Clone, Copy)]
+enum SubdiagnosticSuggestionKind {
+    /// `#[suggestion]`
+    Normal,
+    /// `#[suggestion_short]`
+    Short,
+    /// `#[suggestion_hidden]`
+    Hidden,
+    /// `#[suggestion_verbose]`
+    Verbose,
+}
+
+#[derive(Clone, Copy)]
+enum SubdiagnosticKind {
+    /// `#[label]` or `#[label(...)]`
+    Label,
+    /// `#[note]` or  `#[note(...)]`
+    Note,
+    /// `#[help]` or `#[help(...)]`
+    Help,
+    /// `#[suggestion{,_short,_hidden,_verbose}]`
+    Suggestion(SubdiagnosticSuggestionKind),
+}
+
+impl FromStr for SubdiagnosticKind {
+    type Err = ();
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "label" => Ok(SubdiagnosticKind::Label),
+            "note" => Ok(SubdiagnosticKind::Note),
+            "help" => Ok(SubdiagnosticKind::Help),
+            "suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)),
+            "suggestion_short" => {
+                Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short))
+            }
+            "suggestion_hidden" => {
+                Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden))
+            }
+            "suggestion_verbose" => {
+                Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose))
+            }
+            _ => Err(()),
+        }
+    }
+}
+
+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::Suggestion(SubdiagnosticSuggestionKind::Normal) => {
+                write!(f, "suggestion")
+            }
+            SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short) => {
+                write!(f, "suggestion_short")
+            }
+            SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Hidden) => {
+                write!(f, "suggestion_hidden")
+            }
+            SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Verbose) => {
+                write!(f, "suggestion_verbose")
+            }
+        }
+    }
+
+    fn span(&self) -> Option<proc_macro2::Span> {
+        None
+    }
+}
+
+struct SessionSubdiagnosticDerive<'a> {
+    structure: Structure<'a>,
+    diag: syn::Ident,
+}
+
+struct SessionSubdiagnosticDeriveBuilder<'a> {
+    /// The identifier to use for the generated `DiagnosticBuilder` instance.
+    diag: &'a syn::Ident,
+
+    /// Info for the current variant (or the type if not an enum).
+    variant: &'a VariantInfo<'a>,
+    /// Span for the entire type.
+    span: proc_macro::Span,
+
+    /// Store a map of field name to its corresponding field. This is built on construction of the
+    /// derive builder.
+    fields: HashMap<String, TokenStream>,
+
+    /// Subdiagnostic kind of the type/variant.
+    kind: Option<(SubdiagnosticKind, proc_macro::Span)>,
+
+    /// Slug of the subdiagnostic - corresponds to the Fluent identifier for the message - from the
+    /// `#[kind(slug = "...")]` attribute on the type or variant.
+    slug: Option<(String, proc_macro::Span)>,
+    /// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]`
+    /// attribute on the type or variant.
+    code: Option<(proc_macro2::TokenStream, proc_macro::Span)>,
+
+    /// 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<(proc_macro2::TokenStream, proc_macro::Span)>,
+}
+
+impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
+    fn get_field_binding(&self, field: &String) -> Option<&TokenStream> {
+        self.fields.get(field)
+    }
+}
+
+impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
+    fn identify_kind(&mut self) -> Result<(), SessionDiagnosticDeriveError> {
+        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 kind = match meta {
+                Meta::Path(_) => throw_span_err!(
+                    span,
+                    &format!("`#[{}]` is not a valid `SessionSubdiagnostic` attribute", name)
+                ),
+                Meta::NameValue(_) => throw_span_err!(
+                    span,
+                    &format!("`#[{} = ...]` is not a valid `SessionSubdiagnostic` attribute", name)
+                ),
+                Meta::List(MetaList { nested, .. }) => {
+                    for attr in nested {
+                        let meta = match attr {
+                            syn::NestedMeta::Meta(meta) => meta,
+                            syn::NestedMeta::Lit(_) => throw_span_err!(
+                                span,
+                                &format!(
+                                    "`#[{}(\"...\")]` is not a valid `SessionSubdiagnostic` attribute",
+                                    name
+                                )
+                            ),
+                        };
+
+                        let span = meta.span().unwrap();
+                        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), .. }) => {
+                                match nested_name {
+                                    "code" => {
+                                        let formatted_str = self.build_format(&s.value(), s.span());
+                                        self.code.set_once((formatted_str, span));
+                                    }
+                                    "slug" => self.slug.set_once((s.value(), span)),
+                                    "applicability" => {
+                                        let value = match Applicability::from_str(&s.value()) {
+                                            Ok(v) => v,
+                                            Err(()) => {
+                                                span_err(span, "invalid applicability").emit();
+                                                Applicability::Unspecified
+                                            }
+                                        };
+                                        self.applicability.set_once((quote! { #value }, span));
+                                    }
+                                    other => throw_span_err!(
+                                        span,
+                                        &format!(
+                                            "`#[{}({} = ...)]` is not a valid `SessionSubdiagnostic` attribute",
+                                            name, other
+                                        )
+                                    ),
+                                }
+                            }
+                            Meta::NameValue(..) => throw_span_err!(
+                                span,
+                                &format!(
+                                    "`#[{}({} = ...)]` is not a valid `SessionSubdiagnostic` attribute",
+                                    name, nested_name
+                                ),
+                                |diag| diag.help("value must be a string")
+                            ),
+                            Meta::Path(..) => throw_span_err!(
+                                span,
+                                &format!(
+                                    "`#[{}({})]` is not a valid `SessionSubdiagnostic` attribute",
+                                    name, nested_name
+                                )
+                            ),
+                            Meta::List(..) => throw_span_err!(
+                                span,
+                                &format!(
+                                    "`#[{}({}(...))]` is not a valid `SessionSubdiagnostic` attribute",
+                                    name, nested_name
+                                )
+                            ),
+                        }
+                    }
+
+                    let Ok(kind) = SubdiagnosticKind::from_str(name) else {
+                        throw_span_err!(
+                            span,
+                            &format!(
+                                "`#[{}(...)]` is not a valid `SessionSubdiagnostic` attribute",
+                                name
+                            )
+                        );
+                    };
+                    kind
+                }
+            };
+
+            if matches!(
+                kind,
+                SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note
+            ) && self.code.is_some()
+            {
+                throw_span_err!(
+                    span,
+                    &format!("`code` is not a valid nested attribute of a `{}` attribute", name)
+                );
+            }
+
+            if self.slug.is_none() {
+                throw_span_err!(
+                    span,
+                    &format!("`slug` must be set in a `#[{}(...)]` attribute", name)
+                );
+            }
+
+            self.kind.set_once((kind, span));
+        }
+
+        Ok(())
+    }
+
+    fn generate_field_code(
+        &mut self,
+        binding: &BindingInfo<'_>,
+        is_suggestion: bool,
+    ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
+        let ast = binding.ast();
+
+        let option_ty = option_inner_ty(&ast.ty);
+        let info = FieldInfo {
+            vis: &ast.vis,
+            binding: binding,
+            ty: option_ty.unwrap_or(&ast.ty),
+            span: &ast.span(),
+        };
+
+        for attr in &ast.attrs {
+            let name = attr.path.segments.last().unwrap().ident.to_string();
+            let name = name.as_str();
+            let span = attr.span().unwrap();
+
+            let meta = attr.parse_meta()?;
+            match meta {
+                Meta::Path(_) => match name {
+                    "primary_span" => {
+                        report_error_if_not_applied_to_span(attr, &info)?;
+                        self.span_field.set_once((binding.binding.clone(), span));
+                        return Ok(quote! {});
+                    }
+                    "applicability" if is_suggestion => {
+                        report_error_if_not_applied_to_applicability(attr, &info)?;
+                        let binding = binding.binding.clone();
+                        self.applicability.set_once((quote! { #binding }, span));
+                        return Ok(quote! {});
+                    }
+                    "applicability" => {
+                        span_err(span, "`#[applicability]` is only valid on suggestions").emit();
+                        return Ok(quote! {});
+                    }
+                    "skip_arg" => {
+                        return Ok(quote! {});
+                    }
+                    other => span_err(
+                        span,
+                        &format!(
+                            "`#[{}]` is not a valid `SessionSubdiagnostic` field attribute",
+                            other
+                        ),
+                    )
+                    .emit(),
+                },
+                Meta::NameValue(_) => span_err(
+                    span,
+                    &format!(
+                        "`#[{} = ...]` is not a valid `SessionSubdiagnostic` field attribute",
+                        name
+                    ),
+                )
+                .emit(),
+                Meta::List(_) => span_err(
+                    span,
+                    &format!(
+                        "`#[{}(...)]` is not a valid `SessionSubdiagnostic` field attribute",
+                        name
+                    ),
+                )
+                .emit(),
+            }
+        }
+
+        let ident = ast.ident.as_ref().unwrap();
+
+        let diag = &self.diag;
+        let generated = quote! {
+            #diag.set_arg(
+                stringify!(#ident),
+                #binding.into_diagnostic_arg()
+            );
+        };
+
+        if option_ty.is_none() {
+            Ok(quote! { #generated })
+        } else {
+            Ok(quote! {
+                if let Some(#binding) = #binding {
+                    #generated
+                }
+            })
+        }
+    }
+
+    fn into_tokens(&mut self) -> Result<TokenStream, SessionDiagnosticDeriveError> {
+        self.identify_kind()?;
+        let Some(kind) = self.kind.map(|(kind, _)| kind) else {
+            throw_span_err!(
+                self.variant.ast().ident.span().unwrap(),
+                "subdiagnostic kind not specified"
+            );
+        };
+
+        let is_suggestion = matches!(kind, SubdiagnosticKind::Suggestion(_));
+
+        let mut args = TokenStream::new();
+        for binding in self.variant.bindings() {
+            let arg = self
+                .generate_field_code(binding, is_suggestion)
+                .unwrap_or_else(|v| v.to_compile_error());
+            args.extend(arg);
+        }
+
+        // Missing slug errors will already have been reported.
+        let slug = self.slug.as_ref().map(|(slug, _)| &**slug).unwrap_or("missing-slug");
+        let code = match self.code.as_ref() {
+            Some((code, _)) => Some(quote! { #code }),
+            None if is_suggestion => {
+                span_err(self.span, "suggestion without `code = \"...\"`").emit();
+                Some(quote! { /* macro error */ "..." })
+            }
+            None => None,
+        };
+
+        let span_field = self.span_field.as_ref().map(|(span, _)| span);
+        let applicability = match self.applicability.clone() {
+            Some((applicability, _)) => Some(applicability),
+            None if is_suggestion => {
+                span_err(self.span, "suggestion without `applicability`").emit();
+                Some(quote! { rustc_errors::Applicability::Unspecified })
+            }
+            None => None,
+        };
+
+        let diag = &self.diag;
+        let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
+        let message = quote! { rustc_errors::DiagnosticMessage::fluent(#slug) };
+        let call = if matches!(kind, SubdiagnosticKind::Suggestion(..)) {
+            if let Some(span) = span_field {
+                quote! { #diag.#name(#span, #message, #code, #applicability); }
+            } else {
+                span_err(self.span, "suggestion without `#[primary_span]` field").emit();
+                quote! { unreachable!(); }
+            }
+        } else if matches!(kind, SubdiagnosticKind::Label) {
+            if let Some(span) = span_field {
+                quote! { #diag.#name(#span, #message); }
+            } else {
+                span_err(self.span, "label without `#[primary_span]` field").emit();
+                quote! { unreachable!(); }
+            }
+        } else {
+            if let Some(span) = span_field {
+                quote! { #diag.#name(#span, #message); }
+            } else {
+                quote! { #diag.#name(#message); }
+            }
+        };
+
+        Ok(quote! {
+            #call
+            #args
+        })
+    }
+}
+
+impl<'a> SessionSubdiagnosticDerive<'a> {
+    fn new(structure: Structure<'a>) -> Self {
+        let diag = format_ident!("diag");
+        Self { structure, diag }
+    }
+
+    fn into_tokens(self) -> TokenStream {
+        let SessionSubdiagnosticDerive { mut structure, diag } = self;
+        let implementation = {
+            let ast = structure.ast();
+            let span = ast.span().unwrap();
+            match ast.data {
+                syn::Data::Struct(..) | syn::Data::Enum(..) => (),
+                syn::Data::Union(..) => {
+                    span_err(
+                        span,
+                        "`#[derive(SessionSubdiagnostic)]` can only be used on structs and enums",
+                    );
+                }
+            }
+
+            if matches!(ast.data, syn::Data::Enum(..)) {
+                for attr in &ast.attrs {
+                    span_err(
+                        attr.span().unwrap(),
+                        "unsupported type attribute for subdiagnostic enum",
+                    )
+                    .emit();
+                }
+            }
+
+            structure.bind_with(|_| synstructure::BindStyle::Move);
+            let variants_ = structure.each_variant(|variant| {
+                // Build the mapping of field names to fields. This allows attributes to peek
+                // values from other fields.
+                let mut fields_map = HashMap::new();
+                for binding in variant.bindings() {
+                    let field = binding.ast();
+                    if let Some(ident) = &field.ident {
+                        fields_map.insert(ident.to_string(), quote! { #binding });
+                    }
+                }
+
+                let mut builder = SessionSubdiagnosticDeriveBuilder {
+                    diag: &diag,
+                    variant,
+                    span,
+                    fields: fields_map,
+                    kind: None,
+                    slug: None,
+                    code: None,
+                    span_field: None,
+                    applicability: None,
+                };
+                builder.into_tokens().unwrap_or_else(|v| v.to_compile_error())
+            });
+
+            quote! {
+                match self {
+                    #variants_
+                }
+            }
+        };
+
+        let ret = structure.gen_impl(quote! {
+            gen impl rustc_errors::AddSubdiagnostic for @Self {
+                fn add_to_diagnostic(self, #diag: &mut rustc_errors::Diagnostic) {
+                    use rustc_errors::{Applicability, IntoDiagnosticArg};
+                    #implementation
+                }
+            }
+        });
+        ret
+    }
+}
diff --git a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
index 8db9da7fcb2..a92c288cac9 100644
--- a/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
+++ b/compiler/rustc_typeck/src/check/fn_ctxt/suggestions.rs
@@ -1,5 +1,6 @@
 use super::FnCtxt;
 use crate::astconv::AstConv;
+use crate::errors::{AddReturnTypeSuggestion, ExpectedReturnTypeLabel};
 
 use rustc_ast::util::parser::ExprPrecedence;
 use rustc_errors::{Applicability, Diagnostic, MultiSpan};
@@ -527,28 +528,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         // haven't set a return type at all (and aren't `fn main()` or an impl).
         match (&fn_decl.output, found.is_suggestable(self.tcx), can_suggest, expected.is_unit()) {
             (&hir::FnRetTy::DefaultReturn(span), true, true, true) => {
-                err.span_suggestion(
-                    span,
-                    "try adding a return type",
-                    format!("-> {} ", found),
-                    Applicability::MachineApplicable,
-                );
+                err.subdiagnostic(AddReturnTypeSuggestion::Add { span, found });
                 true
             }
             (&hir::FnRetTy::DefaultReturn(span), false, true, true) => {
                 // FIXME: if `found` could be `impl Iterator` or `impl Fn*`, we should suggest
                 // that.
-                err.span_suggestion(
-                    span,
-                    "a return type might be missing here",
-                    "-> _ ".to_string(),
-                    Applicability::HasPlaceholders,
-                );
+                err.subdiagnostic(AddReturnTypeSuggestion::MissingHere { span });
                 true
             }
             (&hir::FnRetTy::DefaultReturn(span), _, false, true) => {
                 // `fn main()` must return `()`, do not suggest changing return type
-                err.span_label(span, "expected `()` because of default return type");
+                err.subdiagnostic(ExpectedReturnTypeLabel::Unit { span });
                 true
             }
             // expectation was caused by something else, not the default return
@@ -557,16 +548,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                 // Only point to return type if the expected type is the return type, as if they
                 // are not, the expectation must have been caused by something else.
                 debug!("suggest_missing_return_type: return type {:?} node {:?}", ty, ty.kind);
-                let sp = ty.span;
+                let span = ty.span;
                 let ty = <dyn AstConv<'_>>::ast_ty_to_ty(self, ty);
                 debug!("suggest_missing_return_type: return type {:?}", ty);
                 debug!("suggest_missing_return_type: expected type {:?}", ty);
                 let bound_vars = self.tcx.late_bound_vars(fn_id);
                 let ty = Binder::bind_with_vars(ty, bound_vars);
-                let ty = self.normalize_associated_types_in(sp, ty);
+                let ty = self.normalize_associated_types_in(span, ty);
                 let ty = self.tcx.erase_late_bound_regions(ty);
                 if self.can_coerce(expected, ty) {
-                    err.span_label(sp, format!("expected `{}` because of return type", expected));
+                    err.subdiagnostic(ExpectedReturnTypeLabel::Other { span, expected });
                     self.try_suggest_return_impl_trait(err, expected, ty, fn_id);
                     return true;
                 }
diff --git a/compiler/rustc_typeck/src/errors.rs b/compiler/rustc_typeck/src/errors.rs
index fc08171d2f4..3d2f93537e4 100644
--- a/compiler/rustc_typeck/src/errors.rs
+++ b/compiler/rustc_typeck/src/errors.rs
@@ -1,6 +1,6 @@
 //! Errors emitted by typeck.
 use rustc_errors::Applicability;
-use rustc_macros::SessionDiagnostic;
+use rustc_macros::{SessionDiagnostic, SessionSubdiagnostic};
 use rustc_middle::ty::Ty;
 use rustc_span::{symbol::Ident, Span, Symbol};
 
@@ -190,3 +190,41 @@ pub struct AddressOfTemporaryTaken {
     #[label]
     pub span: Span,
 }
+
+#[derive(SessionSubdiagnostic)]
+pub enum AddReturnTypeSuggestion<'tcx> {
+    #[suggestion(
+        slug = "typeck-add-return-type-add",
+        code = "-> {found} ",
+        applicability = "machine-applicable"
+    )]
+    Add {
+        #[primary_span]
+        span: Span,
+        found: Ty<'tcx>,
+    },
+    #[suggestion(
+        slug = "typeck-add-return-type-missing-here",
+        code = "-> _ ",
+        applicability = "has-placeholders"
+    )]
+    MissingHere {
+        #[primary_span]
+        span: Span,
+    },
+}
+
+#[derive(SessionSubdiagnostic)]
+pub enum ExpectedReturnTypeLabel<'tcx> {
+    #[label(slug = "typeck-expected-default-return-type")]
+    Unit {
+        #[primary_span]
+        span: Span,
+    },
+    #[label(slug = "typeck-expected-return-type")]
+    Other {
+        #[primary_span]
+        span: Span,
+        expected: Ty<'tcx>,
+    },
+}
diff --git a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
new file mode 100644
index 00000000000..a587bd2cbb2
--- /dev/null
+++ b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs
@@ -0,0 +1,501 @@
+// check-fail
+// Tests error conditions for specifying subdiagnostics using #[derive(SessionSubdiagnostic)]
+
+// The proc_macro2 crate handles spans differently when on beta/stable release rather than nightly,
+// changing the output of this test. Since SessionSubdiagnostic is strictly internal to the compiler
+// the test is just ignored on stable and beta:
+// ignore-beta
+// ignore-stable
+
+#![feature(rustc_private)]
+#![crate_type = "lib"]
+
+extern crate rustc_errors;
+extern crate rustc_session;
+extern crate rustc_span;
+extern crate rustc_macros;
+
+use rustc_errors::Applicability;
+use rustc_span::Span;
+use rustc_macros::SessionSubdiagnostic;
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "label-a")]
+struct A {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+enum B {
+    #[label(slug = "label-b-a")]
+    A {
+        #[primary_span]
+        span: Span,
+        var: String,
+    },
+    #[label(slug = "label-b-b")]
+    B {
+        #[primary_span]
+        span: Span,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "label-c")]
+//~^ ERROR label without `#[primary_span]` field
+struct C {
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label]
+//~^ ERROR `#[label]` is not a valid `SessionSubdiagnostic` attribute 
+struct D {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[foo]
+//~^ ERROR `#[foo]` is not a valid `SessionSubdiagnostic` attribute
+//~^^ ERROR cannot find attribute `foo` in this scope
+struct E {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label = "..."]
+//~^ ERROR `#[label = ...]` is not a valid `SessionSubdiagnostic` attribute
+struct F {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(bug = "...")]
+//~^ ERROR `#[label(bug = ...)]` is not a valid `SessionSubdiagnostic` attribute
+struct G {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label("...")]
+//~^ ERROR `#[label("...")]` is not a valid `SessionSubdiagnostic` attribute
+struct H {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = 4)]
+//~^ ERROR `#[label(slug = ...)]` is not a valid `SessionSubdiagnostic` attribute
+struct J {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug("..."))]
+//~^ ERROR `#[label(slug(...))]` is not a valid `SessionSubdiagnostic` attribute
+struct K {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug)]
+//~^ ERROR `#[label(slug)]` is not a valid `SessionSubdiagnostic` attribute
+struct L {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label()]
+//~^ ERROR `slug` must be set in a `#[label(...)]` attribute
+struct M {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(code = "...")]
+//~^ ERROR `code` is not a valid nested attribute of a `label` attribute
+struct N {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[foo]
+//~^ ERROR cannot find attribute `foo` in this scope
+//~^^ ERROR unsupported type attribute for subdiagnostic enum
+enum O {
+    #[label(slug = "...")]
+    A {
+        #[primary_span]
+        span: Span,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+enum P {
+    #[bar]
+//~^ ERROR `#[bar]` is not a valid `SessionSubdiagnostic` attribute
+//~^^ ERROR cannot find attribute `bar` in this scope
+    A {
+        #[primary_span]
+        span: Span,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+enum Q {
+    #[bar = "..."]
+//~^ ERROR `#[bar = ...]` is not a valid `SessionSubdiagnostic` attribute
+//~^^ ERROR cannot find attribute `bar` in this scope
+    A {
+        #[primary_span]
+        span: Span,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+enum R {
+    #[bar = 4]
+//~^ ERROR `#[bar = ...]` is not a valid `SessionSubdiagnostic` attribute
+//~^^ ERROR cannot find attribute `bar` in this scope
+    A {
+        #[primary_span]
+        span: Span,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+enum S {
+    #[bar("...")]
+//~^ ERROR `#[bar("...")]` is not a valid `SessionSubdiagnostic` attribute
+//~^^ ERROR cannot find attribute `bar` in this scope
+    A {
+        #[primary_span]
+        span: Span,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+enum T {
+    #[label(code = "...")]
+//~^ ERROR `code` is not a valid nested attribute of a `label`
+    A {
+        #[primary_span]
+        span: Span,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+enum U {
+    #[label(slug = "label-u")]
+    A {
+        #[primary_span]
+        span: Span,
+        var: String,
+    },
+    B {
+//~^ ERROR subdiagnostic kind not specified
+        #[primary_span]
+        span: Span,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "...")]
+//~^ ERROR label without `#[primary_span]` field
+struct V {
+    #[primary_span]
+    //~^ ERROR the `#[primary_span]` attribute can only be applied to fields of type `Span`
+    span: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "...")]
+struct W {
+    #[primary_span]
+    span: Span,
+    #[applicability]
+    //~^ ERROR `#[applicability]` is only valid on suggestions
+    applicability: Applicability,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "...")]
+struct X {
+    #[primary_span]
+    span: Span,
+    #[bar]
+    //~^ ERROR `#[bar]` is not a valid `SessionSubdiagnostic` field attribute
+    //~^^ ERROR cannot find attribute `bar` in this scope
+    bar: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "...")]
+struct Y {
+    #[primary_span]
+    span: Span,
+    #[bar = "..."]
+    //~^ ERROR `#[bar = ...]` is not a valid `SessionSubdiagnostic` field attribute
+    //~^^ ERROR cannot find attribute `bar` in this scope
+    bar: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "...")]
+struct Z {
+    #[primary_span]
+    span: Span,
+    #[bar("...")]
+    //~^ ERROR `#[bar(...)]` is not a valid `SessionSubdiagnostic` field attribute
+    //~^^ ERROR cannot find attribute `bar` in this scope
+    bar: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "label-aa")]
+struct AA {
+    #[primary_span]
+    span: Span,
+    #[skip_arg]
+    z: Z
+}
+
+#[derive(SessionSubdiagnostic)]
+union AB {
+//~^ ERROR unexpected unsupported untagged union
+    span: u32,
+    b: u64
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "label-ac-1")]
+//~^ NOTE previously specified here
+//~^^ NOTE previously specified here
+#[label(slug = "label-ac-2")]
+//~^ ERROR specified multiple times
+//~^^ ERROR specified multiple times
+struct AC {
+    #[primary_span]
+    span: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "label-ad-1", slug = "label-ad-2")]
+//~^ ERROR specified multiple times
+//~^^ NOTE previously specified here
+struct AD {
+    #[primary_span]
+    span: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label(slug = "label-ad-1")]
+struct AE {
+    #[primary_span]
+//~^ NOTE previously specified here
+    span_a: Span,
+    #[primary_span]
+//~^ ERROR specified multiple times
+    span_b: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+struct AF {
+//~^ ERROR subdiagnostic kind not specified
+    #[primary_span]
+    span: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "suggestion-af", code = "...")]
+struct AG {
+    #[primary_span]
+    span: Span,
+    #[applicability]
+    applicability: Applicability,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+enum AH {
+    #[suggestion(slug = "suggestion-ag-a", code = "...")]
+    A {
+        #[primary_span]
+        span: Span,
+        #[applicability]
+        applicability: Applicability,
+        var: String,
+    },
+    #[suggestion(slug = "suggestion-ag-b", code = "...")]
+    B {
+        #[primary_span]
+        span: Span,
+        #[applicability]
+        applicability: Applicability,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "...", code = "...", code = "...")]
+//~^ ERROR specified multiple times
+//~^^ NOTE previously specified here
+struct AI {
+    #[primary_span]
+    span: Span,
+    #[applicability]
+    applicability: Applicability,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "...", code = "...")]
+struct AJ {
+    #[primary_span]
+    span: Span,
+    #[applicability]
+//~^ NOTE previously specified here
+    applicability_a: Applicability,
+    #[applicability]
+//~^ ERROR specified multiple times
+    applicability_b: Applicability,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "...", code = "...")]
+//~^ ERROR suggestion without `applicability`
+struct AK {
+    #[primary_span]
+    span: Span,
+    #[applicability]
+//~^ ERROR the `#[applicability]` attribute can only be applied to fields of type `Applicability`
+    applicability: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "...", code = "...")]
+//~^ ERROR suggestion without `applicability`
+struct AL {
+    #[primary_span]
+    span: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "...")]
+//~^ ERROR suggestion without `code = "..."`
+struct AM {
+    #[primary_span]
+    span: Span,
+    #[applicability]
+    applicability: Applicability,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "...", code ="...", applicability = "foo")]
+//~^ ERROR invalid applicability
+struct AN {
+    #[primary_span]
+    span: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[help(slug = "label-am")]
+struct AO {
+    var: String
+}
+
+#[derive(SessionSubdiagnostic)]
+#[note(slug = "label-an")]
+struct AP;
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "...", code = "...")]
+//~^ ERROR suggestion without `applicability`
+//~^^ ERROR suggestion without `#[primary_span]` field
+struct AQ {
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "...", code ="...", applicability = "machine-applicable")]
+struct AR {
+    #[primary_span]
+    span: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[label]
+//~^ ERROR unsupported type attribute for subdiagnostic enum
+enum AS {
+    #[label(slug = "...")]
+    A {
+        #[primary_span]
+        span: Span,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")]
+struct AT {
+    #[primary_span]
+    span: Span,
+    var: String,
+}
+
+#[derive(SessionSubdiagnostic)]
+#[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")]
+//~^ ERROR `var` doesn't refer to a field on this type
+struct AU {
+    #[primary_span]
+    span: Span,
+}
+
+#[derive(SessionSubdiagnostic)]
+enum AV {
+    #[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")]
+    A {
+        #[primary_span]
+        span: Span,
+        var: String,
+    }
+}
+
+#[derive(SessionSubdiagnostic)]
+enum AW {
+    #[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")]
+//~^ ERROR `var` doesn't refer to a field on this type
+    A {
+        #[primary_span]
+        span: Span,
+    }
+}
diff --git a/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr
new file mode 100644
index 00000000000..bc96fcf771b
--- /dev/null
+++ b/src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr
@@ -0,0 +1,387 @@
+error: label without `#[primary_span]` field
+  --> $DIR/subdiagnostic-derive.rs:47:1
+   |
+LL | / #[label(slug = "label-c")]
+LL | |
+LL | | struct C {
+LL | |     var: String,
+LL | | }
+   | |_^
+
+error: `#[label]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:54:1
+   |
+LL | #[label]
+   | ^^^^^^^^
+
+error: `#[foo]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:63:1
+   |
+LL | #[foo]
+   | ^^^^^^
+
+error: `#[label = ...]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:73:1
+   |
+LL | #[label = "..."]
+   | ^^^^^^^^^^^^^^^^
+
+error: `#[label(bug = ...)]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:82:9
+   |
+LL | #[label(bug = "...")]
+   |         ^^^^^^^^^^^
+
+error: `#[label("...")]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:91:1
+   |
+LL | #[label("...")]
+   | ^^^^^^^^^^^^^^^
+
+error: `#[label(slug = ...)]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:100:9
+   |
+LL | #[label(slug = 4)]
+   |         ^^^^^^^^
+   |
+   = help: value must be a string
+
+error: `#[label(slug(...))]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:109:9
+   |
+LL | #[label(slug("..."))]
+   |         ^^^^^^^^^^^
+
+error: `#[label(slug)]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:118:9
+   |
+LL | #[label(slug)]
+   |         ^^^^
+
+error: `slug` must be set in a `#[label(...)]` attribute
+  --> $DIR/subdiagnostic-derive.rs:127:1
+   |
+LL | #[label()]
+   | ^^^^^^^^^^
+
+error: `code` is not a valid nested attribute of a `label` attribute
+  --> $DIR/subdiagnostic-derive.rs:136:1
+   |
+LL | #[label(code = "...")]
+   | ^^^^^^^^^^^^^^^^^^^^^^
+
+error: unsupported type attribute for subdiagnostic enum
+  --> $DIR/subdiagnostic-derive.rs:145:1
+   |
+LL | #[foo]
+   | ^^^^^^
+
+error: `#[bar]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:159:5
+   |
+LL |     #[bar]
+   |     ^^^^^^
+
+error: `#[bar = ...]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:171:5
+   |
+LL |     #[bar = "..."]
+   |     ^^^^^^^^^^^^^^
+
+error: `#[bar = ...]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:183:5
+   |
+LL |     #[bar = 4]
+   |     ^^^^^^^^^^
+
+error: `#[bar("...")]` is not a valid `SessionSubdiagnostic` attribute
+  --> $DIR/subdiagnostic-derive.rs:195:5
+   |
+LL |     #[bar("...")]
+   |     ^^^^^^^^^^^^^
+
+error: `code` is not a valid nested attribute of a `label` attribute
+  --> $DIR/subdiagnostic-derive.rs:207:5
+   |
+LL |     #[label(code = "...")]
+   |     ^^^^^^^^^^^^^^^^^^^^^^
+
+error: subdiagnostic kind not specified
+  --> $DIR/subdiagnostic-derive.rs:224:5
+   |
+LL |     B {
+   |     ^
+
+error: the `#[primary_span]` attribute can only be applied to fields of type `Span`
+  --> $DIR/subdiagnostic-derive.rs:236:5
+   |
+LL |     #[primary_span]
+   |     ^^^^^^^^^^^^^^^
+
+error: label without `#[primary_span]` field
+  --> $DIR/subdiagnostic-derive.rs:233:1
+   |
+LL | / #[label(slug = "...")]
+LL | |
+LL | | struct V {
+LL | |     #[primary_span]
+LL | |
+LL | |     span: String,
+LL | | }
+   | |_^
+
+error: `#[applicability]` is only valid on suggestions
+  --> $DIR/subdiagnostic-derive.rs:246:5
+   |
+LL |     #[applicability]
+   |     ^^^^^^^^^^^^^^^^
+
+error: `#[bar]` is not a valid `SessionSubdiagnostic` field attribute
+  --> $DIR/subdiagnostic-derive.rs:256:5
+   |
+LL |     #[bar]
+   |     ^^^^^^
+
+error: `#[bar = ...]` is not a valid `SessionSubdiagnostic` field attribute
+  --> $DIR/subdiagnostic-derive.rs:267:5
+   |
+LL |     #[bar = "..."]
+   |     ^^^^^^^^^^^^^^
+
+error: `#[bar(...)]` is not a valid `SessionSubdiagnostic` field attribute
+  --> $DIR/subdiagnostic-derive.rs:278:5
+   |
+LL |     #[bar("...")]
+   |     ^^^^^^^^^^^^^
+
+error: unexpected unsupported untagged union
+  --> $DIR/subdiagnostic-derive.rs:294:1
+   |
+LL | / union AB {
+LL | |
+LL | |     span: u32,
+LL | |     b: u64
+LL | | }
+   | |_^
+
+error: specified multiple times
+  --> $DIR/subdiagnostic-derive.rs:304:9
+   |
+LL | #[label(slug = "label-ac-2")]
+   |         ^^^^^^^^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/subdiagnostic-derive.rs:301:9
+   |
+LL | #[label(slug = "label-ac-1")]
+   |         ^^^^^^^^^^^^^^^^^^^
+
+error: specified multiple times
+  --> $DIR/subdiagnostic-derive.rs:304:1
+   |
+LL | #[label(slug = "label-ac-2")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/subdiagnostic-derive.rs:301:1
+   |
+LL | #[label(slug = "label-ac-1")]
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+error: specified multiple times
+  --> $DIR/subdiagnostic-derive.rs:313:30
+   |
+LL | #[label(slug = "label-ad-1", slug = "label-ad-2")]
+   |                              ^^^^^^^^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/subdiagnostic-derive.rs:313:9
+   |
+LL | #[label(slug = "label-ad-1", slug = "label-ad-2")]
+   |         ^^^^^^^^^^^^^^^^^^^
+
+error: specified multiple times
+  --> $DIR/subdiagnostic-derive.rs:327:5
+   |
+LL |     #[primary_span]
+   |     ^^^^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/subdiagnostic-derive.rs:324:5
+   |
+LL |     #[primary_span]
+   |     ^^^^^^^^^^^^^^^
+
+error: subdiagnostic kind not specified
+  --> $DIR/subdiagnostic-derive.rs:333:8
+   |
+LL | struct AF {
+   |        ^^
+
+error: specified multiple times
+  --> $DIR/subdiagnostic-derive.rs:370:42
+   |
+LL | #[suggestion(slug = "...", code = "...", code = "...")]
+   |                                          ^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/subdiagnostic-derive.rs:370:28
+   |
+LL | #[suggestion(slug = "...", code = "...", code = "...")]
+   |                            ^^^^^^^^^^^^
+
+error: specified multiple times
+  --> $DIR/subdiagnostic-derive.rs:388:5
+   |
+LL |     #[applicability]
+   |     ^^^^^^^^^^^^^^^^
+   |
+note: previously specified here
+  --> $DIR/subdiagnostic-derive.rs:385:5
+   |
+LL |     #[applicability]
+   |     ^^^^^^^^^^^^^^^^
+
+error: the `#[applicability]` attribute can only be applied to fields of type `Applicability`
+  --> $DIR/subdiagnostic-derive.rs:399:5
+   |
+LL |     #[applicability]
+   |     ^^^^^^^^^^^^^^^^
+
+error: suggestion without `applicability`
+  --> $DIR/subdiagnostic-derive.rs:394:1
+   |
+LL | / #[suggestion(slug = "...", code = "...")]
+LL | |
+LL | | struct AK {
+LL | |     #[primary_span]
+...  |
+LL | |     applicability: Span,
+LL | | }
+   | |_^
+
+error: suggestion without `applicability`
+  --> $DIR/subdiagnostic-derive.rs:405:1
+   |
+LL | / #[suggestion(slug = "...", code = "...")]
+LL | |
+LL | | struct AL {
+LL | |     #[primary_span]
+LL | |     span: Span,
+LL | | }
+   | |_^
+
+error: suggestion without `code = "..."`
+  --> $DIR/subdiagnostic-derive.rs:413:1
+   |
+LL | / #[suggestion(slug = "...")]
+LL | |
+LL | | struct AM {
+LL | |     #[primary_span]
+...  |
+LL | |     applicability: Applicability,
+LL | | }
+   | |_^
+
+error: invalid applicability
+  --> $DIR/subdiagnostic-derive.rs:423:41
+   |
+LL | #[suggestion(slug = "...", code ="...", applicability = "foo")]
+   |                                         ^^^^^^^^^^^^^^^^^^^^^
+
+error: suggestion without `applicability`
+  --> $DIR/subdiagnostic-derive.rs:441:1
+   |
+LL | / #[suggestion(slug = "...", code = "...")]
+LL | |
+LL | |
+LL | | struct AQ {
+LL | |     var: String,
+LL | | }
+   | |_^
+
+error: suggestion without `#[primary_span]` field
+  --> $DIR/subdiagnostic-derive.rs:441:1
+   |
+LL | / #[suggestion(slug = "...", code = "...")]
+LL | |
+LL | |
+LL | | struct AQ {
+LL | |     var: String,
+LL | | }
+   | |_^
+
+error: unsupported type attribute for subdiagnostic enum
+  --> $DIR/subdiagnostic-derive.rs:456:1
+   |
+LL | #[label]
+   | ^^^^^^^^
+
+error: `var` doesn't refer to a field on this type
+  --> $DIR/subdiagnostic-derive.rs:476:34
+   |
+LL | #[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")]
+   |                                  ^^^^^^^
+
+error: `var` doesn't refer to a field on this type
+  --> $DIR/subdiagnostic-derive.rs:495:38
+   |
+LL |     #[suggestion(slug = "...", code ="{var}", applicability = "machine-applicable")]
+   |                                      ^^^^^^^
+
+error: cannot find attribute `foo` in this scope
+  --> $DIR/subdiagnostic-derive.rs:63:3
+   |
+LL | #[foo]
+   |   ^^^
+
+error: cannot find attribute `foo` in this scope
+  --> $DIR/subdiagnostic-derive.rs:145:3
+   |
+LL | #[foo]
+   |   ^^^
+
+error: cannot find attribute `bar` in this scope
+  --> $DIR/subdiagnostic-derive.rs:159:7
+   |
+LL |     #[bar]
+   |       ^^^
+
+error: cannot find attribute `bar` in this scope
+  --> $DIR/subdiagnostic-derive.rs:171:7
+   |
+LL |     #[bar = "..."]
+   |       ^^^
+
+error: cannot find attribute `bar` in this scope
+  --> $DIR/subdiagnostic-derive.rs:183:7
+   |
+LL |     #[bar = 4]
+   |       ^^^
+
+error: cannot find attribute `bar` in this scope
+  --> $DIR/subdiagnostic-derive.rs:195:7
+   |
+LL |     #[bar("...")]
+   |       ^^^
+
+error: cannot find attribute `bar` in this scope
+  --> $DIR/subdiagnostic-derive.rs:256:7
+   |
+LL |     #[bar]
+   |       ^^^
+
+error: cannot find attribute `bar` in this scope
+  --> $DIR/subdiagnostic-derive.rs:267:7
+   |
+LL |     #[bar = "..."]
+   |       ^^^
+
+error: cannot find attribute `bar` in this scope
+  --> $DIR/subdiagnostic-derive.rs:278:7
+   |
+LL |     #[bar("...")]
+   |       ^^^
+
+error: aborting due to 51 previous errors
+