about summary refs log tree commit diff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/rustc_macros/src/diagnostics/subdiagnostic.rs216
1 files changed, 120 insertions, 96 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
index 8b40e295bd8..dce5d3cfb84 100644
--- a/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
+++ b/compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
@@ -211,11 +211,39 @@ impl<'a> HasFieldMap for SessionSubdiagnosticDeriveBuilder<'a> {
     }
 }
 
+/// Provides frequently-needed information about the diagnostic kinds being derived for this type.
+#[derive(Clone, Copy, Debug)]
+struct KindsStatistics {
+    has_multipart_suggestion: bool,
+    all_multipart_suggestions: bool,
+    has_normal_suggestion: bool,
+}
+
+impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
+    fn from_iter<T: IntoIterator<Item = &'a SubdiagnosticKind>>(kinds: T) -> Self {
+        let mut ret = Self {
+            has_multipart_suggestion: false,
+            all_multipart_suggestions: true,
+            has_normal_suggestion: false,
+        };
+        for kind in kinds {
+            if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
+                ret.has_multipart_suggestion = true;
+            } else {
+                ret.all_multipart_suggestions = false;
+            }
+
+            if let SubdiagnosticKind::Suggestion { .. } = kind {
+                ret.has_normal_suggestion = true;
+            }
+        }
+        ret
+    }
+}
+
 impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
-    fn identify_kind(
-        &mut self,
-    ) -> Result<Option<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
-        let mut kind_slug = None;
+    fn identify_kind(&mut self) -> Result<Vec<(SubdiagnosticKind, Path)>, DiagnosticDeriveError> {
+        let mut kind_slugs = vec![];
 
         for attr in self.variant.ast().attrs {
             let span = attr.span().unwrap();
@@ -362,10 +390,10 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
                 | SubdiagnosticKind::MultipartSuggestion { .. } => {}
             }
 
-            kind_slug.set_once(((kind, slug), span))
+            kind_slugs.push((kind, slug))
         }
 
-        Ok(kind_slug.map(|(kind_slug, _)| kind_slug))
+        Ok(kind_slugs)
     }
 
     /// Generates the code for a field with no attributes.
@@ -387,7 +415,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
     fn generate_field_attr_code(
         &mut self,
         binding: &BindingInfo<'_>,
-        kind: &SubdiagnosticKind,
+        kind_stats: KindsStatistics,
     ) -> TokenStream {
         let ast = binding.ast();
         assert!(ast.attrs.len() > 0, "field without attributes generating attr code");
@@ -405,7 +433,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
                 };
 
                 let generated = self
-                    .generate_field_code_inner(kind, attr, info)
+                    .generate_field_code_inner(kind_stats, attr, info)
                     .unwrap_or_else(|v| v.to_compile_error());
 
                 inner_ty.with(binding, generated)
@@ -415,15 +443,15 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
 
     fn generate_field_code_inner(
         &mut self,
-        kind: &SubdiagnosticKind,
+        kind_stats: KindsStatistics,
         attr: &Attribute,
         info: FieldInfo<'_>,
     ) -> Result<TokenStream, DiagnosticDeriveError> {
         let meta = attr.parse_meta()?;
         match meta {
-            Meta::Path(path) => self.generate_field_code_inner_path(kind, attr, info, path),
+            Meta::Path(path) => self.generate_field_code_inner_path(kind_stats, attr, info, path),
             Meta::List(list @ MetaList { .. }) => {
-                self.generate_field_code_inner_list(kind, attr, info, list)
+                self.generate_field_code_inner_list(kind_stats, attr, info, list)
             }
             _ => throw_invalid_attr!(attr, &meta),
         }
@@ -432,7 +460,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
     /// Generates the code for a `[Meta::Path]`-like attribute on a field (e.g. `#[primary_span]`).
     fn generate_field_code_inner_path(
         &mut self,
-        kind: &SubdiagnosticKind,
+        kind_stats: KindsStatistics,
         attr: &Attribute,
         info: FieldInfo<'_>,
         path: Path,
@@ -445,7 +473,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
         match name {
             "skip_arg" => Ok(quote! {}),
             "primary_span" => {
-                if matches!(kind, SubdiagnosticKind::MultipartSuggestion { .. }) {
+                if kind_stats.has_multipart_suggestion {
                     throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
                         diag.help(
                             "multipart suggestions use one or more `#[suggestion_part]`s rather \
@@ -464,32 +492,20 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
             "suggestion_part" => {
                 self.has_suggestion_parts = true;
 
-                match kind {
-                    SubdiagnosticKind::MultipartSuggestion { .. } => {
-                        span_err(
-                            span,
-                            "`#[suggestion_part(...)]` attribute without `code = \"...\"`",
-                        )
+                if kind_stats.has_multipart_suggestion {
+                    span_err(span, "`#[suggestion_part(...)]` attribute without `code = \"...\"`")
                         .emit();
-                        Ok(quote! {})
-                    }
-                    SubdiagnosticKind::Label
-                    | SubdiagnosticKind::Note
-                    | SubdiagnosticKind::Help
-                    | SubdiagnosticKind::Warn
-                    | SubdiagnosticKind::Suggestion { .. } => {
-                        throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
-                            diag.help(
+                    Ok(quote! {})
+                } else {
+                    throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
+                        diag.help(
                                 "`#[suggestion_part(...)]` is only valid in multipart suggestions, use `#[primary_span]` instead",
                             )
-                        });
-                    }
+                    });
                 }
             }
             "applicability" => {
-                if let SubdiagnosticKind::Suggestion { .. }
-                | SubdiagnosticKind::MultipartSuggestion { .. } = kind
-                {
+                if kind_stats.has_multipart_suggestion || kind_stats.has_normal_suggestion {
                     report_error_if_not_applied_to_applicability(attr, &info)?;
 
                     let binding = info.binding.binding.clone();
@@ -501,13 +517,16 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
                 Ok(quote! {})
             }
             _ => throw_invalid_attr!(attr, &Meta::Path(path), |diag| {
-                let span_attr = if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
-                    "suggestion_part"
-                } else {
-                    "primary_span"
-                };
+                let mut span_attrs = vec![];
+                if kind_stats.has_multipart_suggestion {
+                    span_attrs.push("suggestion_part");
+                }
+                if !kind_stats.all_multipart_suggestions {
+                    span_attrs.push("primary_span")
+                }
                 diag.help(format!(
-                    "only `{span_attr}`, `applicability` and `skip_arg` are valid field attributes",
+                    "only `{}`, `applicability` and `skip_arg` are valid field attributes",
+                    span_attrs.join(", ")
                 ))
             }),
         }
@@ -517,7 +536,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
     /// `#[suggestion_part(code = "...")]`).
     fn generate_field_code_inner_list(
         &mut self,
-        kind: &SubdiagnosticKind,
+        kind_stats: KindsStatistics,
         attr: &Attribute,
         info: FieldInfo<'_>,
         list: MetaList,
@@ -529,7 +548,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
 
         match name {
             "suggestion_part" => {
-                if !matches!(kind, SubdiagnosticKind::MultipartSuggestion { .. }) {
+                if !kind_stats.has_multipart_suggestion {
                     throw_invalid_attr!(attr, &Meta::List(list), |diag| {
                         diag.help(
                             "`#[suggestion_part(...)]` is only valid in multipart suggestions",
@@ -576,35 +595,36 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
                 Ok(quote! { suggestions.push((#binding, #code)); })
             }
             _ => throw_invalid_attr!(attr, &Meta::List(list), |diag| {
-                let span_attr = if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
-                    "suggestion_part"
-                } else {
-                    "primary_span"
-                };
+                let mut span_attrs = vec![];
+                if kind_stats.has_multipart_suggestion {
+                    span_attrs.push("suggestion_part");
+                }
+                if !kind_stats.all_multipart_suggestions {
+                    span_attrs.push("primary_span")
+                }
                 diag.help(format!(
-                    "only `{span_attr}`, `applicability` and `skip_arg` are valid field attributes",
+                    "only `{}`, `applicability` and `skip_arg` are valid field attributes",
+                    span_attrs.join(", ")
                 ))
             }),
         }
     }
 
     pub fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
-        let Some((kind, slug)) = self.identify_kind()? else {
+        let kind_slugs = self.identify_kind()?;
+        if kind_slugs.is_empty() {
             throw_span_err!(
                 self.variant.ast().ident.span().unwrap(),
                 "subdiagnostic kind not specified"
             );
         };
 
-        let init = match &kind {
-            SubdiagnosticKind::Label
-            | SubdiagnosticKind::Note
-            | SubdiagnosticKind::Help
-            | SubdiagnosticKind::Warn
-            | SubdiagnosticKind::Suggestion { .. } => quote! {},
-            SubdiagnosticKind::MultipartSuggestion { .. } => {
-                quote! { let mut suggestions = Vec::new(); }
-            }
+        let kind_stats: KindsStatistics = kind_slugs.iter().map(|(kind, _slug)| kind).collect();
+
+        let init = if kind_stats.has_multipart_suggestion {
+            quote! { let mut suggestions = Vec::new(); }
+        } else {
+            quote! {}
         };
 
         let attr_args: TokenStream = self
@@ -612,7 +632,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
             .bindings()
             .iter()
             .filter(|binding| !binding.ast().attrs.is_empty())
-            .map(|binding| self.generate_field_attr_code(binding, &kind))
+            .map(|binding| self.generate_field_attr_code(binding, kind_stats))
             .collect();
 
         let span_field = self.span_field.as_ref().map(|(span, _)| span);
@@ -622,48 +642,52 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
         );
 
         let diag = &self.diag;
-        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 } => {
-                if let Some(span) = span_field {
-                    let style = suggestion_kind.to_suggestion_style();
-
-                    quote! { #diag.#name(#span, #message, #code, #applicability, #style); }
-                } else {
-                    span_err(self.span, "suggestion without `#[primary_span]` field").emit();
-                    quote! { unreachable!(); }
-                }
-            }
-            SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => {
-                if !self.has_suggestion_parts {
-                    span_err(
-                        self.span,
-                        "multipart suggestion without any `#[suggestion_part(...)]` fields",
-                    )
-                    .emit();
+        let mut calls = TokenStream::new();
+        for (kind, slug) in kind_slugs {
+            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 } => {
+                    if let Some(span) = span_field {
+                        let style = suggestion_kind.to_suggestion_style();
+
+                        quote! { #diag.#name(#span, #message, #code, #applicability, #style); }
+                    } else {
+                        span_err(self.span, "suggestion without `#[primary_span]` field").emit();
+                        quote! { unreachable!(); }
+                    }
                 }
+                SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => {
+                    if !self.has_suggestion_parts {
+                        span_err(
+                            self.span,
+                            "multipart suggestion without any `#[suggestion_part(...)]` fields",
+                        )
+                        .emit();
+                    }
 
-                let style = suggestion_kind.to_suggestion_style();
+                    let style = suggestion_kind.to_suggestion_style();
 
-                quote! { #diag.#name(#message, suggestions, #applicability, #style); }
-            }
-            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!(); }
+                    quote! { #diag.#name(#message, suggestions, #applicability, #style); }
                 }
-            }
-            _ => {
-                if let Some(span) = span_field {
-                    quote! { #diag.#name(#span, #message); }
-                } else {
-                    quote! { #diag.#name(#message); }
+                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!(); }
+                    }
                 }
-            }
-        };
+                _ => {
+                    if let Some(span) = span_field {
+                        quote! { #diag.#name(#span, #message); }
+                    } else {
+                        quote! { #diag.#name(#message); }
+                    }
+                }
+            };
+            calls.extend(call);
+        }
 
         let plain_args: TokenStream = self
             .variant
@@ -676,7 +700,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
         Ok(quote! {
             #init
             #attr_args
-            #call
+            #calls
             #plain_args
         })
     }