about summary refs log tree commit diff
path: root/compiler/rustc_macros
diff options
context:
space:
mode:
authorXiretza <xiretza@xiretza.xyz>2022-09-12 20:44:28 +0200
committerXiretza <xiretza@xiretza.xyz>2022-09-22 17:25:50 +0200
commite7251cc441f19306e6e71715a9ab36029c543268 (patch)
tree1949b20e93ff17ec1ece40e3592913b5cdd013f9 /compiler/rustc_macros
parentd4a1a6f6986fd722506f7019d4ad669e46f15129 (diff)
downloadrust-e7251cc441f19306e6e71715a9ab36029c543268.tar.gz
rust-e7251cc441f19306e6e71715a9ab36029c543268.zip
Extract subdiagnostic attribute parsing
Diffstat (limited to 'compiler/rustc_macros')
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs273
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs256
2 files changed, 297 insertions, 232 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index 9116dd186f9..9ea03e186e7 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -4,100 +4,16 @@ 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};
 
-use super::utils::SpannedOption;
-
-/// 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::utils::{SpannedOption, SubdiagnosticKind};
 
 /// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
 pub(crate) struct SubdiagnosticDerive<'a> {
@@ -198,8 +114,8 @@ struct SubdiagnosticDeriveBuilder<'a> {
 
     /// Identifier for the binding to the `#[primary_span]` field.
     span_field: SpannedOption<proc_macro2::Ident>,
-    /// If a suggestion, the identifier for the binding to the `#[applicability]` field or a
-    /// `rustc_errors::Applicability::*` variant directly.
+
+    /// 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
@@ -219,6 +135,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 {
@@ -227,8 +144,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 {
@@ -248,129 +172,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 mut slug = None;
-            let mut code = None;
-
-            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",
-                            )
-                        })
-                    }
-                };
-            }
+            let (kind, slug) = SubdiagnosticKind::from_attr(attr, self)?;
 
-            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 {
+                let name = attr.path.segments.last().unwrap().ident.to_string();
+                let name = name.as_str();
 
-            let Some((slug, _)) = slug else {
                 throw_span_err!(
-                    span,
+                    attr.span().unwrap(),
                     &format!(
                         "diagnostic slug must be first argument of a `#[{}(...)]` attribute",
                         name
@@ -378,21 +187,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)
@@ -510,6 +305,15 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
                 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);
                 } else {
@@ -638,11 +442,6 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
             .collect();
 
         let span_field = self.span_field.value_ref();
-        let applicability = self
-            .applicability
-            .take()
-            .value()
-            .unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
 
         let diag = &self.diag;
         let mut calls = TokenStream::new();
@@ -650,7 +449,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();
 
@@ -660,7 +465,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 3efcd216d19..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
@@ -311,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,
@@ -367,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
+    }
+}