Extract subdiagnostic attribute parsing
This commit is contained in:
parent
d4a1a6f698
commit
e7251cc441
4 changed files with 403 additions and 316 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue