macros: allow setting applicability in attribute

In the initial implementation of the `SessionSubdiagnostic`, the
`Applicability` of a suggestion can be set both as a field and as part
of the attribute, this commit adds the same support to the original
`SessionDiagnostic` derive.

Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
David Wood 2022-04-27 05:43:36 +01:00
parent e8ee0d7a20
commit e5d9371b30
5 changed files with 120 additions and 54 deletions

View File

@ -5,12 +5,13 @@
SessionDiagnosticDeriveError,
};
use crate::diagnostics::utils::{
option_inner_ty, report_error_if_not_applied_to_span, type_matches_path, FieldInfo,
HasFieldMap, SetOnce,
option_inner_ty, report_error_if_not_applied_to_span, type_matches_path, Applicability,
FieldInfo, HasFieldMap, SetOnce,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::collections::HashMap;
use std::str::FromStr;
use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type};
use synstructure::Structure;
@ -430,7 +431,7 @@ fn generate_non_option_field_code(
}),
};
let (span_, applicability) = self.span_and_applicability_of_ty(info)?;
let (span_field, mut applicability) = self.span_and_applicability_of_ty(info)?;
let mut msg = None;
let mut code = None;
@ -445,6 +446,7 @@ fn generate_non_option_field_code(
let nested_name = nested_name.as_str();
match meta {
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
let span = meta.span().unwrap();
match nested_name {
"message" => {
msg = Some(s.value());
@ -453,9 +455,27 @@ fn generate_non_option_field_code(
let formatted_str = self.build_format(&s.value(), s.span());
code = Some(formatted_str);
}
"applicability" => {
applicability = match applicability {
Some(v) => {
span_err(
span,
"applicability cannot be set in both the field and attribute"
).emit();
Some(v)
}
None => match Applicability::from_str(&s.value()) {
Ok(v) => Some(quote! { #v }),
Err(()) => {
span_err(span, "invalid applicability").emit();
None
}
},
}
}
_ => throw_invalid_nested_attr!(attr, &nested_attr, |diag| {
diag.help(
"only `message` and `code` are valid field attributes",
"only `message`, `code` and `applicability` are valid field attributes",
)
}),
}
@ -464,6 +484,9 @@ fn generate_non_option_field_code(
}
}
let applicability = applicability
.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
let method = format_ident!("span_{}", name);
let slug = self
@ -475,7 +498,7 @@ fn generate_non_option_field_code(
let msg = quote! { rustc_errors::DiagnosticMessage::fluent_attr(#slug, #msg) };
let code = code.unwrap_or_else(|| quote! { String::new() });
Ok(quote! { #diag.#method(#span_, #msg, #code, #applicability); })
Ok(quote! { #diag.#method(#span_field, #msg, #code, #applicability); })
}
_ => throw_invalid_attr!(attr, &meta),
}
@ -505,12 +528,12 @@ fn add_subdiagnostic(
fn span_and_applicability_of_ty(
&self,
info: FieldInfo<'_>,
) -> Result<(TokenStream, TokenStream), SessionDiagnosticDeriveError> {
) -> Result<(TokenStream, Option<TokenStream>), SessionDiagnosticDeriveError> {
match &info.ty {
// If `ty` is `Span` w/out applicability, then use `Applicability::Unspecified`.
ty @ Type::Path(..) if type_matches_path(ty, &["rustc_span", "Span"]) => {
let binding = &info.binding.binding;
Ok((quote!(*#binding), quote!(rustc_errors::Applicability::Unspecified)))
Ok((quote!(*#binding), None))
}
// If `ty` is `(Span, Applicability)` then return tokens accessing those.
Type::Tuple(tup) => {
@ -546,7 +569,7 @@ fn span_and_applicability_of_ty(
.map(|applicability_idx| quote!(#binding.#applicability_idx))
.unwrap_or_else(|| quote!(rustc_errors::Applicability::Unspecified));
return Ok((span, applicability));
return Ok((span, Some(applicability)));
}
throw_span_err!(info.span.unwrap(), "wrong types for suggestion", |diag| {

View File

@ -6,7 +6,7 @@
};
use crate::diagnostics::utils::{
option_inner_ty, report_error_if_not_applied_to_applicability,
report_error_if_not_applied_to_span, FieldInfo, HasFieldMap, SetOnce,
report_error_if_not_applied_to_span, Applicability, FieldInfo, HasFieldMap, SetOnce,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
@ -16,48 +16,6 @@
use syn::{spanned::Spanned, Meta, MetaList, MetaNameValue};
use synstructure::{BindingInfo, Structure, VariantInfo};
/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
/// the user's selection of applicability if specified in an attribute.
enum Applicability {
MachineApplicable,
MaybeIncorrect,
HasPlaceholders,
Unspecified,
}
impl FromStr for Applicability {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"machine-applicable" => Ok(Applicability::MachineApplicable),
"maybe-incorrect" => Ok(Applicability::MaybeIncorrect),
"has-placeholders" => Ok(Applicability::HasPlaceholders),
"unspecified" => Ok(Applicability::Unspecified),
_ => Err(()),
}
}
}
impl quote::ToTokens for Applicability {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(match self {
Applicability::MachineApplicable => {
quote! { rustc_errors::Applicability::MachineApplicable }
}
Applicability::MaybeIncorrect => {
quote! { rustc_errors::Applicability::MaybeIncorrect }
}
Applicability::HasPlaceholders => {
quote! { rustc_errors::Applicability::HasPlaceholders }
}
Applicability::Unspecified => {
quote! { rustc_errors::Applicability::Unspecified }
}
});
}
}
/// Which kind of suggestion is being created?
#[derive(Clone, Copy)]
enum SubdiagnosticSuggestionKind {

View File

@ -3,6 +3,7 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::collections::BTreeSet;
use std::str::FromStr;
use syn::{spanned::Spanned, Attribute, Meta, Type, Visibility};
use synstructure::BindingInfo;
@ -222,3 +223,45 @@ fn build_format(&self, input: &str, span: proc_macro2::Span) -> TokenStream {
}
}
}
/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
/// the user's selection of applicability if specified in an attribute.
pub(crate) enum Applicability {
MachineApplicable,
MaybeIncorrect,
HasPlaceholders,
Unspecified,
}
impl FromStr for Applicability {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"machine-applicable" => Ok(Applicability::MachineApplicable),
"maybe-incorrect" => Ok(Applicability::MaybeIncorrect),
"has-placeholders" => Ok(Applicability::HasPlaceholders),
"unspecified" => Ok(Applicability::Unspecified),
_ => Err(()),
}
}
}
impl quote::ToTokens for Applicability {
fn to_tokens(&self, tokens: &mut TokenStream) {
tokens.extend(match self {
Applicability::MachineApplicable => {
quote! { rustc_errors::Applicability::MachineApplicable }
}
Applicability::MaybeIncorrect => {
quote! { rustc_errors::Applicability::MaybeIncorrect }
}
Applicability::HasPlaceholders => {
quote! { rustc_errors::Applicability::HasPlaceholders }
}
Applicability::Unspecified => {
quote! { rustc_errors::Applicability::Unspecified }
}
});
}
}

View File

@ -433,3 +433,33 @@ struct ErrorWithNoteWrongOrder {
struct ErrorWithNoteCustomWrongOrder {
val: String,
}
#[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")]
struct ApplicabilityInBoth {
#[suggestion(message = "bar", code = "...", applicability = "maybe-incorrect")]
//~^ ERROR applicability cannot be set in both the field and attribute
suggestion: (Span, Applicability),
}
#[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")]
struct InvalidApplicability {
#[suggestion(message = "bar", code = "...", applicability = "batman")]
//~^ ERROR invalid applicability
suggestion: Span,
}
#[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")]
struct ValidApplicability {
#[suggestion(message = "bar", code = "...", applicability = "maybe-incorrect")]
suggestion: Span,
}
#[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")]
struct NoApplicability {
#[suggestion(message = "bar", code = "...")]
suggestion: Span,
}

View File

@ -257,7 +257,7 @@ error: `#[suggestion(nonsense = ...)]` is not a valid attribute
LL | #[suggestion(nonsense = "bar")]
| ^^^^^^^^^^^^^^^^
|
= help: only `message` and `code` are valid field attributes
= help: only `message`, `code` and `applicability` are valid field attributes
error: `#[suggestion(msg = ...)]` is not a valid attribute
--> $DIR/diagnostic-derive.rs:232:18
@ -265,7 +265,7 @@ error: `#[suggestion(msg = ...)]` is not a valid attribute
LL | #[suggestion(msg = "bar")]
| ^^^^^^^^^^^
|
= help: only `message` and `code` are valid field attributes
= help: only `message`, `code` and `applicability` are valid field attributes
error: wrong field type for suggestion
--> $DIR/diagnostic-derive.rs:254:5
@ -325,6 +325,18 @@ error: `#[note = ...]` must come after `#[error(..)]` or `#[warn(..)]`
LL | #[note = "bar"]
| ^^^^^^^^^^^^^^^
error: applicability cannot be set in both the field and attribute
--> $DIR/diagnostic-derive.rs:440:49
|
LL | #[suggestion(message = "bar", code = "...", applicability = "maybe-incorrect")]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: invalid applicability
--> $DIR/diagnostic-derive.rs:448:49
|
LL | #[suggestion(message = "bar", code = "...", applicability = "batman")]
| ^^^^^^^^^^^^^^^^^^^^^^^^
error: cannot find attribute `nonsense` in this scope
--> $DIR/diagnostic-derive.rs:51:3
|
@ -348,6 +360,6 @@ LL | #[derive(SessionDiagnostic)]
|
= note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to 41 previous errors
error: aborting due to 43 previous errors
For more information about this error, try `rustc --explain E0599`.