Extract subdiagnostic attribute parsing
This commit is contained in:
parent
d4a1a6f698
commit
e7251cc441
4 changed files with 403 additions and 316 deletions
|
@ -4,100 +4,16 @@ use crate::diagnostics::error::{
|
|||
span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
|
||||
};
|
||||
use crate::diagnostics::utils::{
|
||||
report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
|
||||
Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
|
||||
report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span, FieldInfo,
|
||||
FieldInnerTy, HasFieldMap, SetOnce,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path};
|
||||
use synstructure::{BindingInfo, Structure, VariantInfo};
|
||||
|
||||
use super::utils::SpannedOption;
|
||||
|
||||
/// Which kind of suggestion is being created?
|
||||
#[derive(Clone, Copy)]
|
||||
enum SubdiagnosticSuggestionKind {
|
||||
/// `#[suggestion]`
|
||||
Normal,
|
||||
/// `#[suggestion_short]`
|
||||
Short,
|
||||
/// `#[suggestion_hidden]`
|
||||
Hidden,
|
||||
/// `#[suggestion_verbose]`
|
||||
Verbose,
|
||||
}
|
||||
|
||||
impl FromStr for SubdiagnosticSuggestionKind {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"" => Ok(SubdiagnosticSuggestionKind::Normal),
|
||||
"_short" => Ok(SubdiagnosticSuggestionKind::Short),
|
||||
"_hidden" => Ok(SubdiagnosticSuggestionKind::Hidden),
|
||||
"_verbose" => Ok(SubdiagnosticSuggestionKind::Verbose),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubdiagnosticSuggestionKind {
|
||||
pub fn to_suggestion_style(&self) -> TokenStream {
|
||||
match self {
|
||||
SubdiagnosticSuggestionKind::Normal => {
|
||||
quote! { rustc_errors::SuggestionStyle::ShowCode }
|
||||
}
|
||||
SubdiagnosticSuggestionKind::Short => {
|
||||
quote! { rustc_errors::SuggestionStyle::HideCodeInline }
|
||||
}
|
||||
SubdiagnosticSuggestionKind::Hidden => {
|
||||
quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
|
||||
}
|
||||
SubdiagnosticSuggestionKind::Verbose => {
|
||||
quote! { rustc_errors::SuggestionStyle::ShowAlways }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Which kind of subdiagnostic is being created from a variant?
|
||||
#[derive(Clone)]
|
||||
enum SubdiagnosticKind {
|
||||
/// `#[label(...)]`
|
||||
Label,
|
||||
/// `#[note(...)]`
|
||||
Note,
|
||||
/// `#[help(...)]`
|
||||
Help,
|
||||
/// `#[warning(...)]`
|
||||
Warn,
|
||||
/// `#[suggestion{,_short,_hidden,_verbose}]`
|
||||
Suggestion { suggestion_kind: SubdiagnosticSuggestionKind, code: TokenStream },
|
||||
/// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
|
||||
MultipartSuggestion { suggestion_kind: SubdiagnosticSuggestionKind },
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
use super::utils::{SpannedOption, SubdiagnosticKind};
|
||||
|
||||
/// The central struct for constructing the `add_to_diagnostic` method from an annotated struct.
|
||||
pub(crate) struct SubdiagnosticDerive<'a> {
|
||||
|
@ -198,8 +114,8 @@ struct SubdiagnosticDeriveBuilder<'a> {
|
|||
|
||||
/// Identifier for the binding to the `#[primary_span]` field.
|
||||
span_field: SpannedOption<proc_macro2::Ident>,
|
||||
/// If a suggestion, the identifier for the binding to the `#[applicability]` field or a
|
||||
/// `rustc_errors::Applicability::*` variant directly.
|
||||
|
||||
/// The binding to the `#[applicability]` field, if present.
|
||||
applicability: SpannedOption<TokenStream>,
|
||||
|
||||
/// Set to true when a `#[suggestion_part]` field is encountered, used to generate an error
|
||||
|
@ -219,6 +135,7 @@ struct KindsStatistics {
|
|||
has_multipart_suggestion: bool,
|
||||
all_multipart_suggestions: bool,
|
||||
has_normal_suggestion: bool,
|
||||
all_applicabilities_static: bool,
|
||||
}
|
||||
|
||||
impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
|
||||
|
@ -227,8 +144,15 @@ impl<'a> FromIterator<&'a SubdiagnosticKind> for KindsStatistics {
|
|||
has_multipart_suggestion: false,
|
||||
all_multipart_suggestions: true,
|
||||
has_normal_suggestion: false,
|
||||
all_applicabilities_static: true,
|
||||
};
|
||||
|
||||
for kind in kinds {
|
||||
if let SubdiagnosticKind::MultipartSuggestion { applicability: None, .. }
|
||||
| SubdiagnosticKind::Suggestion { applicability: None, .. } = kind
|
||||
{
|
||||
ret.all_applicabilities_static = false;
|
||||
}
|
||||
if let SubdiagnosticKind::MultipartSuggestion { .. } = kind {
|
||||
ret.has_multipart_suggestion = true;
|
||||
} else {
|
||||
|
@ -248,129 +172,14 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
|
|||
let mut kind_slugs = vec![];
|
||||
|
||||
for attr in self.variant.ast().attrs {
|
||||
let span = attr.span().unwrap();
|
||||
let (kind, slug) = SubdiagnosticKind::from_attr(attr, self)?;
|
||||
|
||||
let name = attr.path.segments.last().unwrap().ident.to_string();
|
||||
let name = name.as_str();
|
||||
let Some(slug) = slug else {
|
||||
let name = attr.path.segments.last().unwrap().ident.to_string();
|
||||
let name = name.as_str();
|
||||
|
||||
let meta = attr.parse_meta()?;
|
||||
let Meta::List(MetaList { ref nested, .. }) = meta else {
|
||||
throw_invalid_attr!(attr, &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, code: TokenStream::new() }
|
||||
} else if let Some(suggestion_kind) =
|
||||
name.strip_prefix("multipart_suggestion").and_then(|s| s.parse().ok())
|
||||
{
|
||||
SubdiagnosticKind::MultipartSuggestion { suggestion_kind }
|
||||
} else {
|
||||
throw_invalid_attr!(attr, &meta);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut slug = None;
|
||||
let mut code = None;
|
||||
|
||||
let mut nested_iter = nested.into_iter();
|
||||
if let Some(nested_attr) = nested_iter.next() {
|
||||
match nested_attr {
|
||||
NestedMeta::Meta(Meta::Path(path)) => {
|
||||
slug.set_once(path.clone(), span);
|
||||
}
|
||||
NestedMeta::Meta(meta @ Meta::NameValue(_))
|
||||
if matches!(
|
||||
meta.path().segments.last().unwrap().ident.to_string().as_str(),
|
||||
"code" | "applicability"
|
||||
) =>
|
||||
{
|
||||
// Don't error for valid follow-up attributes.
|
||||
}
|
||||
nested_attr => {
|
||||
throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
|
||||
diag.help(
|
||||
"first argument of the attribute should be the diagnostic \
|
||||
slug",
|
||||
)
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
for nested_attr in nested_iter {
|
||||
let meta = match nested_attr {
|
||||
NestedMeta::Meta(ref meta) => meta,
|
||||
_ => throw_invalid_nested_attr!(attr, &nested_attr),
|
||||
};
|
||||
|
||||
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")
|
||||
}),
|
||||
_ => throw_invalid_nested_attr!(attr, &nested_attr),
|
||||
};
|
||||
|
||||
match nested_name {
|
||||
"code" => {
|
||||
if matches!(kind, SubdiagnosticKind::Suggestion { .. }) {
|
||||
let formatted_str = self.build_format(&value.value(), value.span());
|
||||
code.set_once(formatted_str, span);
|
||||
} else {
|
||||
span_err(
|
||||
span,
|
||||
&format!(
|
||||
"`code` is not a valid nested attribute of a `{}` attribute",
|
||||
name
|
||||
),
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
"applicability" => {
|
||||
if matches!(
|
||||
kind,
|
||||
SubdiagnosticKind::Suggestion { .. }
|
||||
| SubdiagnosticKind::MultipartSuggestion { .. }
|
||||
) {
|
||||
let value =
|
||||
Applicability::from_str(&value.value()).unwrap_or_else(|()| {
|
||||
span_err(span, "invalid applicability").emit();
|
||||
Applicability::Unspecified
|
||||
});
|
||||
self.applicability.set_once(quote! { #value }, span);
|
||||
} else {
|
||||
span_err(
|
||||
span,
|
||||
&format!(
|
||||
"`applicability` is not a valid nested attribute of a `{}` attribute",
|
||||
name
|
||||
)
|
||||
).emit();
|
||||
}
|
||||
}
|
||||
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
|
||||
diag.help("only `code` and `applicability` are valid nested attributes")
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
let Some((slug, _)) = slug else {
|
||||
throw_span_err!(
|
||||
span,
|
||||
attr.span().unwrap(),
|
||||
&format!(
|
||||
"diagnostic slug must be first argument of a `#[{}(...)]` attribute",
|
||||
name
|
||||
|
@ -378,21 +187,7 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
|
|||
);
|
||||
};
|
||||
|
||||
match kind {
|
||||
SubdiagnosticKind::Suggestion { code: ref mut code_field, .. } => {
|
||||
let Some((code, _)) = code else {
|
||||
throw_span_err!(span, "suggestion without `code = \"...\"`");
|
||||
};
|
||||
*code_field = code;
|
||||
}
|
||||
SubdiagnosticKind::Label
|
||||
| SubdiagnosticKind::Note
|
||||
| SubdiagnosticKind::Help
|
||||
| SubdiagnosticKind::Warn
|
||||
| SubdiagnosticKind::MultipartSuggestion { .. } => {}
|
||||
}
|
||||
|
||||
kind_slugs.push((kind, slug))
|
||||
kind_slugs.push((kind, slug));
|
||||
}
|
||||
|
||||
Ok(kind_slugs)
|
||||
|
@ -510,6 +305,15 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
|
|||
if kind_stats.has_multipart_suggestion || kind_stats.has_normal_suggestion {
|
||||
report_error_if_not_applied_to_applicability(attr, &info)?;
|
||||
|
||||
if kind_stats.all_applicabilities_static {
|
||||
span_err(
|
||||
span,
|
||||
"`#[applicability]` has no effect if all `#[suggestion]`/\
|
||||
`#[multipart_suggestion]` attributes have a static \
|
||||
`applicability = \"...\"`",
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
let binding = info.binding.binding.clone();
|
||||
self.applicability.set_once(quote! { #binding }, span);
|
||||
} else {
|
||||
|
@ -638,11 +442,6 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
|
|||
.collect();
|
||||
|
||||
let span_field = self.span_field.value_ref();
|
||||
let applicability = self
|
||||
.applicability
|
||||
.take()
|
||||
.value()
|
||||
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
|
||||
|
||||
let diag = &self.diag;
|
||||
let mut calls = TokenStream::new();
|
||||
|
@ -650,7 +449,13 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
|
|||
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 } => {
|
||||
SubdiagnosticKind::Suggestion { suggestion_kind, applicability, code } => {
|
||||
let applicability = applicability
|
||||
.value()
|
||||
.map(|a| quote! { #a })
|
||||
.or_else(|| self.applicability.take().value())
|
||||
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
|
||||
|
||||
if let Some(span) = span_field {
|
||||
let style = suggestion_kind.to_suggestion_style();
|
||||
|
||||
|
@ -660,7 +465,13 @@ impl<'a> SubdiagnosticDeriveBuilder<'a> {
|
|||
quote! { unreachable!(); }
|
||||
}
|
||||
}
|
||||
SubdiagnosticKind::MultipartSuggestion { suggestion_kind } => {
|
||||
SubdiagnosticKind::MultipartSuggestion { suggestion_kind, applicability } => {
|
||||
let applicability = applicability
|
||||
.value()
|
||||
.map(|a| quote! { #a })
|
||||
.or_else(|| self.applicability.take().value())
|
||||
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
|
||||
|
||||
if !self.has_suggestion_parts {
|
||||
span_err(
|
||||
self.span,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue