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:
David Wood 2022-05-07 06:02:11 +01:00
parent 1d2ea98cff
commit 7b7061dd89
4 changed files with 97 additions and 33 deletions

View File

@ -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<'_>,

View File

@ -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.

View File

@ -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"]

View File

@ -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<()>,
}