about summary refs log tree commit diff
path: root/compiler/rustc_macros/src/diagnostics/utils.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_macros/src/diagnostics/utils.rs')
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs256
1 files changed, 255 insertions, 1 deletions
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
+    }
+}