macros: allow Vec fields in diagnostic derive

Diagnostics can have multiple primary spans, or have subdiagnostics
repeated at multiple locations, so support `Vec<..>` fields in the
diagnostic derive which become loops in the generated code.

Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
David Wood 2022-05-06 03:43:30 +01:00
parent 3dac70fcc0
commit 859079ff12
4 changed files with 93 additions and 39 deletions

View File

@ -5,8 +5,8 @@ use crate::diagnostics::error::{
SessionDiagnosticDeriveError, SessionDiagnosticDeriveError,
}; };
use crate::diagnostics::utils::{ use crate::diagnostics::utils::{
option_inner_ty, report_error_if_not_applied_to_span, type_matches_path, Applicability, report_error_if_not_applied_to_span, type_matches_path, Applicability, FieldInfo, FieldInnerTy,
FieldInfo, HasFieldMap, SetOnce, HasFieldMap, SetOnce,
}; };
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
@ -353,35 +353,40 @@ impl SessionDiagnosticDeriveBuilder {
info: FieldInfo<'_>, info: FieldInfo<'_>,
) -> Result<TokenStream, SessionDiagnosticDeriveError> { ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
let field_binding = &info.binding.binding; let field_binding = &info.binding.binding;
let option_ty = option_inner_ty(&info.ty);
let generated_code = self.generate_non_option_field_code( let inner_ty = FieldInnerTy::from_type(&info.ty);
let name = attr.path.segments.last().unwrap().ident.to_string();
let (binding, needs_destructure) = match (name.as_str(), &inner_ty) {
// `primary_span` can accept a `Vec<Span>` so don't destructure that.
("primary_span", FieldInnerTy::Vec(_)) => (quote! { #field_binding.clone() }, false),
_ => (quote! { *#field_binding }, true),
};
let generated_code = self.generate_inner_field_code(
attr, attr,
FieldInfo { FieldInfo {
vis: info.vis, vis: info.vis,
binding: info.binding, binding: info.binding,
ty: option_ty.unwrap_or(&info.ty), ty: inner_ty.inner_type().unwrap_or(&info.ty),
span: info.span, span: info.span,
}, },
binding,
)?; )?;
if option_ty.is_none() { if needs_destructure {
Ok(quote! { #generated_code }) Ok(inner_ty.with(field_binding, generated_code))
} else { } else {
Ok(quote! { Ok(generated_code)
if let Some(#field_binding) = #field_binding {
#generated_code
}
})
} }
} }
fn generate_non_option_field_code( fn generate_inner_field_code(
&mut self, &mut self,
attr: &Attribute, attr: &Attribute,
info: FieldInfo<'_>, info: FieldInfo<'_>,
binding: TokenStream,
) -> Result<TokenStream, SessionDiagnosticDeriveError> { ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
let diag = &self.diag; let diag = &self.diag;
let field_binding = &info.binding.binding;
let name = attr.path.segments.last().unwrap().ident.to_string(); let name = attr.path.segments.last().unwrap().ident.to_string();
let name = name.as_str(); let name = name.as_str();
@ -397,14 +402,14 @@ impl SessionDiagnosticDeriveBuilder {
"primary_span" => { "primary_span" => {
report_error_if_not_applied_to_span(attr, &info)?; report_error_if_not_applied_to_span(attr, &info)?;
Ok(quote! { Ok(quote! {
#diag.set_span(*#field_binding); #diag.set_span(#binding);
}) })
} }
"label" | "note" | "help" => { "label" | "note" | "help" => {
report_error_if_not_applied_to_span(attr, &info)?; report_error_if_not_applied_to_span(attr, &info)?;
Ok(self.add_subdiagnostic(field_binding, name, name)) Ok(self.add_subdiagnostic(binding, name, name))
} }
"subdiagnostic" => Ok(quote! { #diag.subdiagnostic(*#field_binding); }), "subdiagnostic" => Ok(quote! { #diag.subdiagnostic(#binding); }),
_ => throw_invalid_attr!(attr, &meta, |diag| { _ => throw_invalid_attr!(attr, &meta, |diag| {
diag diag
.help("only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes") .help("only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes")
@ -413,7 +418,7 @@ impl SessionDiagnosticDeriveBuilder {
Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name { Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(ref s), .. }) => match name {
"label" | "note" | "help" => { "label" | "note" | "help" => {
report_error_if_not_applied_to_span(attr, &info)?; report_error_if_not_applied_to_span(attr, &info)?;
Ok(self.add_subdiagnostic(field_binding, name, &s.value())) Ok(self.add_subdiagnostic(binding, name, &s.value()))
} }
_ => throw_invalid_attr!(attr, &meta, |diag| { _ => throw_invalid_attr!(attr, &meta, |diag| {
diag.help("only `label`, `note` and `help` are valid field attributes") diag.help("only `label`, `note` and `help` are valid field attributes")
@ -509,7 +514,7 @@ impl SessionDiagnosticDeriveBuilder {
/// `fluent_attr_identifier`. /// `fluent_attr_identifier`.
fn add_subdiagnostic( fn add_subdiagnostic(
&self, &self,
field_binding: &proc_macro2::Ident, field_binding: TokenStream,
kind: &str, kind: &str,
fluent_attr_identifier: &str, fluent_attr_identifier: &str,
) -> TokenStream { ) -> TokenStream {
@ -520,7 +525,7 @@ impl SessionDiagnosticDeriveBuilder {
let fn_name = format_ident!("span_{}", kind); let fn_name = format_ident!("span_{}", kind);
quote! { quote! {
#diag.#fn_name( #diag.#fn_name(
*#field_binding, #field_binding,
rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier) rustc_errors::DiagnosticMessage::fluent_attr(#slug, #fluent_attr_identifier)
); );
} }

View File

@ -5,8 +5,8 @@ use crate::diagnostics::error::{
SessionDiagnosticDeriveError, SessionDiagnosticDeriveError,
}; };
use crate::diagnostics::utils::{ use crate::diagnostics::utils::{
option_inner_ty, report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_applicability, report_error_if_not_applied_to_span,
report_error_if_not_applied_to_span, Applicability, FieldInfo, HasFieldMap, SetOnce, Applicability, FieldInfo, FieldInnerTy, HasFieldMap, SetOnce,
}; };
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote};
@ -301,11 +301,11 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
) -> Result<TokenStream, SessionDiagnosticDeriveError> { ) -> Result<TokenStream, SessionDiagnosticDeriveError> {
let ast = binding.ast(); let ast = binding.ast();
let option_ty = option_inner_ty(&ast.ty); let inner_ty = FieldInnerTy::from_type(&ast.ty);
let info = FieldInfo { let info = FieldInfo {
vis: &ast.vis, vis: &ast.vis,
binding: binding, binding: binding,
ty: option_ty.unwrap_or(&ast.ty), ty: inner_ty.inner_type().unwrap_or(&ast.ty),
span: &ast.span(), span: &ast.span(),
}; };
@ -353,15 +353,7 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
); );
}; };
if option_ty.is_none() { Ok(inner_ty.with(binding, generated))
Ok(quote! { #generated })
} else {
Ok(quote! {
if let Some(#binding) = #binding {
#generated
}
})
}
} }
fn into_tokens(&mut self) -> Result<TokenStream, SessionDiagnosticDeriveError> { fn into_tokens(&mut self) -> Result<TokenStream, SessionDiagnosticDeriveError> {

View File

@ -1,7 +1,7 @@
use crate::diagnostics::error::{span_err, throw_span_err, SessionDiagnosticDeriveError}; use crate::diagnostics::error::{span_err, throw_span_err, SessionDiagnosticDeriveError};
use proc_macro::Span; use proc_macro::Span;
use proc_macro2::TokenStream; use proc_macro2::TokenStream;
use quote::{format_ident, quote}; use quote::{format_ident, quote, ToTokens};
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::str::FromStr; use std::str::FromStr;
use syn::{spanned::Spanned, Attribute, Meta, Type, Visibility}; use syn::{spanned::Spanned, Attribute, Meta, Type, Visibility};
@ -76,22 +76,71 @@ pub(crate) fn report_error_if_not_applied_to_span(
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")
} }
/// If `ty` is an Option, returns `Some(inner type)`, otherwise returns `None`. /// Inner type of a field and type of wrapper.
pub(crate) fn option_inner_ty(ty: &Type) -> Option<&Type> { pub(crate) enum FieldInnerTy<'ty> {
/// Field is wrapped in a `Option<$inner>`.
Option(&'ty Type),
/// Field is wrapped in a `Vec<$inner>`.
Vec(&'ty Type),
/// Field isn't wrapped in an outer type.
None,
}
impl<'ty> FieldInnerTy<'ty> {
/// Returns inner type for a field, if there is one.
///
/// - If `ty` is an `Option`, returns `FieldInnerTy::Option { inner: (inner type) }`.
/// - If `ty` is a `Vec`, returns `FieldInnerTy::Vec { inner: (inner type) }`.
/// - Otherwise returns `None`.
pub(crate) fn from_type(ty: &'ty Type) -> Self {
let variant: &dyn Fn(&'ty Type) -> FieldInnerTy<'ty> =
if type_matches_path(ty, &["std", "option", "Option"]) { if type_matches_path(ty, &["std", "option", "Option"]) {
&FieldInnerTy::Option
} else if type_matches_path(ty, &["std", "vec", "Vec"]) {
&FieldInnerTy::Vec
} else {
return FieldInnerTy::None;
};
if let Type::Path(ty_path) = ty { if let Type::Path(ty_path) = ty {
let path = &ty_path.path; let path = &ty_path.path;
let ty = path.segments.iter().last().unwrap(); let ty = path.segments.iter().last().unwrap();
if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments { if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments {
if bracketed.args.len() == 1 { if bracketed.args.len() == 1 {
if let syn::GenericArgument::Type(ty) = &bracketed.args[0] { if let syn::GenericArgument::Type(ty) = &bracketed.args[0] {
return Some(ty); return variant(ty);
} }
} }
} }
} }
unreachable!();
}
/// Returns `Option` containing inner type if there is one.
pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
match self {
FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) => Some(inner),
FieldInnerTy::None => None,
}
}
/// Surrounds `inner` with destructured wrapper type, exposing inner type as `binding`.
pub(crate) fn with(&self, binding: impl ToTokens, inner: impl ToTokens) -> TokenStream {
match self {
FieldInnerTy::Option(..) => quote! {
if let Some(#binding) = #binding {
#inner
}
},
FieldInnerTy::Vec(..) => quote! {
for #binding in #binding {
#inner
}
},
FieldInnerTy::None => quote! { #inner },
}
} }
None
} }
/// Field information passed to the builder. Deliberately omits attrs to discourage the /// Field information passed to the builder. Deliberately omits attrs to discourage the

View File

@ -474,3 +474,11 @@ struct Subdiagnostic {
#[subdiagnostic] #[subdiagnostic]
note: Note, note: Note,
} }
#[derive(SessionDiagnostic)]
#[error(code = "E0123", slug = "foo")]
struct VecField {
#[primary_span]
#[label]
spans: Vec<Span>,
}