macros: support MultiSpan
in diag derives
Add support for `MultiSpan` with any of the attributes that work on a `Span` - requires that diagnostic logic generated for these attributes are emitted in the by-move block rather than the by-ref block that they would normally have been generated in. Signed-off-by: David Wood <david.wood@huawei.com>
This commit is contained in:
parent
c3fdf74885
commit
88c11c5bff
@ -13,7 +13,8 @@
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use syn::{
|
||||
parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
|
||||
parse_quote, spanned::Spanned, Attribute, Field, Meta, MetaList, MetaNameValue, NestedMeta,
|
||||
Path, Type,
|
||||
};
|
||||
use synstructure::{BindingInfo, Structure};
|
||||
|
||||
@ -80,8 +81,8 @@ pub fn preamble<'s>(&mut self, structure: &Structure<'s>) -> TokenStream {
|
||||
}
|
||||
|
||||
pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, TokenStream) {
|
||||
// Keep track of which fields are subdiagnostics or have no attributes.
|
||||
let mut subdiagnostics_or_empty = std::collections::HashSet::new();
|
||||
// Keep track of which fields need to be handled with a by-move binding.
|
||||
let mut needs_moved = std::collections::HashSet::new();
|
||||
|
||||
// Generates calls to `span_label` and similar functions based on the attributes
|
||||
// on fields. Code for suggestions uses formatting machinery and the value of
|
||||
@ -92,16 +93,11 @@ pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, Token
|
||||
let attrs = structure
|
||||
.clone()
|
||||
.filter(|field_binding| {
|
||||
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
|
||||
}
|
||||
let ast = &field_binding.ast();
|
||||
!self.needs_move(ast) || {
|
||||
needs_moved.insert(field_binding.binding.clone());
|
||||
false
|
||||
}
|
||||
})
|
||||
.each(|field_binding| self.generate_field_attrs_code(field_binding));
|
||||
|
||||
@ -111,12 +107,41 @@ pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, Token
|
||||
// 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))
|
||||
.filter(|field_binding| needs_moved.contains(&field_binding.binding))
|
||||
.each(|field_binding| self.generate_field_attrs_code(field_binding));
|
||||
|
||||
(attrs, args)
|
||||
}
|
||||
|
||||
/// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
|
||||
/// call (like `span_label`).
|
||||
fn should_generate_set_arg(&self, field: &Field) -> bool {
|
||||
field.attrs.is_empty()
|
||||
}
|
||||
|
||||
/// Returns `true` if `field` needs to have code generated in the by-move branch of the
|
||||
/// generated derive rather than the by-ref branch.
|
||||
fn needs_move(&self, field: &Field) -> bool {
|
||||
let generates_set_arg = self.should_generate_set_arg(field);
|
||||
let is_multispan = type_matches_path(&field.ty, &["rustc_errors", "MultiSpan"]);
|
||||
// FIXME(davidtwco): better support for one field needing to be in the by-move and
|
||||
// by-ref branches.
|
||||
let is_subdiagnostic = field
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|attr| attr.path.segments.last().unwrap().ident.to_string())
|
||||
.any(|attr| attr == "subdiagnostic");
|
||||
|
||||
// `set_arg` calls take their argument by-move..
|
||||
generates_set_arg
|
||||
// If this is a `MultiSpan` field then it needs to be moved to be used by any
|
||||
// attribute..
|
||||
|| is_multispan
|
||||
// If this a `#[subdiagnostic]` then it needs to be moved as the other diagnostic is
|
||||
// unlikely to be `Copy`..
|
||||
|| is_subdiagnostic
|
||||
}
|
||||
|
||||
/// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
|
||||
/// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
|
||||
/// diagnostic builder calls for setting error code and creating note/help messages.
|
||||
@ -227,57 +252,55 @@ fn generate_field_attrs_code(&mut self, binding_info: &BindingInfo<'_>) -> Token
|
||||
let field = binding_info.ast();
|
||||
let field_binding = &binding_info.binding;
|
||||
|
||||
let inner_ty = FieldInnerTy::from_type(&field.ty);
|
||||
|
||||
// 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() {
|
||||
if self.should_generate_set_arg(&field) {
|
||||
let diag = &self.diag;
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
quote! {
|
||||
return quote! {
|
||||
#diag.set_arg(
|
||||
stringify!(#ident),
|
||||
#field_binding
|
||||
);
|
||||
}
|
||||
} else {
|
||||
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()
|
||||
};
|
||||
}
|
||||
|
||||
let needs_move = self.needs_move(&field);
|
||||
let inner_ty = FieldInnerTy::from_type(&field.ty);
|
||||
|
||||
field
|
||||
.attrs
|
||||
.iter()
|
||||
.map(move |attr| {
|
||||
let name = attr.path.segments.last().unwrap().ident.to_string();
|
||||
let needs_clone =
|
||||
name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_));
|
||||
let (binding, needs_destructure) = if needs_clone {
|
||||
// `primary_span` can accept a `Vec<Span>` so don't destructure that.
|
||||
(quote! { #field_binding.clone() }, false)
|
||||
} else if needs_move {
|
||||
(quote! { #field_binding }, true)
|
||||
} else {
|
||||
(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()
|
||||
}
|
||||
|
||||
fn generate_inner_field_code(
|
||||
|
@ -85,7 +85,13 @@ pub(crate) fn report_error_if_not_applied_to_span(
|
||||
attr: &Attribute,
|
||||
info: &FieldInfo<'_>,
|
||||
) -> Result<(), DiagnosticDeriveError> {
|
||||
report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "`Span`")
|
||||
if !type_matches_path(&info.ty, &["rustc_span", "Span"])
|
||||
&& !type_matches_path(&info.ty, &["rustc_errors", "MultiSpan"])
|
||||
{
|
||||
report_type_error(attr, "`Span` or `MultiSpan`")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Inner type of a field and type of wrapper.
|
||||
|
@ -23,7 +23,7 @@
|
||||
use rustc_middle::ty::Ty;
|
||||
|
||||
extern crate rustc_errors;
|
||||
use rustc_errors::Applicability;
|
||||
use rustc_errors::{Applicability, MultiSpan};
|
||||
|
||||
extern crate rustc_session;
|
||||
|
||||
@ -140,7 +140,7 @@ struct CodeNotProvided {}
|
||||
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
|
||||
struct MessageWrongType {
|
||||
#[primary_span]
|
||||
//~^ ERROR `#[primary_span]` attribute can only be applied to fields of type `Span`
|
||||
//~^ ERROR `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
|
||||
foo: String,
|
||||
}
|
||||
|
||||
@ -165,7 +165,7 @@ struct ErrorWithField {
|
||||
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
|
||||
struct ErrorWithMessageAppliedToField {
|
||||
#[label(typeck::label)]
|
||||
//~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span`
|
||||
//~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
|
||||
name: String,
|
||||
}
|
||||
|
||||
@ -208,7 +208,7 @@ struct LabelOnSpan {
|
||||
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
|
||||
struct LabelOnNonSpan {
|
||||
#[label(typeck::label)]
|
||||
//~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span`
|
||||
//~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
|
||||
id: u32,
|
||||
}
|
||||
|
||||
@ -552,3 +552,10 @@ struct LintsGood {
|
||||
//~^ ERROR only `#[lint(..)]` is supported
|
||||
struct ErrorsBad {
|
||||
}
|
||||
|
||||
#[derive(SessionDiagnostic)]
|
||||
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
|
||||
struct ErrorWithMultiSpan {
|
||||
#[primary_span]
|
||||
span: MultiSpan,
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ LL | | struct SlugNotProvided {}
|
||||
|
|
||||
= help: specify the slug as the first argument to the attribute, such as `#[error(typeck::example_error)]`
|
||||
|
||||
error: the `#[primary_span]` attribute can only be applied to fields of type `Span`
|
||||
error: the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
|
||||
--> $DIR/diagnostic-derive.rs:142:5
|
||||
|
|
||||
LL | #[primary_span]
|
||||
@ -247,7 +247,7 @@ LL | #[nonsense]
|
||||
|
|
||||
= help: only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes
|
||||
|
||||
error: the `#[label(...)]` attribute can only be applied to fields of type `Span`
|
||||
error: the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
|
||||
--> $DIR/diagnostic-derive.rs:167:5
|
||||
|
|
||||
LL | #[label(typeck::label)]
|
||||
@ -279,7 +279,7 @@ LL | #[derive(SessionDiagnostic)]
|
||||
= note: if you intended to print `}`, you can escape it using `}}`
|
||||
= note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: the `#[label(...)]` attribute can only be applied to fields of type `Span`
|
||||
error: the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
|
||||
--> $DIR/diagnostic-derive.rs:210:5
|
||||
|
|
||||
LL | #[label(typeck::label)]
|
||||
|
@ -244,7 +244,7 @@ enum V {
|
||||
//~^ ERROR label without `#[primary_span]` field
|
||||
struct W {
|
||||
#[primary_span]
|
||||
//~^ ERROR the `#[primary_span]` attribute can only be applied to fields of type `Span`
|
||||
//~^ ERROR the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
|
||||
span: String,
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ error: subdiagnostic kind not specified
|
||||
LL | B {
|
||||
| ^
|
||||
|
||||
error: the `#[primary_span]` attribute can only be applied to fields of type `Span`
|
||||
error: the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
|
||||
--> $DIR/subdiagnostic-derive.rs:246:5
|
||||
|
|
||||
LL | #[primary_span]
|
||||
|
Loading…
Reference in New Issue
Block a user