macros: spanless subdiagnostics from ()
fields
Type attributes could previously be used to support spanless subdiagnostics but these couldn't easily be made optional in the same way that spanned subdiagnostics could by using a field attribute on a field with an `Option<Span>` type. Spanless subdiagnostics can now be specified on fields with `()` type or `Option<()>` type. Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
parent
1d2ea98cff
commit
7b7061dd89
4 changed files with 97 additions and 33 deletions
|
@ -5,10 +5,10 @@ use crate::diagnostics::error::{
|
|||
SessionDiagnosticDeriveError,
|
||||
};
|
||||
use crate::diagnostics::utils::{
|
||||
report_error_if_not_applied_to_span, type_matches_path, Applicability, FieldInfo, FieldInnerTy,
|
||||
HasFieldMap, SetOnce,
|
||||
report_error_if_not_applied_to_span, report_type_error, type_is_unit, type_matches_path,
|
||||
Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
|
||||
};
|
||||
use proc_macro2::TokenStream;
|
||||
use proc_macro2::{Ident, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
|
@ -388,7 +388,8 @@ impl SessionDiagnosticDeriveBuilder {
|
|||
) -> Result<TokenStream, SessionDiagnosticDeriveError> {
|
||||
let diag = &self.diag;
|
||||
|
||||
let name = attr.path.segments.last().unwrap().ident.to_string();
|
||||
let ident = &attr.path.segments.last().unwrap().ident;
|
||||
let name = ident.to_string();
|
||||
let name = name.as_str();
|
||||
|
||||
let meta = attr.parse_meta()?;
|
||||
|
@ -405,9 +406,18 @@ impl SessionDiagnosticDeriveBuilder {
|
|||
#diag.set_span(#binding);
|
||||
})
|
||||
}
|
||||
"label" | "note" | "help" => {
|
||||
"label" => {
|
||||
report_error_if_not_applied_to_span(attr, &info)?;
|
||||
Ok(self.add_subdiagnostic(binding, name, name))
|
||||
Ok(self.add_spanned_subdiagnostic(binding, ident, name))
|
||||
}
|
||||
"note" | "help" => {
|
||||
if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
|
||||
Ok(self.add_spanned_subdiagnostic(binding, ident, name))
|
||||
} else if type_is_unit(&info.ty) {
|
||||
Ok(self.add_subdiagnostic(ident, name))
|
||||
} else {
|
||||
report_type_error(attr, "`Span` or `()`")?;
|
||||
}
|
||||
}
|
||||
"subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }),
|
||||
_ => throw_invalid_attr!(attr, &meta, |diag| {
|
||||
|
@ -416,9 +426,18 @@ impl SessionDiagnosticDeriveBuilder {
|
|||
}),
|
||||
},
|
||||
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name {
|
||||
"label" | "note" | "help" => {
|
||||
"label" => {
|
||||
report_error_if_not_applied_to_span(attr, &info)?;
|
||||
Ok(self.add_subdiagnostic(binding, name, &s.value()))
|
||||
Ok(self.add_spanned_subdiagnostic(binding, ident, &s.value()))
|
||||
}
|
||||
"note" | "help" => {
|
||||
if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
|
||||
Ok(self.add_spanned_subdiagnostic(binding, ident, &s.value()))
|
||||
} else if type_is_unit(&info.ty) {
|
||||
Ok(self.add_subdiagnostic(ident, &s.value()))
|
||||
} else {
|
||||
report_type_error(attr, "`Span` or `()`")?;
|
||||
}
|
||||
}
|
||||
_ => throw_invalid_attr!(attr, &meta, |diag| {
|
||||
diag.help("only `label`, `note` and `help` are valid field attributes")
|
||||
|
@ -510,12 +529,12 @@ impl SessionDiagnosticDeriveBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug and
|
||||
/// `fluent_attr_identifier`.
|
||||
fn add_subdiagnostic(
|
||||
/// Adds a spanned subdiagnostic by generating a `diag.span_$kind` call with the current slug
|
||||
/// and `fluent_attr_identifier`.
|
||||
fn add_spanned_subdiagnostic(
|
||||
&self,
|
||||
field_binding: TokenStream,
|
||||
kind: &str,
|
||||
kind: &Ident,
|
||||
fluent_attr_identifier: &str,
|
||||
) -> TokenStream {
|
||||
let diag = &self.diag;
|
||||
|
@ -531,6 +550,16 @@ impl SessionDiagnosticDeriveBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Adds a subdiagnostic by generating a `diag.span_$kind` call with the current slug
|
||||
/// and `fluent_attr_identifier`.
|
||||
fn add_subdiagnostic(&self, kind: &Ident, fluent_attr_identifier: &str) -> TokenStream {
|
||||
let diag = &self.diag;
|
||||
let slug = self.slug.as_ref().map(|(slug, _)| slug.as_str()).unwrap_or("missing-slug");
|
||||
quote! {
|
||||
#diag.#kind(rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier));
|
||||
}
|
||||
}
|
||||
|
||||
fn span_and_applicability_of_ty(
|
||||
&self,
|
||||
info: FieldInfo<'_>,
|
||||
|
|
|
@ -4,7 +4,7 @@ use proc_macro2::TokenStream;
|
|||
use quote::{format_ident, quote, ToTokens};
|
||||
use std::collections::BTreeSet;
|
||||
use std::str::FromStr;
|
||||
use syn::{spanned::Spanned, Attribute, Meta, Type, Visibility};
|
||||
use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple, Visibility};
|
||||
use synstructure::BindingInfo;
|
||||
|
||||
/// Checks whether the type name of `ty` matches `name`.
|
||||
|
@ -25,7 +25,35 @@ pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/// Reports an error if the field's type is not `Applicability`.
|
||||
/// Checks whether the type `ty` is `()`.
|
||||
pub(crate) fn type_is_unit(ty: &Type) -> bool {
|
||||
if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false }
|
||||
}
|
||||
|
||||
/// Reports a type error for field with `attr`.
|
||||
pub(crate) fn report_type_error(
|
||||
attr: &Attribute,
|
||||
ty_name: &str,
|
||||
) -> Result<!, SessionDiagnosticDeriveError> {
|
||||
let name = attr.path.segments.last().unwrap().ident.to_string();
|
||||
let meta = attr.parse_meta()?;
|
||||
|
||||
throw_span_err!(
|
||||
attr.span().unwrap(),
|
||||
&format!(
|
||||
"the `#[{}{}]` attribute can only be applied to fields of type {}",
|
||||
name,
|
||||
match meta {
|
||||
Meta::Path(_) => "",
|
||||
Meta::NameValue(_) => " = ...",
|
||||
Meta::List(_) => "(...)",
|
||||
},
|
||||
ty_name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// Reports an error if the field's type does not match `path`.
|
||||
fn report_error_if_not_applied_to_ty(
|
||||
attr: &Attribute,
|
||||
info: &FieldInfo<'_>,
|
||||
|
@ -33,23 +61,7 @@ fn report_error_if_not_applied_to_ty(
|
|||
ty_name: &str,
|
||||
) -> Result<(), SessionDiagnosticDeriveError> {
|
||||
if !type_matches_path(&info.ty, path) {
|
||||
let name = attr.path.segments.last().unwrap().ident.to_string();
|
||||
let name = name.as_str();
|
||||
let meta = attr.parse_meta()?;
|
||||
|
||||
throw_span_err!(
|
||||
attr.span().unwrap(),
|
||||
&format!(
|
||||
"the `#[{}{}]` attribute can only be applied to fields of type `{}`",
|
||||
name,
|
||||
match meta {
|
||||
Meta::Path(_) => "",
|
||||
Meta::NameValue(_) => " = ...",
|
||||
Meta::List(_) => "(...)",
|
||||
},
|
||||
ty_name
|
||||
)
|
||||
);
|
||||
report_type_error(attr, ty_name)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -64,7 +76,7 @@ pub(crate) fn report_error_if_not_applied_to_applicability(
|
|||
attr,
|
||||
info,
|
||||
&["rustc_errors", "Applicability"],
|
||||
"Applicability",
|
||||
"`Applicability`",
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -73,7 +85,7 @@ pub(crate) fn report_error_if_not_applied_to_span(
|
|||
attr: &Attribute,
|
||||
info: &FieldInfo<'_>,
|
||||
) -> Result<(), SessionDiagnosticDeriveError> {
|
||||
report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "Span")
|
||||
report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "`Span`")
|
||||
}
|
||||
|
||||
/// Inner type of a field and type of wrapper.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#![feature(allow_internal_unstable)]
|
||||
#![feature(let_else)]
|
||||
#![feature(never_type)]
|
||||
#![feature(proc_macro_diagnostic)]
|
||||
#![allow(rustc::default_hash_types)]
|
||||
#![recursion_limit = "128"]
|
||||
|
|
|
@ -482,3 +482,25 @@ struct VecField {
|
|||
#[label]
|
||||
spans: Vec<Span>,
|
||||
}
|
||||
|
||||
#[derive(SessionDiagnostic)]
|
||||
#[error(code = "E0123", slug = "foo")]
|
||||
struct UnitField {
|
||||
#[primary_span]
|
||||
spans: Span,
|
||||
#[help]
|
||||
foo: (),
|
||||
#[help = "a"]
|
||||
bar: (),
|
||||
}
|
||||
|
||||
#[derive(SessionDiagnostic)]
|
||||
#[error(code = "E0123", slug = "foo")]
|
||||
struct OptUnitField {
|
||||
#[primary_span]
|
||||
spans: Span,
|
||||
#[help]
|
||||
foo: Option<()>,
|
||||
#[help = "a"]
|
||||
bar: Option<()>,
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue