Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 83 additions & 72 deletions compiler/rustc_macros/src/diagnostics/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use quote::{format_ident, quote};
use std::collections::HashMap;
use std::str::FromStr;
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.
pub(crate) struct SessionDiagnosticDerive<'a> {
Expand Down Expand Up @@ -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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm torn on whether or not we should build up a set like this or just do the same check again but negate the result (putting it into a function to avoid duplication obviously). Any thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm under the impression that having the set should be faster than doing the check again but I don't have any hard evidence on it either. I'm happy with both alternatives anyway.

I checked synstructure to see if we could generate both at the same time to avoid the second filter but it seems that's not the case.


// Generates calls to `span_label` and similar functions based on the attributes
// on fields. Code for suggestions uses formatting machinery and the value of
// 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
// happens below) for Fluent, we want to move the data, so that has to happen
// in a separate pass over the fields.
let attrs = structure.each(|field_binding| {
let field = field_binding.ast();
let result = field.attrs.iter().map(|attr| {
builder
.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);* }
});
// should be accessed through a borrow. When passing fields to `add_subdiagnostic`
// or `set_arg` (which happens below) for Fluent, we want to move the data, so that
// has to happen in a separate pass over the fields.
let attrs = structure
.clone()
.filter(|field_binding| {
pvdrz marked this conversation as resolved.
Show resolved Hide resolved
let attrs = &field_binding.ast().attrs;

(!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);
let args = structure.each(|field_binding| {
let field = field_binding.ast();
// When a field has attributes like `#[label]` or `#[note]` then it doesn't
// need to be passed as an argument to the diagnostic. But when a field has no
// attributes then it must be passed as an argument to the diagnostic so that
// it can be referred to by Fluent messages.
if field.attrs.is_empty() {
let diag = &builder.diag;
let ident = field_binding.ast().ident.as_ref().unwrap();
quote! {
#diag.set_arg(
stringify!(#ident),
#field_binding
);
}
} else {
quote! {}
}
});
// When a field has attributes like `#[label]` or `#[note]` then it doesn't
// need to be passed as an argument to the diagnostic. But when a field has no
// attributes or a `#[subdiagnostic]` attribute then it must be passed as an
// argument to the diagnostic so that it can be referred to by Fluent messages.
let args = structure
.filter(|field_binding| {
subdiagnostics_or_empty.contains(&field_binding.binding)
})
.each(|field_binding| builder.generate_field_attrs_code(field_binding));

let span = ast.span().unwrap();
let (diag, sess) = (&builder.diag, &builder.sess);
Expand Down Expand Up @@ -347,36 +334,60 @@ impl SessionDiagnosticDeriveBuilder {
Ok(tokens.drain(..).collect())
}

fn generate_field_attr_code(
&mut self,
attr: &syn::Attribute,
info: FieldInfo<'_>,
) -> Result<TokenStream, SessionDiagnosticDeriveError> {
let field_binding = &info.binding.binding;
fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> TokenStream {
let field = binding_info.ast();
let field_binding = &binding_info.binding;

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,
FieldInfo {
vis: info.vis,
binding: info.binding,
ty: inner_ty.inner_type().unwrap_or(&info.ty),
span: info.span,
},
binding,
)?;
let inner_ty = FieldInnerTy::from_type(&field.ty);

if needs_destructure {
Ok(inner_ty.with(field_binding, generated_code))
// When generating `set_arg` or `add_subdiagnostic` 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).
if field.attrs.is_empty() {
let diag = &self.diag;
let ident = field.ident.as_ref().unwrap();
quote! {
#diag.set_arg(
stringify!(#ident),
#field_binding
);
}
} 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()
}
}

Expand Down
1 change: 0 additions & 1 deletion compiler/rustc_macros/src/diagnostics/subdiagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@ impl<'a> SessionSubdiagnosticDeriveBuilder<'a> {

let inner_ty = FieldInnerTy::from_type(&ast.ty);
let info = FieldInfo {
vis: &ast.vis,
binding: binding,
ty: inner_ty.inner_type().unwrap_or(&ast.ty),
span: &ast.span(),
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_macros/src/diagnostics/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, TypeTuple, Visibility};
use syn::{spanned::Spanned, Attribute, Meta, Type, TypeTuple};
use synstructure::BindingInfo;

/// Checks whether the type name of `ty` matches `name`.
Expand Down Expand Up @@ -158,7 +158,6 @@ impl<'ty> FieldInnerTy<'ty> {
/// Field information passed to the builder. Deliberately omits attrs to discourage the
/// `generate_*` methods from walking the attributes themselves.
pub(crate) struct FieldInfo<'a> {
pub(crate) vis: &'a Visibility,
pub(crate) binding: &'a BindingInfo<'a>,
pub(crate) ty: &'a Type,
pub(crate) span: &'a proc_macro2::Span,
Expand Down
16 changes: 7 additions & 9 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,23 +254,23 @@ struct AmbiguousPlus {

#[derive(SessionDiagnostic)]
#[error(code = "E0178", slug = "parser-maybe-recover-from-bad-type-plus")]
struct BadTypePlus<'a> {
struct BadTypePlus {
pub ty: String,
#[primary_span]
pub span: Span,
#[subdiagnostic]
pub sub: BadTypePlusSub<'a>,
pub sub: BadTypePlusSub,
}

#[derive(SessionSubdiagnostic, Clone, Copy)]
pub enum BadTypePlusSub<'a> {
#[derive(SessionSubdiagnostic)]
pub enum BadTypePlusSub {
#[suggestion(
slug = "parser-add-paren",
code = "{sum_with_parens}",
applicability = "machine-applicable"
)]
AddParen {
sum_with_parens: &'a str,
sum_with_parens: String,
#[primary_span]
span: Span,
},
Expand Down Expand Up @@ -1289,11 +1289,9 @@ impl<'a> Parser<'a> {
let bounds = self.parse_generic_bounds(None)?;
let sum_span = ty.span.to(self.prev_token.span);

let sum_with_parens: String;

let sub = match ty.kind {
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.print_opt_lifetime(lifetime);
s.print_mutability(mut_ty.mutbl, false);
Expand All @@ -1303,7 +1301,7 @@ impl<'a> Parser<'a> {
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 },
_ => BadTypePlusSub::ExpectPath { span: sum_span },
Expand Down