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::collections::HashMap;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use syn::{
|
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};
|
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) {
|
pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, TokenStream) {
|
||||||
// Keep track of which fields are subdiagnostics or have no attributes.
|
// Keep track of which fields need to be handled with a by-move binding.
|
||||||
let mut subdiagnostics_or_empty = std::collections::HashSet::new();
|
let mut needs_moved = 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
|
||||||
@ -92,16 +93,11 @@ pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, Token
|
|||||||
let attrs = structure
|
let attrs = structure
|
||||||
.clone()
|
.clone()
|
||||||
.filter(|field_binding| {
|
.filter(|field_binding| {
|
||||||
let attrs = &field_binding.ast().attrs;
|
let ast = &field_binding.ast();
|
||||||
|
!self.needs_move(ast) || {
|
||||||
(!attrs.is_empty()
|
needs_moved.insert(field_binding.binding.clone());
|
||||||
&& attrs.iter().all(|attr| {
|
false
|
||||||
"subdiagnostic" != attr.path.segments.last().unwrap().ident.to_string()
|
}
|
||||||
}))
|
|
||||||
|| {
|
|
||||||
subdiagnostics_or_empty.insert(field_binding.binding.clone());
|
|
||||||
false
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.each(|field_binding| self.generate_field_attrs_code(field_binding));
|
.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
|
// 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.
|
// argument to the diagnostic so that it can be referred to by Fluent messages.
|
||||||
let args = structure
|
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));
|
.each(|field_binding| self.generate_field_attrs_code(field_binding));
|
||||||
|
|
||||||
(attrs, args)
|
(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
|
/// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
|
||||||
/// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
|
/// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
|
||||||
/// diagnostic builder calls for setting error code and creating note/help messages.
|
/// 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_info.ast();
|
||||||
let field_binding = &binding_info.binding;
|
let field_binding = &binding_info.binding;
|
||||||
|
|
||||||
let inner_ty = FieldInnerTy::from_type(&field.ty);
|
if self.should_generate_set_arg(&field) {
|
||||||
|
|
||||||
// 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 diag = &self.diag;
|
||||||
let ident = field.ident.as_ref().unwrap();
|
let ident = field.ident.as_ref().unwrap();
|
||||||
quote! {
|
return quote! {
|
||||||
#diag.set_arg(
|
#diag.set_arg(
|
||||||
stringify!(#ident),
|
stringify!(#ident),
|
||||||
#field_binding
|
#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(
|
fn generate_inner_field_code(
|
||||||
|
@ -85,7 +85,13 @@ pub(crate) fn report_error_if_not_applied_to_span(
|
|||||||
attr: &Attribute,
|
attr: &Attribute,
|
||||||
info: &FieldInfo<'_>,
|
info: &FieldInfo<'_>,
|
||||||
) -> Result<(), DiagnosticDeriveError> {
|
) -> 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.
|
/// Inner type of a field and type of wrapper.
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
use rustc_middle::ty::Ty;
|
use rustc_middle::ty::Ty;
|
||||||
|
|
||||||
extern crate rustc_errors;
|
extern crate rustc_errors;
|
||||||
use rustc_errors::Applicability;
|
use rustc_errors::{Applicability, MultiSpan};
|
||||||
|
|
||||||
extern crate rustc_session;
|
extern crate rustc_session;
|
||||||
|
|
||||||
@ -140,7 +140,7 @@ struct CodeNotProvided {}
|
|||||||
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
|
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
|
||||||
struct MessageWrongType {
|
struct MessageWrongType {
|
||||||
#[primary_span]
|
#[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,
|
foo: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ struct ErrorWithField {
|
|||||||
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
|
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
|
||||||
struct ErrorWithMessageAppliedToField {
|
struct ErrorWithMessageAppliedToField {
|
||||||
#[label(typeck::label)]
|
#[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,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,7 +208,7 @@ struct LabelOnSpan {
|
|||||||
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
|
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
|
||||||
struct LabelOnNonSpan {
|
struct LabelOnNonSpan {
|
||||||
#[label(typeck::label)]
|
#[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,
|
id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,3 +552,10 @@ struct LintsGood {
|
|||||||
//~^ ERROR only `#[lint(..)]` is supported
|
//~^ ERROR only `#[lint(..)]` is supported
|
||||||
struct ErrorsBad {
|
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)]`
|
= 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
|
--> $DIR/diagnostic-derive.rs:142:5
|
||||||
|
|
|
|
||||||
LL | #[primary_span]
|
LL | #[primary_span]
|
||||||
@ -247,7 +247,7 @@ LL | #[nonsense]
|
|||||||
|
|
|
|
||||||
= 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
|
||||||
|
|
||||||
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
|
--> $DIR/diagnostic-derive.rs:167:5
|
||||||
|
|
|
|
||||||
LL | #[label(typeck::label)]
|
LL | #[label(typeck::label)]
|
||||||
@ -279,7 +279,7 @@ LL | #[derive(SessionDiagnostic)]
|
|||||||
= note: if you intended to print `}`, you can escape it using `}}`
|
= 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)
|
= 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
|
--> $DIR/diagnostic-derive.rs:210:5
|
||||||
|
|
|
|
||||||
LL | #[label(typeck::label)]
|
LL | #[label(typeck::label)]
|
||||||
|
@ -244,7 +244,7 @@ enum V {
|
|||||||
//~^ ERROR label without `#[primary_span]` field
|
//~^ ERROR label without `#[primary_span]` field
|
||||||
struct W {
|
struct W {
|
||||||
#[primary_span]
|
#[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,
|
span: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@ error: subdiagnostic kind not specified
|
|||||||
LL | B {
|
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
|
--> $DIR/subdiagnostic-derive.rs:246:5
|
||||||
|
|
|
|
||||||
LL | #[primary_span]
|
LL | #[primary_span]
|
||||||
|
Loading…
Reference in New Issue
Block a user