Auto merge of #97121 - pvdrz:do-subdiagnostics-later, r=davidtwco

Avoid double binding of subdiagnostics inside `#[derive(SessionDiagnostic)]`

r? `@davidtwco`
This commit is contained in:
bors 2022-05-24 10:25:13 +00:00
commit b2eba058e6
4 changed files with 90 additions and 83 deletions

View file

@ -13,7 +13,7 @@ use quote::{format_ident, quote};
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type}; use syn::{spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, Type};
use synstructure::Structure; use synstructure::{BindingInfo, Structure};
/// The central struct for constructing the `into_diagnostic` method from an annotated struct. /// The central struct for constructing the `into_diagnostic` method from an annotated struct.
pub(crate) struct SessionDiagnosticDerive<'a> { pub(crate) struct SessionDiagnosticDerive<'a> {
@ -71,55 +71,42 @@ impl<'a> SessionDiagnosticDerive<'a> {
} }
}; };
// Keep track of which fields are subdiagnostics or have no attributes.
let mut subdiagnostics_or_empty = std::collections::HashSet::new();
// Generates calls to `span_label` and similar functions based on the attributes // Generates calls to `span_label` and similar functions based on the attributes
// on fields. Code for suggestions uses formatting machinery and the value of // on fields. Code for suggestions uses formatting machinery and the value of
// other fields - because any given field can be referenced multiple times, it // other fields - because any given field can be referenced multiple times, it
// should be accessed through a borrow. When passing fields to `set_arg` (which // should be accessed through a borrow. When passing fields to `add_subdiagnostic`
// happens below) for Fluent, we want to move the data, so that has to happen // or `set_arg` (which happens below) for Fluent, we want to move the data, so that
// in a separate pass over the fields. // has to happen in a separate pass over the fields.
let attrs = structure.each(|field_binding| { let attrs = structure
let field = field_binding.ast(); .clone()
let result = field.attrs.iter().map(|attr| { .filter(|field_binding| {
builder let attrs = &field_binding.ast().attrs;
.generate_field_attr_code(
attr,
FieldInfo {
vis: &field.vis,
binding: field_binding,
ty: &field.ty,
span: &field.span(),
},
)
.unwrap_or_else(|v| v.to_compile_error())
});
quote! { #(#result);* } (!attrs.is_empty()
}); && attrs.iter().all(|attr| {
"subdiagnostic"
!= attr.path.segments.last().unwrap().ident.to_string()
}))
|| {
subdiagnostics_or_empty.insert(field_binding.binding.clone());
false
}
})
.each(|field_binding| builder.generate_field_attrs_code(field_binding));
// When generating `set_arg` calls, move data rather than borrow it to avoid
// requiring clones - this must therefore be the last use of each field (for
// example, any formatting machinery that might refer to a field should be
// generated already).
structure.bind_with(|_| synstructure::BindStyle::Move); structure.bind_with(|_| synstructure::BindStyle::Move);
let args = structure.each(|field_binding| { // When a field has attributes like `#[label]` or `#[note]` then it doesn't
let field = field_binding.ast(); // need to be passed as an argument to the diagnostic. But when a field has no
// When a field has attributes like `#[label]` or `#[note]` then it doesn't // attributes or a `#[subdiagnostic]` attribute then it must be passed as an
// need to be passed as an argument to the diagnostic. But when a field has no // argument to the diagnostic so that it can be referred to by Fluent messages.
// attributes then it must be passed as an argument to the diagnostic so that let args = structure
// it can be referred to by Fluent messages. .filter(|field_binding| {
if field.attrs.is_empty() { subdiagnostics_or_empty.contains(&field_binding.binding)
let diag = &builder.diag; })
let ident = field_binding.ast().ident.as_ref().unwrap(); .each(|field_binding| builder.generate_field_attrs_code(field_binding));
quote! {
#diag.set_arg(
stringify!(#ident),
#field_binding
);
}
} else {
quote! {}
}
});
let span = ast.span().unwrap(); let span = ast.span().unwrap();
let (diag, sess) = (&builder.diag, &builder.sess); let (diag, sess) = (&builder.diag, &builder.sess);
@ -347,36 +334,60 @@ impl SessionDiagnosticDeriveBuilder {
Ok(tokens.drain(..).collect()) Ok(tokens.drain(..).collect())
} }
fn generate_field_attr_code( fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
&mut self, let field = binding_info.ast();
attr: &syn::Attribute, let field_binding = &binding_info.binding;
info: FieldInfo<'_>,
) -> Result<TokenStream, SessionDiagnosticDeriveError> {
let field_binding = &info.binding.binding;
let inner_ty = FieldInnerTy::from_type(&info.ty); let inner_ty = FieldInnerTy::from_type(&field.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( // When generating `set_arg` or `add_subdiagnostic` calls, move data rather than
attr, // borrow it to avoid requiring clones - this must therefore be the last use of
FieldInfo { // each field (for example, any formatting machinery that might refer to a field
vis: info.vis, // should be generated already).
binding: info.binding, if field.attrs.is_empty() {
ty: inner_ty.inner_type().unwrap_or(&info.ty), let diag = &self.diag;
span: info.span, let ident = field.ident.as_ref().unwrap();
}, quote! {
binding, #diag.set_arg(
)?; stringify!(#ident),
#field_binding
if needs_destructure { );
Ok(inner_ty.with(field_binding, generated_code)) }
} else { } else {
Ok(generated_code) field
.attrs
.iter()
.map(move |attr| {
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)
}
// `subdiagnostics` are not derefed because they are bound by value.
("subdiagnostic", _) => (quote! { #field_binding }, true),
_ => (quote! { *#field_binding }, true),
};
let generated_code = self
.generate_inner_field_code(
attr,
FieldInfo {
binding: binding_info,
ty: inner_ty.inner_type().unwrap_or(&field.ty),
span: &field.span(),
},
binding,
)
.unwrap_or_else(|v| v.to_compile_error());
if needs_destructure {
inner_ty.with(field_binding, generated_code)
} else {
generated_code
}
})
.collect()
} }
} }

View file

@ -303,7 +303,6 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {
let inner_ty = FieldInnerTy::from_type(&ast.ty); let inner_ty = FieldInnerTy::from_type(&ast.ty);
let info = FieldInfo { let info = FieldInfo {
vis: &ast.vis,
binding: binding, binding: binding,
ty: inner_ty.inner_type().unwrap_or(&ast.ty), ty: inner_ty.inner_type().unwrap_or(&ast.ty),
span: &ast.span(), span: &ast.span(),

View file

@ -4,7 +4,7 @@ use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens}; 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, TypeTuple, Visibility}; use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
use synstructure::BindingInfo; use synstructure::BindingInfo;
/// Checks whether the type name of `ty` matches `name`. /// Checks whether the type name of `ty` matches `name`.
@ -158,7 +158,6 @@ impl<'ty> FieldInnerTy<'ty> {
/// Field information passed to the builder. Deliberately omits attrs to discourage the /// Field information passed to the builder. Deliberately omits attrs to discourage the
/// `generate_*` methods from walking the attributes themselves. /// `generate_*` methods from walking the attributes themselves.
pub(crate) struct FieldInfo<'a> { pub(crate) struct FieldInfo<'a> {
pub(crate) vis: &'a Visibility,
pub(crate) binding: &'a BindingInfo<'a>, pub(crate) binding: &'a BindingInfo<'a>,
pub(crate) ty: &'a Type, pub(crate) ty: &'a Type,
pub(crate) span: &'a proc_macro2::Span, pub(crate) span: &'a proc_macro2::Span,

View file

@ -254,23 +254,23 @@ struct AmbiguousPlus {
#[derive(SessionDiagnostic)] #[derive(SessionDiagnostic)]
#[error(code = "E0178", slug = "parser-maybe-recover-from-bad-type-plus")] #[error(code = "E0178", slug = "parser-maybe-recover-from-bad-type-plus")]
struct BadTypePlus<'a> { struct BadTypePlus {
pub ty: String, pub ty: String,
#[primary_span] #[primary_span]
pub span: Span, pub span: Span,
#[subdiagnostic] #[subdiagnostic]
pub sub: BadTypePlusSub<'a>, pub sub: BadTypePlusSub,
} }
#[derive(SessionSubdiagnostic, Clone, Copy)] #[derive(SessionSubdiagnostic)]
pub enum BadTypePlusSub<'a> { pub enum BadTypePlusSub {
#[suggestion( #[suggestion(
slug = "parser-add-paren", slug = "parser-add-paren",
code = "{sum_with_parens}", code = "{sum_with_parens}",
applicability = "machine-applicable" applicability = "machine-applicable"
)] )]
AddParen { AddParen {
sum_with_parens: &'a str, sum_with_parens: String,
#[primary_span] #[primary_span]
span: Span, span: Span,
}, },
@ -1289,11 +1289,9 @@ impl<'a> Parser<'a> {
let bounds = self.parse_generic_bounds(None)?; let bounds = self.parse_generic_bounds(None)?;
let sum_span = ty.span.to(self.prev_token.span); let sum_span = ty.span.to(self.prev_token.span);
let sum_with_parens: String;
let sub = match ty.kind { let sub = match ty.kind {
TyKind::Rptr(ref lifetime, ref mut_ty) => { TyKind::Rptr(ref lifetime, ref mut_ty) => {
sum_with_parens = pprust::to_string(|s| { let sum_with_parens = pprust::to_string(|s| {
s.s.word("&"); s.s.word("&");
s.print_opt_lifetime(lifetime); s.print_opt_lifetime(lifetime);
s.print_mutability(mut_ty.mutbl, false); s.print_mutability(mut_ty.mutbl, false);
@ -1303,7 +1301,7 @@ impl<'a> Parser<'a> {
s.pclose() s.pclose()
}); });
BadTypePlusSub::AddParen { sum_with_parens: &sum_with_parens, span: sum_span } BadTypePlusSub::AddParen { sum_with_parens, span: sum_span }
} }
TyKind::Ptr(..) | TyKind::BareFn(..) => BadTypePlusSub::ForgotParen { span: sum_span }, TyKind::Ptr(..) | TyKind::BareFn(..) => BadTypePlusSub::ForgotParen { span: sum_span },
_ => BadTypePlusSub::ExpectPath { span: sum_span }, _ => BadTypePlusSub::ExpectPath { span: sum_span },