macros: use typed identifiers in subdiag derive
As in the diagnostic derive, using typed identifiers in the subdiagnostic derive improves the diagnostics of using the subdiagnostic derive as Fluent messages will be confirmed to exist at compile-time. Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
@@ -72,12 +72,12 @@ pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
|
||||
/// ```ignore (rust)
|
||||
/// #[derive(SessionSubdiagnostic)]
|
||||
/// pub enum ExpectedIdentifierLabel<'tcx> {
|
||||
/// #[label(slug = "parser-expected-identifier")]
|
||||
/// #[label(parser::expected_identifier)]
|
||||
/// WithoutFound {
|
||||
/// #[primary_span]
|
||||
/// span: Span,
|
||||
/// }
|
||||
/// #[label(slug = "parser-expected-identifier-found")]
|
||||
/// #[label(parser::expected_identifier_found)]
|
||||
/// WithFound {
|
||||
/// #[primary_span]
|
||||
/// span: Span,
|
||||
@@ -86,7 +86,7 @@ pub fn session_diagnostic_derive(s: Structure<'_>) -> TokenStream {
|
||||
/// }
|
||||
///
|
||||
/// #[derive(SessionSubdiagnostic)]
|
||||
/// #[suggestion_verbose(slug = "parser-raw-identifier")]
|
||||
/// #[suggestion_verbose(parser::raw_identifier)]
|
||||
/// pub struct RawIdentifierSuggestion<'tcx> {
|
||||
/// #[primary_span]
|
||||
/// span: Span,
|
||||
|
||||
@@ -13,7 +13,7 @@ use quote::{format_ident, quote};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::str::FromStr;
|
||||
use syn::{spanned::Spanned, Meta, MetaList, MetaNameValue};
|
||||
use syn::{parse_quote, spanned::Spanned, Meta, MetaList, MetaNameValue, NestedMeta, Path};
|
||||
use synstructure::{BindingInfo, Structure, VariantInfo};
|
||||
|
||||
/// Which kind of suggestion is being created?
|
||||
@@ -194,8 +194,8 @@ struct SessionSubdiagnosticDeriveBuilder<'a> {
|
||||
kind: Option<(SubdiagnosticKind, proc_macro::Span)>,
|
||||
|
||||
/// Slug of the subdiagnostic - corresponds to the Fluent identifier for the message - from the
|
||||
/// `#[kind(slug = "...")]` attribute on the type or variant.
|
||||
slug: Option<(String, proc_macro::Span)>,
|
||||
/// `#[kind(slug)]` attribute on the type or variant.
|
||||
slug: Option<(Path, proc_macro::Span)>,
|
||||
/// If a suggestion, the code to suggest as a replacement - from the `#[kind(code = "...")]`
|
||||
/// attribute on the type or variant.
|
||||
code: Option<(TokenStream, proc_macro::Span)>,
|
||||
@@ -224,9 +224,34 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
|
||||
let meta = attr.parse_meta()?;
|
||||
let kind = match meta {
|
||||
Meta::List(MetaList { ref nested, .. }) => {
|
||||
for nested_attr in nested {
|
||||
let mut nested_iter = nested.into_iter();
|
||||
if let Some(nested_attr) = nested_iter.next() {
|
||||
match nested_attr {
|
||||
NestedMeta::Meta(Meta::Path(path)) => {
|
||||
self.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 {
|
||||
syn::NestedMeta::Meta(ref meta) => meta,
|
||||
NestedMeta::Meta(ref meta) => meta,
|
||||
_ => throw_invalid_nested_attr!(attr, &nested_attr),
|
||||
};
|
||||
|
||||
@@ -241,7 +266,6 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
|
||||
let formatted_str = self.build_format(&s.value(), s.span());
|
||||
self.code.set_once((formatted_str, span));
|
||||
}
|
||||
"slug" => self.slug.set_once((s.value(), span)),
|
||||
"applicability" => {
|
||||
let value = match Applicability::from_str(&s.value()) {
|
||||
Ok(v) => v,
|
||||
@@ -253,11 +277,23 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
|
||||
self.applicability.set_once((quote! { #value }, span));
|
||||
}
|
||||
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
|
||||
diag.help("only `code`, `slug` and `applicability` are valid nested attributes")
|
||||
diag.help(
|
||||
"only `code` and `applicability` are valid nested \
|
||||
attributes",
|
||||
)
|
||||
}),
|
||||
}
|
||||
}
|
||||
_ => throw_invalid_nested_attr!(attr, &nested_attr),
|
||||
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
|
||||
if matches!(meta, Meta::Path(_)) {
|
||||
diag.help(
|
||||
"a diagnostic slug must be the first argument to the \
|
||||
attribute",
|
||||
)
|
||||
} else {
|
||||
diag
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,10 +317,27 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
|
||||
);
|
||||
}
|
||||
|
||||
if matches!(
|
||||
kind,
|
||||
SubdiagnosticKind::Label | SubdiagnosticKind::Help | SubdiagnosticKind::Note
|
||||
) && self.applicability.is_some()
|
||||
{
|
||||
throw_span_err!(
|
||||
span,
|
||||
&format!(
|
||||
"`applicability` is not a valid nested attribute of a `{}` attribute",
|
||||
name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if self.slug.is_none() {
|
||||
throw_span_err!(
|
||||
span,
|
||||
&format!("`slug` must be set in a `#[{}(...)]` attribute", name)
|
||||
&format!(
|
||||
"diagnostic slug must be first argument of a `#[{}(...)]` attribute",
|
||||
name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -335,7 +388,10 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
|
||||
return Ok(quote! {});
|
||||
}
|
||||
_ => throw_invalid_attr!(attr, &meta, |diag| {
|
||||
diag.help("only `primary_span`, `applicability` and `skip_arg` are valid field attributes")
|
||||
diag.help(
|
||||
"only `primary_span`, `applicability` and `skip_arg` are valid field \
|
||||
attributes",
|
||||
)
|
||||
}),
|
||||
},
|
||||
_ => throw_invalid_attr!(attr, &meta),
|
||||
@@ -375,7 +431,11 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
|
||||
}
|
||||
|
||||
// Missing slug errors will already have been reported.
|
||||
let slug = self.slug.as_ref().map(|(slug, _)| &**slug).unwrap_or("missing-slug");
|
||||
let slug = self
|
||||
.slug
|
||||
.as_ref()
|
||||
.map(|(slug, _)| slug.clone())
|
||||
.unwrap_or_else(|| parse_quote! { you::need::to::specify::a::slug });
|
||||
let code = match self.code.as_ref() {
|
||||
Some((code, _)) => Some(quote! { #code }),
|
||||
None if is_suggestion => {
|
||||
@@ -397,7 +457,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
|
||||
|
||||
let diag = &self.diag;
|
||||
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
|
||||
let message = quote! { rustc_errors::SubdiagnosticMessage::message(#slug) };
|
||||
let message = quote! { rustc_errors::fluent::#slug };
|
||||
let call = if matches!(kind, SubdiagnosticKind::Suggestion(..)) {
|
||||
if let Some(span) = span_field {
|
||||
quote! { #diag.#name(#span, #message, #code, #applicability); }
|
||||
|
||||
Reference in New Issue
Block a user