mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-22 14:55:26 +00:00
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:
parent
3dac70fcc0
commit
859079ff12
@ -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)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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> {
|
||||||
|
@ -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
|
||||||
|
@ -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>,
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user