macros: support adding warnings to diags

Both diagnostic and subdiagnostic derives were missing the ability to
add warnings to diagnostics - this is made more difficult by the `warn`
attribute already existing, so this name being unavailable for the
derives to use. `#[warn_]` is used instead, which requires
special-casing so that `{span_,}warn` is called instead of
`{span_,}warn_`.

Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
David Wood 2022-07-11 18:46:24 +01:00
parent 88c11c5bff
commit 81cf2294b4
8 changed files with 70 additions and 25 deletions

View File

@ -59,7 +59,7 @@ impl<'a> SessionDiagnosticDerive<'a> {
return DiagnosticDeriveError::ErrorHandled.to_compile_error(); return DiagnosticDeriveError::ErrorHandled.to_compile_error();
} }
(Some(DiagnosticDeriveKind::Lint), _) => { (Some(DiagnosticDeriveKind::Lint), _) => {
span_err(span, "only `#[error(..)]` and `#[warn(..)]` are supported") span_err(span, "only `#[error(..)]` and `#[warning(..)]` are supported")
.help("use the `#[error(...)]` attribute to create a error") .help("use the `#[error(...)]` attribute to create a error")
.emit(); .emit();
return DiagnosticDeriveError::ErrorHandled.to_compile_error(); return DiagnosticDeriveError::ErrorHandled.to_compile_error();

View File

@ -8,7 +8,7 @@ use crate::diagnostics::utils::{
report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path, report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path,
Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce, Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
}; };
use proc_macro2::{Ident, TokenStream}; use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
@ -156,7 +156,7 @@ impl DiagnosticDeriveBuilder {
let name = name.as_str(); let name = name.as_str();
let meta = attr.parse_meta()?; let meta = attr.parse_meta()?;
let is_help_or_note = matches!(name, "help" | "note"); let is_help_note_or_warn = matches!(name, "help" | "note" | "warn_");
let nested = match meta { let nested = match meta {
// Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` for most cases or // Most attributes are lists, like `#[error(..)]`/`#[warning(..)]` for most cases or
@ -164,8 +164,12 @@ impl DiagnosticDeriveBuilder {
Meta::List(MetaList { ref nested, .. }) => nested, Meta::List(MetaList { ref nested, .. }) => nested,
// Subdiagnostics without spans can be applied to the type too, and these are just // Subdiagnostics without spans can be applied to the type too, and these are just
// paths: `#[help]` and `#[note]` // paths: `#[help]` and `#[note]`
Meta::Path(_) if is_help_or_note => { Meta::Path(_) if is_help_note_or_warn => {
let fn_name = proc_macro2::Ident::new(name, attr.span()); let fn_name = if name == "warn_" {
Ident::new("warn", attr.span())
} else {
Ident::new(name, attr.span())
};
return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::_subdiag::#fn_name); }); return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::_subdiag::#fn_name); });
} }
_ => throw_invalid_attr!(attr, &meta), _ => throw_invalid_attr!(attr, &meta),
@ -177,9 +181,11 @@ impl DiagnosticDeriveBuilder {
"error" => self.kind.set_once((DiagnosticDeriveKind::Error, span)), "error" => self.kind.set_once((DiagnosticDeriveKind::Error, span)),
"warning" => self.kind.set_once((DiagnosticDeriveKind::Warn, span)), "warning" => self.kind.set_once((DiagnosticDeriveKind::Warn, span)),
"lint" => self.kind.set_once((DiagnosticDeriveKind::Lint, span)), "lint" => self.kind.set_once((DiagnosticDeriveKind::Lint, span)),
"help" | "note" => (), "help" | "note" | "warn_" => (),
_ => throw_invalid_attr!(attr, &meta, |diag| { _ => throw_invalid_attr!(attr, &meta, |diag| {
diag.help("only `error`, `warning`, `help` and `note` are valid attributes") diag.help(
"only `error`, `warning`, `help`, `note` and `warn_` are valid attributes",
)
}), }),
} }
@ -188,14 +194,16 @@ impl DiagnosticDeriveBuilder {
let mut nested_iter = nested.into_iter(); let mut nested_iter = nested.into_iter();
if let Some(nested_attr) = nested_iter.next() { if let Some(nested_attr) = nested_iter.next() {
// Report an error if there are any other list items after the path. // Report an error if there are any other list items after the path.
if is_help_or_note && nested_iter.next().is_some() { if is_help_note_or_warn && nested_iter.next().is_some() {
throw_invalid_nested_attr!(attr, &nested_attr, |diag| { throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
diag.help("`help` and `note` struct attributes can only have one argument") diag.help(
"`help`, `note` and `warn_` struct attributes can only have one argument",
)
}); });
} }
match nested_attr { match nested_attr {
NestedMeta::Meta(Meta::Path(path)) if is_help_or_note => { NestedMeta::Meta(Meta::Path(path)) if is_help_note_or_warn => {
let fn_name = proc_macro2::Ident::new(name, attr.span()); let fn_name = proc_macro2::Ident::new(name, attr.span());
return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); }); return Ok(quote! { #diag.#fn_name(rustc_errors::fluent::#path); });
} }
@ -203,7 +211,7 @@ impl DiagnosticDeriveBuilder {
self.slug.set_once((path.clone(), span)); self.slug.set_once((path.clone(), span));
} }
NestedMeta::Meta(meta @ Meta::NameValue(_)) NestedMeta::Meta(meta @ Meta::NameValue(_))
if !is_help_or_note if !is_help_note_or_warn
&& meta.path().segments.last().unwrap().ident.to_string() == "code" => && meta.path().segments.last().unwrap().ident.to_string() == "code" =>
{ {
// don't error for valid follow-up attributes // don't error for valid follow-up attributes
@ -347,10 +355,12 @@ impl DiagnosticDeriveBuilder {
report_error_if_not_applied_to_span(attr, &info)?; report_error_if_not_applied_to_span(attr, &info)?;
Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label })) Ok(self.add_spanned_subdiagnostic(binding, ident, parse_quote! { _subdiag::label }))
} }
"note" | "help" => { "note" | "help" | "warn_" => {
let path = match name { let warn_ident = Ident::new("warn", Span::call_site());
"note" => parse_quote! { _subdiag::note }, let (ident, path) = match name {
"help" => parse_quote! { _subdiag::help }, "note" => (ident, parse_quote! { _subdiag::note }),
"help" => (ident, parse_quote! { _subdiag::help }),
"warn_" => (&warn_ident, parse_quote! { _subdiag::warn }),
_ => unreachable!(), _ => unreachable!(),
}; };
if type_matches_path(&info.ty, &["rustc_span", "Span"]) { if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
@ -387,10 +397,10 @@ impl DiagnosticDeriveBuilder {
"suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => { "suggestion" | "suggestion_short" | "suggestion_hidden" | "suggestion_verbose" => {
return self.generate_inner_field_code_suggestion(attr, info); return self.generate_inner_field_code_suggestion(attr, info);
} }
"label" | "help" | "note" => (), "label" | "help" | "note" | "warn_" => (),
_ => throw_invalid_attr!(attr, &meta, |diag| { _ => throw_invalid_attr!(attr, &meta, |diag| {
diag.help( diag.help(
"only `label`, `note`, `help` or `suggestion{,_short,_hidden,_verbose}` are \ "only `label`, `help`, `note`, `warn` or `suggestion{,_short,_hidden,_verbose}` are \
valid field attributes", valid field attributes",
) )
}), }),
@ -419,7 +429,14 @@ impl DiagnosticDeriveBuilder {
Ok(self.add_spanned_subdiagnostic(binding, ident, msg)) Ok(self.add_spanned_subdiagnostic(binding, ident, msg))
} }
"note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)), "note" | "help" if type_is_unit(&info.ty) => Ok(self.add_subdiagnostic(ident, msg)),
"note" | "help" => report_type_error(attr, "`Span` or `()`")?, // `warn_` must be special-cased because the attribute `warn` already has meaning and
// so isn't used, despite the diagnostic API being named `warn`.
"warn_" if type_matches_path(&info.ty, &["rustc_span", "Span"]) => Ok(self
.add_spanned_subdiagnostic(binding, &Ident::new("warn", Span::call_site()), msg)),
"warn_" if type_is_unit(&info.ty) => {
Ok(self.add_subdiagnostic(&Ident::new("warn", Span::call_site()), msg))
}
"note" | "help" | "warn_" => report_type_error(attr, "`Span` or `()`")?,
_ => unreachable!(), _ => unreachable!(),
} }
} }

View File

@ -260,10 +260,12 @@ pub(crate) fn fluent_messages(input: proc_macro::TokenStream) -> proc_macro::Tok
#generated #generated
pub mod _subdiag { pub mod _subdiag {
pub const note: crate::SubdiagnosticMessage =
crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("note"));
pub const help: crate::SubdiagnosticMessage = pub const help: crate::SubdiagnosticMessage =
crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("help")); crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("help"));
pub const note: crate::SubdiagnosticMessage =
crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("note"));
pub const warn: crate::SubdiagnosticMessage =
crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("warn"));
pub const label: crate::SubdiagnosticMessage = pub const label: crate::SubdiagnosticMessage =
crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("label")); crate::SubdiagnosticMessage::FluentAttr(std::borrow::Cow::Borrowed("label"));
pub const suggestion: crate::SubdiagnosticMessage = pub const suggestion: crate::SubdiagnosticMessage =

View File

@ -37,6 +37,8 @@ enum SubdiagnosticKind {
Note, Note,
/// `#[help(...)]` /// `#[help(...)]`
Help, Help,
/// `#[warn_(...)]`
Warn,
/// `#[suggestion{,_short,_hidden,_verbose}]` /// `#[suggestion{,_short,_hidden,_verbose}]`
Suggestion(SubdiagnosticSuggestionKind), Suggestion(SubdiagnosticSuggestionKind),
} }
@ -49,6 +51,7 @@ impl FromStr for SubdiagnosticKind {
"label" => Ok(SubdiagnosticKind::Label), "label" => Ok(SubdiagnosticKind::Label),
"note" => Ok(SubdiagnosticKind::Note), "note" => Ok(SubdiagnosticKind::Note),
"help" => Ok(SubdiagnosticKind::Help), "help" => Ok(SubdiagnosticKind::Help),
"warn_" => Ok(SubdiagnosticKind::Warn),
"suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)), "suggestion" => Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal)),
"suggestion_short" => { "suggestion_short" => {
Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short)) Ok(SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Short))
@ -70,6 +73,7 @@ impl quote::IdentFragment for SubdiagnosticKind {
SubdiagnosticKind::Label => write!(f, "label"), SubdiagnosticKind::Label => write!(f, "label"),
SubdiagnosticKind::Note => write!(f, "note"), SubdiagnosticKind::Note => write!(f, "note"),
SubdiagnosticKind::Help => write!(f, "help"), SubdiagnosticKind::Help => write!(f, "help"),
SubdiagnosticKind::Warn => write!(f, "warn"),
SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal) => { SubdiagnosticKind::Suggestion(SubdiagnosticSuggestionKind::Normal) => {
write!(f, "suggestion") write!(f, "suggestion")
} }

View File

@ -130,8 +130,9 @@ decl_derive!(
warning, warning,
error, error,
lint, lint,
note,
help, help,
note,
warn_,
// field attributes // field attributes
skip_arg, skip_arg,
primary_span, primary_span,
@ -148,8 +149,9 @@ decl_derive!(
warning, warning,
error, error,
lint, lint,
note,
help, help,
note,
warn_,
// field attributes // field attributes
skip_arg, skip_arg,
primary_span, primary_span,
@ -166,6 +168,7 @@ decl_derive!(
label, label,
help, help,
note, note,
warn_,
suggestion, suggestion,
suggestion_short, suggestion_short,
suggestion_hidden, suggestion_hidden,

View File

@ -538,7 +538,7 @@ struct LabelWithTrailingList {
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[lint(typeck::ambiguous_lifetime_bound)] #[lint(typeck::ambiguous_lifetime_bound)]
//~^ ERROR only `#[error(..)]` and `#[warn(..)]` are supported //~^ ERROR only `#[error(..)]` and `#[warning(..)]` are supported
struct LintsBad { struct LintsBad {
} }
@ -559,3 +559,10 @@ struct ErrorWithMultiSpan {
#[primary_span] #[primary_span]
span: MultiSpan, span: MultiSpan,
} }
#[derive(SessionDiagnostic)]
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
#[warn_]
struct ErrorWithWarn {
val: String,
}

View File

@ -21,7 +21,7 @@ error: `#[nonsense(...)]` is not a valid attribute
LL | #[nonsense(typeck::ambiguous_lifetime_bound, code = "E0123")] LL | #[nonsense(typeck::ambiguous_lifetime_bound, code = "E0123")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
= help: only `error`, `warning`, `help` and `note` are valid attributes = help: only `error`, `warning`, `help`, `note` and `warn_` are valid attributes
error: diagnostic kind not specified error: diagnostic kind not specified
--> $DIR/diagnostic-derive.rs:53:1 --> $DIR/diagnostic-derive.rs:53:1
@ -363,7 +363,7 @@ error: `#[label(...)]` is not a valid attribute
LL | #[label(typeck::label, foo("..."))] LL | #[label(typeck::label, foo("..."))]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: only `#[error(..)]` and `#[warn(..)]` are supported error: only `#[error(..)]` and `#[warning(..)]` are supported
--> $DIR/diagnostic-derive.rs:540:1 --> $DIR/diagnostic-derive.rs:540:1
| |
LL | / #[lint(typeck::ambiguous_lifetime_bound)] LL | / #[lint(typeck::ambiguous_lifetime_bound)]

View File

@ -508,3 +508,15 @@ enum AX {
span: Span, span: Span,
} }
} }
#[derive(SessionSubdiagnostic)]
#[warn_(parser::add_paren)]
struct AY {
}
#[derive(SessionSubdiagnostic)]
#[warn_(parser::add_paren)]
struct AZ {
#[primary_span]
span: Span,
}