diff options
Diffstat (limited to 'compiler/rustc_macros/src/diagnostics/utils.rs')
-rw-r--r-- | compiler/rustc_macros/src/diagnostics/utils.rs | 232 |
1 files changed, 126 insertions, 106 deletions
diff --git a/compiler/rustc_macros/src/diagnostics/utils.rs b/compiler/rustc_macros/src/diagnostics/utils.rs index 27b8f676f..b9b09c662 100644 --- a/compiler/rustc_macros/src/diagnostics/utils.rs +++ b/compiler/rustc_macros/src/diagnostics/utils.rs @@ -1,5 +1,5 @@ use crate::diagnostics::error::{ - span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError, + span_err, throw_invalid_attr, throw_span_err, DiagnosticDeriveError, }; use proc_macro::Span; use proc_macro2::{Ident, TokenStream}; @@ -8,11 +8,13 @@ use std::cell::RefCell; use std::collections::{BTreeSet, HashMap}; use std::fmt; use std::str::FromStr; +use syn::meta::ParseNestedMeta; +use syn::punctuated::Punctuated; +use syn::{parenthesized, LitStr, Path, Token}; use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple}; -use syn::{MetaList, MetaNameValue, NestedMeta, Path}; use synstructure::{BindingInfo, VariantInfo}; -use super::error::{invalid_attr, invalid_nested_attr}; +use super::error::invalid_attr; thread_local! { pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0); @@ -50,13 +52,18 @@ pub(crate) fn type_is_unit(ty: &Type) -> bool { if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false } } +/// Checks whether the type `ty` is `bool`. +pub(crate) fn type_is_bool(ty: &Type) -> bool { + type_matches_path(ty, &["bool"]) +} + /// Reports a type error for field with `attr`. pub(crate) fn report_type_error( attr: &Attribute, ty_name: &str, ) -> Result<!, DiagnosticDeriveError> { - let name = attr.path.segments.last().unwrap().ident.to_string(); - let meta = attr.parse_meta()?; + let name = attr.path().segments.last().unwrap().ident.to_string(); + let meta = &attr.meta; throw_span_err!( attr.span().unwrap(), @@ -192,6 +199,11 @@ impl<'ty> FieldInnerTy<'ty> { #inner } }, + FieldInnerTy::Plain(t) if type_is_bool(t) => quote! { + if #binding { + #inner + } + }, FieldInnerTy::Plain(..) => quote! { #inner }, } } @@ -408,59 +420,62 @@ pub(super) enum AllowMultipleAlternatives { Yes, } +fn parse_suggestion_values( + nested: ParseNestedMeta<'_>, + allow_multiple: AllowMultipleAlternatives, +) -> syn::Result<Vec<LitStr>> { + let values = if let Ok(val) = nested.value() { + vec![val.parse()?] + } else { + let content; + parenthesized!(content in nested.input); + + if let AllowMultipleAlternatives::No = allow_multiple { + span_err( + nested.input.span().unwrap(), + "expected exactly one string literal for `code = ...`", + ) + .emit(); + vec![] + } else { + let literals = Punctuated::<LitStr, Token![,]>::parse_terminated(&content); + + match literals { + Ok(p) if p.is_empty() => { + span_err( + content.span().unwrap(), + "expected at least one string literal for `code(...)`", + ) + .emit(); + vec![] + } + Ok(p) => p.into_iter().collect(), + Err(_) => { + span_err( + content.span().unwrap(), + "`code(...)` must contain only string literals", + ) + .emit(); + vec![] + } + } + } + }; + + Ok(values) +} + /// 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, + nested: ParseNestedMeta<'_>, 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![] - } + let values = match parse_suggestion_values(nested, allow_multiple) { + Ok(x) => x, + Err(e) => return e.into_compile_error(), }; if let AllowMultipleAlternatives::Yes = allow_multiple { @@ -591,11 +606,9 @@ impl SubdiagnosticKind { let span = attr.span().unwrap(); - let name = attr.path.segments.last().unwrap().ident.to_string(); + 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, @@ -608,7 +621,7 @@ impl SubdiagnosticKind { name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix) { if suggestion_kind != SuggestionKind::Normal { - invalid_attr(attr, &meta) + invalid_attr(attr) .help(format!( r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"# )) @@ -625,7 +638,7 @@ impl SubdiagnosticKind { name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix) { if suggestion_kind != SuggestionKind::Normal { - invalid_attr(attr, &meta) + invalid_attr(attr) .help(format!( r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"# )) @@ -637,16 +650,16 @@ impl SubdiagnosticKind { applicability: None, } } else { - throw_invalid_attr!(attr, &meta); + throw_invalid_attr!(attr); } } }; - let nested = match meta { - Meta::List(MetaList { ref nested, .. }) => { + let list = match &attr.meta { + Meta::List(list) => { // An attribute with properties, such as `#[suggestion(code = "...")]` or // `#[error(some::slug)]` - nested + list } Meta::Path(_) => { // An attribute without a slug or other properties, such as `#[note]` - return @@ -668,69 +681,68 @@ impl SubdiagnosticKind { } } _ => { - throw_invalid_attr!(attr, &meta) + throw_invalid_attr!(attr) } }; let mut code = None; let mut suggestion_kind = None; - let mut nested_iter = nested.into_iter().peekable(); + let mut first = true; + let mut slug = None; - // 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; + list.parse_nested_meta(|nested| { + if nested.input.is_empty() || nested.input.peek(Token![,]) { + if first { + slug = Some(nested.path); + } else { + span_err(nested.input.span().unwrap(), "a diagnostic slug must be the first argument to the attribute").emit(); } - }; - let span = meta.span().unwrap(); - let nested_name = meta.path().segments.last().unwrap().ident.to_string(); + first = false; + return Ok(()); + } + + first = false; + + let nested_name = nested.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), + let path_span = nested.path.span().unwrap(); + let val_span = nested.input.span().unwrap(); - Meta::Path(_) => throw_invalid_nested_attr!(attr, nested_attr, |diag| { - diag.help("a diagnostic slug must be the first argument to the attribute") - }), - _ => None, - }; + macro_rules! get_string { + () => {{ + let Ok(value) = nested.value().and_then(|x| x.parse::<LitStr>()) else { + span_err(val_span, "expected `= \"xxx\"`").emit(); + return Ok(()); + }; + value + }}; + } + + let mut has_errors = false; + let input = nested.input; match (nested_name, &mut kind) { ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => { let code_init = build_suggestion_code( code_field, - meta, + nested, fields, AllowMultipleAlternatives::Yes, ); - code.set_once(code_init, span); + code.set_once(code_init, path_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 = get_string!(); let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| { - span_err(span, "invalid applicability").emit(); + span_err(value.span().unwrap(), "invalid applicability").emit(); + has_errors = true; Applicability::Unspecified }); applicability.set_once(value, span); @@ -740,15 +752,13 @@ impl SubdiagnosticKind { SubdiagnosticKind::Suggestion { .. } | SubdiagnosticKind::MultipartSuggestion { .. }, ) => { - let Some(value) = string_value else { - invalid_nested_attr(attr, nested_attr).emit(); - continue; - }; + let value = get_string!(); let value = value.value().parse().unwrap_or_else(|()| { span_err(value.span().unwrap(), "invalid suggestion style") .help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`") .emit(); + has_errors = true; SuggestionKind::Normal }); @@ -757,22 +767,32 @@ impl SubdiagnosticKind { // Invalid nested attribute (_, SubdiagnosticKind::Suggestion { .. }) => { - invalid_nested_attr(attr, nested_attr) + span_err(path_span, "invalid nested attribute") .help( "only `style`, `code` and `applicability` are valid nested attributes", ) .emit(); + has_errors = true; } (_, SubdiagnosticKind::MultipartSuggestion { .. }) => { - invalid_nested_attr(attr, nested_attr) + span_err(path_span, "invalid nested attribute") .help("only `style` and `applicability` are valid nested attributes") - .emit() + .emit(); + has_errors = true; } _ => { - invalid_nested_attr(attr, nested_attr).emit(); + span_err(path_span, "invalid nested attribute").emit(); + has_errors = true; } } - } + + if has_errors { + // Consume the rest of the input to avoid spamming errors + let _ = input.parse::<TokenStream>(); + } + + Ok(()) + })?; match kind { SubdiagnosticKind::Suggestion { @@ -835,5 +855,5 @@ pub(super) fn should_generate_set_arg(field: &Field) -> bool { } pub(super) fn is_doc_comment(attr: &Attribute) -> bool { - attr.path.segments.last().unwrap().ident == "doc" + attr.path().segments.last().unwrap().ident == "doc" } |