mirror of
https://github.com/rust-lang/rust.git
synced 2026-04-27 18:57:42 +03:00
Remove slugs from the #[derive(Diagnostic)] macro
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
#![deny(unused_must_use)]
|
||||
|
||||
use std::cell::RefCell;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use synstructure::Structure;
|
||||
@@ -22,7 +20,6 @@ pub(crate) fn new(structure: Structure<'a>) -> Self {
|
||||
pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
let DiagnosticDerive { mut structure } = self;
|
||||
let kind = DiagnosticDeriveKind::Diagnostic;
|
||||
let messages = RefCell::new(Vec::new());
|
||||
let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
|
||||
let preamble = builder.preamble(variant);
|
||||
let body = builder.body(variant);
|
||||
@@ -30,7 +27,6 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
let Some(message) = builder.primary_message() else {
|
||||
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
|
||||
};
|
||||
messages.borrow_mut().push(message.clone());
|
||||
let message = message.diag_message(Some(variant));
|
||||
|
||||
let init = quote! {
|
||||
@@ -52,9 +48,7 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
});
|
||||
|
||||
// A lifetime of `'a` causes conflicts, but `_sess` is fine.
|
||||
// FIXME(edition_2024): Fix the `keyword_idents_2024` lint to not trigger here?
|
||||
#[allow(keyword_idents_2024)]
|
||||
let mut imp = structure.gen_impl(quote! {
|
||||
structure.gen_impl(quote! {
|
||||
gen impl<'_sess, G> rustc_errors::Diagnostic<'_sess, G> for @Self
|
||||
where G: rustc_errors::EmissionGuarantee
|
||||
{
|
||||
@@ -67,11 +61,7 @@ fn into_diag(
|
||||
#implementation
|
||||
}
|
||||
}
|
||||
});
|
||||
for test in messages.borrow().iter().map(|s| s.generate_test(&structure)) {
|
||||
imp.extend(test);
|
||||
}
|
||||
imp
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +78,6 @@ pub(crate) fn new(structure: Structure<'a>) -> Self {
|
||||
pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
let LintDiagnosticDerive { mut structure } = self;
|
||||
let kind = DiagnosticDeriveKind::LintDiagnostic;
|
||||
let messages = RefCell::new(Vec::new());
|
||||
let implementation = kind.each_variant(&mut structure, |mut builder, variant| {
|
||||
let preamble = builder.preamble(variant);
|
||||
let body = builder.body(variant);
|
||||
@@ -96,7 +85,6 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
let Some(message) = builder.primary_message() else {
|
||||
return DiagnosticDeriveError::ErrorHandled.to_compile_error();
|
||||
};
|
||||
messages.borrow_mut().push(message.clone());
|
||||
let message = message.diag_message(Some(variant));
|
||||
let primary_message = quote! {
|
||||
diag.primary_message(#message);
|
||||
@@ -112,9 +100,7 @@ pub(crate) fn into_tokens(self) -> TokenStream {
|
||||
}
|
||||
});
|
||||
|
||||
// FIXME(edition_2024): Fix the `keyword_idents_2024` lint to not trigger here?
|
||||
#[allow(keyword_idents_2024)]
|
||||
let mut imp = structure.gen_impl(quote! {
|
||||
structure.gen_impl(quote! {
|
||||
gen impl<'__a> rustc_errors::LintDiagnostic<'__a, ()> for @Self {
|
||||
#[track_caller]
|
||||
fn decorate_lint<'__b>(
|
||||
@@ -124,11 +110,6 @@ fn decorate_lint<'__b>(
|
||||
#implementation;
|
||||
}
|
||||
}
|
||||
});
|
||||
for test in messages.borrow().iter().map(|s| s.generate_test(&structure)) {
|
||||
imp.extend(test);
|
||||
}
|
||||
|
||||
imp
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use quote::{format_ident, quote, quote_spanned};
|
||||
use syn::parse::ParseStream;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Attribute, LitStr, Meta, Path, Token, Type, parse_quote};
|
||||
use syn::{Attribute, LitStr, Meta, Path, Token, Type};
|
||||
use synstructure::{BindingInfo, Structure, VariantInfo};
|
||||
|
||||
use super::utils::SubdiagnosticVariant;
|
||||
@@ -109,24 +109,14 @@ impl DiagnosticDeriveVariantBuilder {
|
||||
pub(crate) fn primary_message(&self) -> Option<&Message> {
|
||||
match self.message.as_ref() {
|
||||
None => {
|
||||
span_err(self.span, "diagnostic slug not specified")
|
||||
span_err(self.span, "diagnostic message not specified")
|
||||
.help(
|
||||
"specify the slug as the first argument to the `#[diag(...)]` \
|
||||
attribute, such as `#[diag(hir_analysis_example_error)]`",
|
||||
"specify the message as the first argument to the `#[diag(...)]` \
|
||||
attribute, such as `#[diag(\"Example error\")]`",
|
||||
)
|
||||
.emit();
|
||||
None
|
||||
}
|
||||
Some(Message::Slug(slug))
|
||||
if let Some(Mismatch { slug_name, crate_name, slug_prefix }) =
|
||||
Mismatch::check(slug) =>
|
||||
{
|
||||
span_err(slug.span().unwrap(), "diagnostic slug and crate name do not match")
|
||||
.note(format!("slug is `{slug_name}` but the crate name is `{crate_name}`"))
|
||||
.help(format!("expected a slug starting with `{slug_prefix}_...`"))
|
||||
.emit();
|
||||
None
|
||||
}
|
||||
Some(msg) => Some(msg),
|
||||
}
|
||||
}
|
||||
@@ -177,25 +167,15 @@ fn parse_subdiag_attribute(
|
||||
.help("consider creating a `Subdiagnostic` instead"));
|
||||
}
|
||||
|
||||
// For subdiagnostics without a message specified, insert a placeholder slug
|
||||
let slug = subdiag.slug.unwrap_or_else(|| {
|
||||
Message::Slug(match subdiag.kind {
|
||||
SubdiagnosticKind::Label => parse_quote! { _subdiag::label },
|
||||
SubdiagnosticKind::Note => parse_quote! { _subdiag::note },
|
||||
SubdiagnosticKind::NoteOnce => parse_quote! { _subdiag::note_once },
|
||||
SubdiagnosticKind::Help => parse_quote! { _subdiag::help },
|
||||
SubdiagnosticKind::HelpOnce => parse_quote! { _subdiag::help_once },
|
||||
SubdiagnosticKind::Warn => parse_quote! { _subdiag::warn },
|
||||
SubdiagnosticKind::Suggestion { .. } => parse_quote! { _subdiag::suggestion },
|
||||
SubdiagnosticKind::MultipartSuggestion { .. } => unreachable!(),
|
||||
})
|
||||
});
|
||||
let Some(message) = subdiag.message else {
|
||||
throw_invalid_attr!(attr, |diag| diag.help("subdiagnostic message is missing"))
|
||||
};
|
||||
|
||||
Ok(Some((subdiag.kind, slug, false)))
|
||||
Ok(Some((subdiag.kind, message, false)))
|
||||
}
|
||||
|
||||
/// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
|
||||
/// attributes like `#[diag(..)]`, such as the slug and error code. Generates
|
||||
/// attributes like `#[diag(..)]`, such as the message and error code. Generates
|
||||
/// diagnostic builder calls for setting error code and creating note/help messages.
|
||||
fn generate_structure_code_for_attr(
|
||||
&mut self,
|
||||
@@ -213,9 +193,6 @@ fn generate_structure_code_for_attr(
|
||||
if name == "diag" {
|
||||
let mut tokens = TokenStream::new();
|
||||
attr.parse_args_with(|input: ParseStream<'_>| {
|
||||
let mut input = &*input;
|
||||
let slug_recovery_point = input.fork();
|
||||
|
||||
if input.peek(LitStr) {
|
||||
// Parse an inline message
|
||||
let message = input.parse::<LitStr>()?;
|
||||
@@ -226,15 +203,8 @@ fn generate_structure_code_for_attr(
|
||||
)
|
||||
.emit();
|
||||
}
|
||||
self.message = Some(Message::Inline(message.span(), message.value()));
|
||||
} else {
|
||||
// Parse a slug
|
||||
let slug = input.parse::<Path>()?;
|
||||
if input.is_empty() || input.peek(Token![,]) {
|
||||
self.message = Some(Message::Slug(slug));
|
||||
} else {
|
||||
input = &slug_recovery_point;
|
||||
}
|
||||
self.message =
|
||||
Some(Message { message_span: message.span(), value: message.value() });
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
@@ -248,7 +218,7 @@ fn generate_structure_code_for_attr(
|
||||
if input.peek(Token![,]) {
|
||||
span_err(
|
||||
arg_name.span().unwrap(),
|
||||
"diagnostic slug must be the first argument",
|
||||
"diagnostic message must be the first argument",
|
||||
)
|
||||
.emit();
|
||||
continue;
|
||||
@@ -265,7 +235,7 @@ fn generate_structure_code_for_attr(
|
||||
}
|
||||
_ => {
|
||||
span_err(arg_name.span().unwrap(), "unknown argument")
|
||||
.note("only the `code` parameter is valid after the slug")
|
||||
.note("only the `code` parameter is valid after the message")
|
||||
.emit();
|
||||
}
|
||||
}
|
||||
@@ -276,7 +246,7 @@ fn generate_structure_code_for_attr(
|
||||
return Ok(tokens);
|
||||
}
|
||||
|
||||
let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else {
|
||||
let Some((subdiag, message, _no_span)) = self.parse_subdiag_attribute(attr)? else {
|
||||
// Some attributes aren't errors - like documentation comments - but also aren't
|
||||
// subdiagnostics.
|
||||
return Ok(quote! {});
|
||||
@@ -287,7 +257,7 @@ fn generate_structure_code_for_attr(
|
||||
| SubdiagnosticKind::NoteOnce
|
||||
| SubdiagnosticKind::Help
|
||||
| SubdiagnosticKind::HelpOnce
|
||||
| SubdiagnosticKind::Warn => Ok(self.add_subdiagnostic(&fn_ident, slug, variant)),
|
||||
| SubdiagnosticKind::Warn => Ok(self.add_subdiagnostic(&fn_ident, message, variant)),
|
||||
SubdiagnosticKind::Label | SubdiagnosticKind::Suggestion { .. } => {
|
||||
throw_invalid_attr!(attr, |diag| diag
|
||||
.help("`#[label]` and `#[suggestion]` can only be applied to fields"));
|
||||
@@ -406,7 +376,7 @@ fn generate_inner_field_code(
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let Some((subdiag, slug, _no_span)) = self.parse_subdiag_attribute(attr)? else {
|
||||
let Some((subdiag, message, _no_span)) = self.parse_subdiag_attribute(attr)? else {
|
||||
// Some attributes aren't errors - like documentation comments - but also aren't
|
||||
// subdiagnostics.
|
||||
return Ok(quote! {});
|
||||
@@ -415,7 +385,7 @@ fn generate_inner_field_code(
|
||||
match subdiag {
|
||||
SubdiagnosticKind::Label => {
|
||||
report_error_if_not_applied_to_span(attr, &info)?;
|
||||
Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug, variant))
|
||||
Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, message, variant))
|
||||
}
|
||||
SubdiagnosticKind::Note
|
||||
| SubdiagnosticKind::NoteOnce
|
||||
@@ -426,11 +396,11 @@ fn generate_inner_field_code(
|
||||
if type_matches_path(inner, &["rustc_span", "Span"])
|
||||
|| type_matches_path(inner, &["rustc_span", "MultiSpan"])
|
||||
{
|
||||
Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, slug, variant))
|
||||
Ok(self.add_spanned_subdiagnostic(binding, &fn_ident, message, variant))
|
||||
} else if type_is_unit(inner)
|
||||
|| (matches!(info.ty, FieldInnerTy::Plain(_)) && type_is_bool(inner))
|
||||
{
|
||||
Ok(self.add_subdiagnostic(&fn_ident, slug, variant))
|
||||
Ok(self.add_subdiagnostic(&fn_ident, message, variant))
|
||||
} else {
|
||||
report_type_error(attr, "`Span`, `MultiSpan`, `bool` or `()`")?
|
||||
}
|
||||
@@ -456,7 +426,7 @@ fn generate_inner_field_code(
|
||||
applicability.set_once(quote! { #static_applicability }, span);
|
||||
}
|
||||
|
||||
let message = slug.diag_message(Some(variant));
|
||||
let message = message.diag_message(Some(variant));
|
||||
let applicability = applicability
|
||||
.value()
|
||||
.unwrap_or_else(|| quote! { rustc_errors::Applicability::Unspecified });
|
||||
@@ -477,7 +447,7 @@ fn generate_inner_field_code(
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
|
||||
/// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current message
|
||||
/// and `fluent_attr_identifier`.
|
||||
fn add_spanned_subdiagnostic(
|
||||
&self,
|
||||
@@ -496,7 +466,7 @@ fn add_spanned_subdiagnostic(
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
|
||||
/// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current message
|
||||
/// and `fluent_attr_identifier`.
|
||||
fn add_subdiagnostic(
|
||||
&self,
|
||||
@@ -567,27 +537,3 @@ fn type_err(span: &Span) -> Result<!, DiagnosticDeriveError> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Mismatch {
|
||||
slug_name: String,
|
||||
crate_name: String,
|
||||
slug_prefix: String,
|
||||
}
|
||||
|
||||
impl Mismatch {
|
||||
/// Checks whether the slug starts with the crate name it's in.
|
||||
fn check(slug: &syn::Path) -> Option<Mismatch> {
|
||||
// If this is missing we're probably in a test, so bail.
|
||||
let crate_name = std::env::var("CARGO_CRATE_NAME").ok()?;
|
||||
|
||||
// If we're not in a "rustc_" crate, bail.
|
||||
let Some(("rustc", slug_prefix)) = crate_name.split_once('_') else { return None };
|
||||
|
||||
let slug_name = slug.segments.first()?.ident.to_string();
|
||||
if slug_name.starts_with(slug_prefix) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Mismatch { slug_name, slug_prefix: slug_prefix.to_string(), crate_name })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
use fluent_syntax::ast::{Expression, InlineExpression, Pattern, PatternElement};
|
||||
use proc_macro2::{Span, TokenStream};
|
||||
use quote::quote;
|
||||
use syn::Path;
|
||||
use syn::ext::IdentExt;
|
||||
use synstructure::{Structure, VariantInfo};
|
||||
use synstructure::VariantInfo;
|
||||
|
||||
use crate::diagnostics::error::span_err;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum Message {
|
||||
Slug(Path),
|
||||
Inline(Span, String),
|
||||
pub(crate) struct Message {
|
||||
pub message_span: Span,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
@@ -19,69 +18,9 @@ impl Message {
|
||||
/// The passed `variant` is used to check whether all variables in the message are used.
|
||||
/// For subdiagnostics, we cannot check this.
|
||||
pub(crate) fn diag_message(&self, variant: Option<&VariantInfo<'_>>) -> TokenStream {
|
||||
match self {
|
||||
Message::Slug(slug) => {
|
||||
quote! { crate::fluent_generated::#slug }
|
||||
}
|
||||
Message::Inline(message_span, message) => {
|
||||
verify_fluent_message(*message_span, &message, variant);
|
||||
quote! { rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed(#message)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a `#[test]` that verifies that all referenced variables
|
||||
/// exist on this structure.
|
||||
pub(crate) fn generate_test(&self, structure: &Structure<'_>) -> TokenStream {
|
||||
match self {
|
||||
Message::Slug(slug) => {
|
||||
// FIXME: We can't identify variables in a subdiagnostic
|
||||
for field in structure.variants().iter().flat_map(|v| v.ast().fields.iter()) {
|
||||
for attr_name in field.attrs.iter().filter_map(|at| at.path().get_ident()) {
|
||||
if attr_name == "subdiagnostic" {
|
||||
return quote!();
|
||||
}
|
||||
}
|
||||
}
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
// We need to make sure that the same diagnostic slug can be used multiple times without
|
||||
// causing an error, so just have a global counter here.
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
let slug = slug.get_ident().unwrap();
|
||||
let ident = quote::format_ident!(
|
||||
"verify_{slug}_{}",
|
||||
COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
);
|
||||
let ref_slug = quote::format_ident!("{slug}_refs");
|
||||
let struct_name = &structure.ast().ident;
|
||||
let variables: Vec<_> = structure
|
||||
.variants()
|
||||
.iter()
|
||||
.flat_map(|v| {
|
||||
v.ast()
|
||||
.fields
|
||||
.iter()
|
||||
.filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))
|
||||
})
|
||||
.collect();
|
||||
// tidy errors on `#[test]` outside of test files, so we use `#[test ]` to work around this
|
||||
quote! {
|
||||
#[cfg(test)]
|
||||
#[test ]
|
||||
fn #ident() {
|
||||
let variables = [#(#variables),*];
|
||||
for vref in crate::fluent_generated::#ref_slug {
|
||||
assert!(variables.contains(vref), "{}: variable `{vref}` not found ({})", stringify!(#struct_name), stringify!(#slug));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Inline(..) => {
|
||||
// We don't generate a test for inline diagnostics, we can verify these at compile-time!
|
||||
// This verification is done in the `diag_message` function above
|
||||
quote! {}
|
||||
}
|
||||
}
|
||||
let message = &self.value;
|
||||
verify_fluent_message(self.message_span, &message, variant);
|
||||
quote! { rustc_errors::DiagMessage::Inline(std::borrow::Cow::Borrowed(#message)) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,10 +186,10 @@ impl<'parent, 'a> SubdiagnosticDeriveVariantBuilder<'parent, 'a> {
|
||||
fn identify_kind(
|
||||
&mut self,
|
||||
) -> Result<Vec<(SubdiagnosticKind, Message)>, DiagnosticDeriveError> {
|
||||
let mut kind_slugs = vec![];
|
||||
let mut kind_messages = vec![];
|
||||
|
||||
for attr in self.variant.ast().attrs {
|
||||
let Some(SubdiagnosticVariant { kind, slug }) =
|
||||
let Some(SubdiagnosticVariant { kind, message }) =
|
||||
SubdiagnosticVariant::from_attr(attr, &self.fields)?
|
||||
else {
|
||||
// Some attributes aren't errors - like documentation comments - but also aren't
|
||||
@@ -197,22 +197,22 @@ fn identify_kind(
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(slug) = slug else {
|
||||
let Some(message) = message else {
|
||||
let name = attr.path().segments.last().unwrap().ident.to_string();
|
||||
let name = name.as_str();
|
||||
|
||||
throw_span_err!(
|
||||
attr.span().unwrap(),
|
||||
format!(
|
||||
"diagnostic slug must be first argument of a `#[{name}(...)]` attribute"
|
||||
"diagnostic message must be first argument of a `#[{name}(...)]` attribute"
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
kind_slugs.push((kind, slug));
|
||||
kind_messages.push((kind, message));
|
||||
}
|
||||
|
||||
Ok(kind_slugs)
|
||||
Ok(kind_messages)
|
||||
}
|
||||
|
||||
/// Generates the code for a field with no attributes.
|
||||
@@ -498,9 +498,9 @@ fn generate_field_code_inner_list(
|
||||
}
|
||||
|
||||
pub(crate) fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveError> {
|
||||
let kind_slugs = self.identify_kind()?;
|
||||
let kind_messages = self.identify_kind()?;
|
||||
|
||||
let kind_stats: KindsStatistics = kind_slugs.iter().map(|(kind, _slug)| kind).collect();
|
||||
let kind_stats: KindsStatistics = kind_messages.iter().map(|(kind, _msg)| kind).collect();
|
||||
|
||||
let init = if kind_stats.has_multipart_suggestion {
|
||||
quote! { let mut suggestions = Vec::new(); }
|
||||
@@ -516,7 +516,7 @@ pub(crate) fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveErro
|
||||
.map(|binding| self.generate_field_attr_code(binding, kind_stats))
|
||||
.collect();
|
||||
|
||||
if kind_slugs.is_empty() && !self.has_subdiagnostic {
|
||||
if kind_messages.is_empty() && !self.has_subdiagnostic {
|
||||
if self.is_enum {
|
||||
// It's okay for a variant to not be a subdiagnostic at all..
|
||||
return Ok(quote! {});
|
||||
@@ -533,9 +533,9 @@ pub(crate) fn into_tokens(&mut self) -> Result<TokenStream, DiagnosticDeriveErro
|
||||
|
||||
let diag = &self.parent.diag;
|
||||
let mut calls = TokenStream::new();
|
||||
for (kind, slug) in kind_slugs {
|
||||
for (kind, messages) in kind_messages {
|
||||
let message = format_ident!("__message");
|
||||
let message_stream = slug.diag_message(None);
|
||||
let message_stream = messages.diag_message(None);
|
||||
calls.extend(quote! { let #message = #diag.eagerly_translate(#message_stream); });
|
||||
|
||||
let name = format_ident!("{}{}", if span_field.is_some() { "span_" } else { "" }, kind);
|
||||
|
||||
@@ -267,7 +267,7 @@ fn value_ref(&self) -> Option<&T> {
|
||||
///
|
||||
/// ```ignore (not-usage-example)
|
||||
/// /// Suggest `==` when users wrote `===`.
|
||||
/// #[suggestion(slug = "parser-not-javascript-eq", code = "{lhs} == {rhs}")]
|
||||
/// #[suggestion("example message", code = "{lhs} == {rhs}")]
|
||||
/// struct NotJavaScriptEq {
|
||||
/// #[primary_span]
|
||||
/// span: Span,
|
||||
@@ -588,13 +588,13 @@ pub(super) enum SubdiagnosticKind {
|
||||
|
||||
pub(super) struct SubdiagnosticVariant {
|
||||
pub(super) kind: SubdiagnosticKind,
|
||||
pub(super) slug: Option<Message>,
|
||||
pub(super) message: Option<Message>,
|
||||
}
|
||||
|
||||
impl SubdiagnosticVariant {
|
||||
/// Constructs a `SubdiagnosticVariant` 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.
|
||||
/// `#[error("add parenthesis")]` or `#[suggestion(code = "...")]`. Returns the
|
||||
/// `SubdiagnosticKind` and the diagnostic message, if specified.
|
||||
pub(super) fn from_attr(
|
||||
attr: &Attribute,
|
||||
fields: &FieldMap,
|
||||
@@ -660,11 +660,11 @@ pub(super) fn from_attr(
|
||||
let list = match &attr.meta {
|
||||
Meta::List(list) => {
|
||||
// An attribute with properties, such as `#[suggestion(code = "...")]` or
|
||||
// `#[error(some::slug)]`
|
||||
// `#[error("message")]`
|
||||
list
|
||||
}
|
||||
Meta::Path(_) => {
|
||||
// An attribute without a slug or other properties, such as `#[note]` - return
|
||||
// An attribute without a message or other properties, such as `#[note]` - return
|
||||
// without further processing.
|
||||
//
|
||||
// Only allow this if there are no mandatory properties, such as `code = "..."` in
|
||||
@@ -677,7 +677,7 @@ pub(super) fn from_attr(
|
||||
| SubdiagnosticKind::HelpOnce
|
||||
| SubdiagnosticKind::Warn
|
||||
| SubdiagnosticKind::MultipartSuggestion { .. } => {
|
||||
return Ok(Some(SubdiagnosticVariant { kind, slug: None }));
|
||||
return Ok(Some(SubdiagnosticVariant { kind, message: None }));
|
||||
}
|
||||
SubdiagnosticKind::Suggestion { .. } => {
|
||||
throw_span_err!(span, "suggestion without `code = \"...\"`")
|
||||
@@ -692,45 +692,34 @@ pub(super) fn from_attr(
|
||||
let mut code = None;
|
||||
let mut suggestion_kind = None;
|
||||
|
||||
let mut slug = None;
|
||||
let mut message = None;
|
||||
|
||||
list.parse_args_with(|input: ParseStream<'_>| {
|
||||
let mut is_first = true;
|
||||
while !input.is_empty() {
|
||||
// Try to parse an inline diagnostic message
|
||||
if input.peek(LitStr) {
|
||||
let message = input.parse::<LitStr>()?;
|
||||
if !message.suffix().is_empty() {
|
||||
let inline_message = input.parse::<LitStr>()?;
|
||||
if !inline_message.suffix().is_empty() {
|
||||
span_err(
|
||||
message.span().unwrap(),
|
||||
inline_message.span().unwrap(),
|
||||
"Inline message is not allowed to have a suffix",
|
||||
).emit();
|
||||
}
|
||||
if !input.is_empty() { input.parse::<Token![,]>()?; }
|
||||
if is_first {
|
||||
slug = Some(Message::Inline(message.span(), message.value()));
|
||||
message = Some(Message { message_span: inline_message.span(), value: inline_message.value() });
|
||||
is_first = false;
|
||||
} else {
|
||||
span_err(message.span().unwrap(), "a diagnostic message must be the first argument to the attribute").emit();
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to parse a slug instead
|
||||
let arg_name: Path = input.parse::<Path>()?;
|
||||
let arg_name_span = arg_name.span().unwrap();
|
||||
if input.is_empty() || input.parse::<Token![,]>().is_ok() {
|
||||
if is_first {
|
||||
slug = Some(Message::Slug(arg_name));
|
||||
is_first = false;
|
||||
} else {
|
||||
span_err(arg_name_span, "a diagnostic slug must be the first argument to the attribute").emit();
|
||||
span_err(inline_message.span().unwrap(), "a diagnostic message must be the first argument to the attribute").emit();
|
||||
}
|
||||
continue
|
||||
}
|
||||
is_first = false;
|
||||
|
||||
// Try to parse an argument
|
||||
let arg_name: Path = input.parse::<Path>()?;
|
||||
let arg_name_span = arg_name.span().unwrap();
|
||||
match (arg_name.require_ident()?.to_string().as_str(), &mut kind) {
|
||||
("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
|
||||
let code_init = build_suggestion_code(
|
||||
@@ -836,7 +825,7 @@ pub(super) fn from_attr(
|
||||
| SubdiagnosticKind::Warn => {}
|
||||
}
|
||||
|
||||
Ok(Some(SubdiagnosticVariant { kind, slug }))
|
||||
Ok(Some(SubdiagnosticVariant { kind, message }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user