summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_macros/src/diagnostics/utils.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:11:38 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:13:23 +0000
commit20431706a863f92cb37dc512fef6e48d192aaf2c (patch)
tree2867f13f5fd5437ba628c67d7f87309ccadcd286 /compiler/rustc_macros/src/diagnostics/utils.rs
parentReleasing progress-linux version 1.65.0+dfsg1-2~progress7.99u1. (diff)
downloadrustc-20431706a863f92cb37dc512fef6e48d192aaf2c.tar.xz
rustc-20431706a863f92cb37dc512fef6e48d192aaf2c.zip
Merging upstream version 1.66.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_macros/src/diagnostics/utils.rs')
-rw-r--r--compiler/rustc_macros/src/diagnostics/utils.rs421
1 files changed, 405 insertions, 16 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs
index ad9ecd39b..374c795d0 100644
--- a/compiler/rustc_macros/src/diagnostics/utils.rs
+++ b/compiler/rustc_macros/src/diagnostics/utils.rs
@@ -1,11 +1,31 @@
-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 proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote, ToTokens};
+use std::cell::RefCell;
use std::collections::{BTreeSet, HashMap};
+use std::fmt;
use std::str::FromStr;
-use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
-use synstructure::{BindingInfo, Structure};
+use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple};
+use syn::{MetaList, MetaNameValue, NestedMeta, Path};
+use synstructure::{BindingInfo, VariantInfo};
+
+use super::error::invalid_nested_attr;
+
+thread_local! {
+ pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0);
+}
+
+/// Returns an ident of the form `__code_N` where `N` is incremented once with every call.
+pub(crate) fn new_code_ident() -> syn::Ident {
+ CODE_IDENT_COUNT.with(|count| {
+ let ident = format_ident!("__code_{}", *count.borrow());
+ *count.borrow_mut() += 1;
+ ident
+ })
+}
/// Checks whether the type name of `ty` matches `name`.
///
@@ -135,6 +155,15 @@ impl<'ty> FieldInnerTy<'ty> {
unreachable!();
}
+ /// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e.
+ /// that cloning might be required for values moved in the loop body).
+ pub(crate) fn will_iterate(&self) -> bool {
+ match self {
+ FieldInnerTy::Vec(..) => true,
+ FieldInnerTy::Option(..) | FieldInnerTy::None => false,
+ }
+ }
+
/// Returns `Option` containing inner type if there is one.
pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
match self {
@@ -172,13 +201,17 @@ pub(crate) struct FieldInfo<'a> {
/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
/// for error reporting if they are set more than once.
pub(crate) trait SetOnce<T> {
- fn set_once(&mut self, _: (T, Span));
+ fn set_once(&mut self, value: T, span: Span);
fn value(self) -> Option<T>;
+ fn value_ref(&self) -> Option<&T>;
}
-impl<T> SetOnce<T> for Option<(T, Span)> {
- fn set_once(&mut self, (value, span): (T, Span)) {
+/// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`].
+pub(super) type SpannedOption<T> = Option<(T, Span)>;
+
+impl<T> SetOnce<T> for SpannedOption<T> {
+ fn set_once(&mut self, value: T, span: Span) {
match self {
None => {
*self = Some((value, span));
@@ -194,8 +227,14 @@ impl<T> SetOnce<T> for Option<(T, Span)> {
fn value(self) -> Option<T> {
self.map(|(v, _)| v)
}
+
+ fn value_ref(&self) -> Option<&T> {
+ self.as_ref().map(|(v, _)| v)
+ }
}
+pub(super) type FieldMap = HashMap<String, TokenStream>;
+
pub(crate) trait HasFieldMap {
/// Returns the binding for the field with the given name, if it exists on the type.
fn get_field_binding(&self, field: &String) -> Option<&TokenStream>;
@@ -303,6 +342,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,
@@ -345,17 +385,366 @@ impl quote::ToTokens for Applicability {
/// Build the mapping of field names to fields. This allows attributes to peek values from
/// other fields.
-pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap<String, TokenStream> {
- let mut fields_map = HashMap::new();
-
- let ast = structure.ast();
- 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(), quote! { &self.#ident });
+pub(super) fn build_field_mapping<'v>(variant: &VariantInfo<'v>) -> HashMap<String, TokenStream> {
+ let mut fields_map = FieldMap::new();
+ for binding in variant.bindings() {
+ if let Some(ident) = &binding.ast().ident {
+ fields_map.insert(ident.to_string(), quote! { #binding });
+ }
+ }
+ fields_map
+}
+
+#[derive(Copy, Clone, Debug)]
+pub(super) enum AllowMultipleAlternatives {
+ No,
+ Yes,
+}
+
+/// Constructs the `format!()` invocation(s) necessary for a `#[suggestion*(code = "foo")]` or
+/// `#[suggestion*(code("foo", "bar"))]` attribute field
+pub(super) fn build_suggestion_code(
+ code_field: &Ident,
+ meta: &Meta,
+ fields: &impl HasFieldMap,
+ allow_multiple: AllowMultipleAlternatives,
+) -> TokenStream {
+ let values = match meta {
+ // `code = "foo"`
+ Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => vec![s],
+ // `code("foo", "bar")`
+ Meta::List(MetaList { nested, .. }) => {
+ if let AllowMultipleAlternatives::No = allow_multiple {
+ span_err(
+ meta.span().unwrap(),
+ "expected exactly one string literal for `code = ...`",
+ )
+ .emit();
+ vec![]
+ } else if nested.is_empty() {
+ span_err(
+ meta.span().unwrap(),
+ "expected at least one string literal for `code(...)`",
+ )
+ .emit();
+ vec![]
+ } else {
+ nested
+ .into_iter()
+ .filter_map(|item| {
+ if let NestedMeta::Lit(syn::Lit::Str(s)) = item {
+ Some(s)
+ } else {
+ span_err(
+ item.span().unwrap(),
+ "`code(...)` must contain only string literals",
+ )
+ .emit();
+ None
+ }
+ })
+ .collect()
}
}
+ _ => {
+ span_err(
+ meta.span().unwrap(),
+ r#"`code = "..."`/`code(...)` must contain only string literals"#,
+ )
+ .emit();
+ vec![]
+ }
+ };
+
+ if let AllowMultipleAlternatives::Yes = allow_multiple {
+ let formatted_strings: Vec<_> = values
+ .into_iter()
+ .map(|value| fields.build_format(&value.value(), value.span()))
+ .collect();
+ quote! { let #code_field = [#(#formatted_strings),*].into_iter(); }
+ } else if let [value] = values.as_slice() {
+ let formatted_str = fields.build_format(&value.value(), value.span());
+ quote! { let #code_field = #formatted_str; }
+ } else {
+ // error handled previously
+ quote! { let #code_field = String::new(); }
}
+}
- 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>,
+ /// Identifier for variable used for formatted code, e.g. `___code_0`. Enables separation
+ /// of formatting and diagnostic emission so that `set_arg` calls can happen in-between..
+ code_field: syn::Ident,
+ /// Initialization logic for `code_field`'s variable, e.g.
+ /// `let __formatted_code = /* whatever */;`
+ code_init: 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<Option<(SubdiagnosticKind, Option<Path>)>, DiagnosticDeriveError> {
+ // Always allow documentation comments.
+ if is_doc_comment(attr) {
+ return Ok(None);
+ }
+
+ 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_field: new_code_ident(),
+ code_init: 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(Some((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 string_value = match meta {
+ Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => Some(value),
+
+ Meta::Path(_) => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
+ diag.help("a diagnostic slug must be the first argument to the attribute")
+ }),
+ _ => None,
+ };
+
+ match (nested_name, &mut kind) {
+ ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
+ let code_init = build_suggestion_code(
+ code_field,
+ meta,
+ fields,
+ AllowMultipleAlternatives::Yes,
+ );
+ code.set_once(code_init, span);
+ }
+ (
+ "applicability",
+ SubdiagnosticKind::Suggestion { ref mut applicability, .. }
+ | SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. },
+ ) => {
+ let Some(value) = string_value else {
+ invalid_nested_attr(attr, &nested_attr).emit();
+ continue;
+ };
+
+ 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 { ref code_field, ref mut code_init, .. } => {
+ *code_init = if let Some(init) = code.value() {
+ init
+ } else {
+ span_err(span, "suggestion without `code = \"...\"`").emit();
+ quote! { let #code_field = std::iter::empty(); }
+ };
+ }
+ SubdiagnosticKind::Label
+ | SubdiagnosticKind::Note
+ | SubdiagnosticKind::Help
+ | SubdiagnosticKind::Warn
+ | SubdiagnosticKind::MultipartSuggestion { .. } => {}
+ }
+
+ Ok(Some((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, "suggestions_with_style"),
+ SubdiagnosticKind::MultipartSuggestion { .. } => {
+ write!(f, "multipart_suggestion_with_style")
+ }
+ }
+ }
+
+ fn span(&self) -> Option<proc_macro2::Span> {
+ None
+ }
+}
+
+/// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
+/// call (like `span_label`).
+pub(super) fn should_generate_set_arg(field: &Field) -> bool {
+ field.attrs.is_empty()
+}
+
+pub(super) fn is_doc_comment(attr: &Attribute) -> bool {
+ attr.path.segments.last().unwrap().ident.to_string() == "doc"
}