Skip to content

Commit 88c11c5

Browse files
committed
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 <[email protected]>
1 parent c3fdf74 commit 88c11c5

File tree

6 files changed

+104
-68
lines changed

6 files changed

+104
-68
lines changed

compiler/rustc_macros/src/diagnostics/diagnostic_builder.rs

+81-58
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ use quote::{format_ident, quote};
1313
use std::collections::HashMap;
1414
use std::str::FromStr;
1515
use syn::{
16-
parse_quote, spanned::Spanned, Attribute, Meta, MetaList, MetaNameValue, NestedMeta, Path, Type,
16+
parse_quote, spanned::Spanned, Attribute, Field, Meta, MetaList, MetaNameValue, NestedMeta,
17+
Path, Type,
1718
};
1819
use synstructure::{BindingInfo, Structure};
1920

@@ -80,8 +81,8 @@ impl DiagnosticDeriveBuilder {
8081
}
8182

8283
pub fn body<'s>(&mut self, structure: &mut Structure<'s>) -> (TokenStream, TokenStream) {
83-
// Keep track of which fields are subdiagnostics or have no attributes.
84-
let mut subdiagnostics_or_empty = std::collections::HashSet::new();
84+
// Keep track of which fields need to be handled with a by-move binding.
85+
let mut needs_moved = std::collections::HashSet::new();
8586

8687
// Generates calls to `span_label` and similar functions based on the attributes
8788
// on fields. Code for suggestions uses formatting machinery and the value of
@@ -92,16 +93,11 @@ impl DiagnosticDeriveBuilder {
9293
let attrs = structure
9394
.clone()
9495
.filter(|field_binding| {
95-
let attrs = &field_binding.ast().attrs;
96-
97-
(!attrs.is_empty()
98-
&& attrs.iter().all(|attr| {
99-
"subdiagnostic" != attr.path.segments.last().unwrap().ident.to_string()
100-
}))
101-
|| {
102-
subdiagnostics_or_empty.insert(field_binding.binding.clone());
103-
false
104-
}
96+
let ast = &field_binding.ast();
97+
!self.needs_move(ast) || {
98+
needs_moved.insert(field_binding.binding.clone());
99+
false
100+
}
105101
})
106102
.each(|field_binding| self.generate_field_attrs_code(field_binding));
107103

@@ -111,12 +107,41 @@ impl DiagnosticDeriveBuilder {
111107
// attributes or a `#[subdiagnostic]` attribute then it must be passed as an
112108
// argument to the diagnostic so that it can be referred to by Fluent messages.
113109
let args = structure
114-
.filter(|field_binding| subdiagnostics_or_empty.contains(&field_binding.binding))
110+
.filter(|field_binding| needs_moved.contains(&field_binding.binding))
115111
.each(|field_binding| self.generate_field_attrs_code(field_binding));
116112

117113
(attrs, args)
118114
}
119115

116+
/// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
117+
/// call (like `span_label`).
118+
fn should_generate_set_arg(&self, field: &Field) -> bool {
119+
field.attrs.is_empty()
120+
}
121+
122+
/// Returns `true` if `field` needs to have code generated in the by-move branch of the
123+
/// generated derive rather than the by-ref branch.
124+
fn needs_move(&self, field: &Field) -> bool {
125+
let generates_set_arg = self.should_generate_set_arg(field);
126+
let is_multispan = type_matches_path(&field.ty, &["rustc_errors", "MultiSpan"]);
127+
// FIXME(davidtwco): better support for one field needing to be in the by-move and
128+
// by-ref branches.
129+
let is_subdiagnostic = field
130+
.attrs
131+
.iter()
132+
.map(|attr| attr.path.segments.last().unwrap().ident.to_string())
133+
.any(|attr| attr == "subdiagnostic");
134+
135+
// `set_arg` calls take their argument by-move..
136+
generates_set_arg
137+
// If this is a `MultiSpan` field then it needs to be moved to be used by any
138+
// attribute..
139+
|| is_multispan
140+
// If this a `#[subdiagnostic]` then it needs to be moved as the other diagnostic is
141+
// unlikely to be `Copy`..
142+
|| is_subdiagnostic
143+
}
144+
120145
/// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
121146
/// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
122147
/// diagnostic builder calls for setting error code and creating note/help messages.
@@ -227,57 +252,55 @@ impl DiagnosticDeriveBuilder {
227252
let field = binding_info.ast();
228253
let field_binding = &binding_info.binding;
229254

230-
let inner_ty = FieldInnerTy::from_type(&field.ty);
231-
232-
// When generating `set_arg` or `add_subdiagnostic` calls, move data rather than
233-
// borrow it to avoid requiring clones - this must therefore be the last use of
234-
// each field (for example, any formatting machinery that might refer to a field
235-
// should be generated already).
236-
if field.attrs.is_empty() {
255+
if self.should_generate_set_arg(&field) {
237256
let diag = &self.diag;
238257
let ident = field.ident.as_ref().unwrap();
239-
quote! {
258+
return quote! {
240259
#diag.set_arg(
241260
stringify!(#ident),
242261
#field_binding
243262
);
244-
}
245-
} else {
246-
field
247-
.attrs
248-
.iter()
249-
.map(move |attr| {
250-
let name = attr.path.segments.last().unwrap().ident.to_string();
251-
let (binding, needs_destructure) = match (name.as_str(), &inner_ty) {
252-
// `primary_span` can accept a `Vec<Span>` so don't destructure that.
253-
("primary_span", FieldInnerTy::Vec(_)) => {
254-
(quote! { #field_binding.clone() }, false)
255-
}
256-
// `subdiagnostics` are not derefed because they are bound by value.
257-
("subdiagnostic", _) => (quote! { #field_binding }, true),
258-
_ => (quote! { *#field_binding }, true),
259-
};
260-
261-
let generated_code = self
262-
.generate_inner_field_code(
263-
attr,
264-
FieldInfo {
265-
binding: binding_info,
266-
ty: inner_ty.inner_type().unwrap_or(&field.ty),
267-
span: &field.span(),
268-
},
269-
binding,
270-
)
271-
.unwrap_or_else(|v| v.to_compile_error());
272-
273-
if needs_destructure {
274-
inner_ty.with(field_binding, generated_code)
275-
} else {
276-
generated_code
277-
}
278-
})
279-
.collect()
263+
};
280264
}
265+
266+
let needs_move = self.needs_move(&field);
267+
let inner_ty = FieldInnerTy::from_type(&field.ty);
268+
269+
field
270+
.attrs
271+
.iter()
272+
.map(move |attr| {
273+
let name = attr.path.segments.last().unwrap().ident.to_string();
274+
let needs_clone =
275+
name == "primary_span" && matches!(inner_ty, FieldInnerTy::Vec(_));
276+
let (binding, needs_destructure) = if needs_clone {
277+
// `primary_span` can accept a `Vec<Span>` so don't destructure that.
278+
(quote! { #field_binding.clone() }, false)
279+
} else if needs_move {
280+
(quote! { #field_binding }, true)
281+
} else {
282+
(quote! { *#field_binding }, true)
283+
};
284+
285+
let generated_code = self
286+
.generate_inner_field_code(
287+
attr,
288+
FieldInfo {
289+
binding: binding_info,
290+
ty: inner_ty.inner_type().unwrap_or(&field.ty),
291+
span: &field.span(),
292+
},
293+
binding,
294+
)
295+
.unwrap_or_else(|v| v.to_compile_error());
296+
297+
if needs_destructure {
298+
inner_ty.with(field_binding, generated_code)
299+
} else {
300+
generated_code
301+
}
302+
})
303+
.collect()
281304
}
282305

283306
fn generate_inner_field_code(

compiler/rustc_macros/src/diagnostics/utils.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,13 @@ pub(crate) fn report_error_if_not_applied_to_span(
8585
attr: &Attribute,
8686
info: &FieldInfo<'_>,
8787
) -> Result<(), DiagnosticDeriveError> {
88-
report_error_if_not_applied_to_ty(attr, info, &["rustc_span", "Span"], "`Span`")
88+
if !type_matches_path(&info.ty, &["rustc_span", "Span"])
89+
&& !type_matches_path(&info.ty, &["rustc_errors", "MultiSpan"])
90+
{
91+
report_type_error(attr, "`Span` or `MultiSpan`")?;
92+
}
93+
94+
Ok(())
8995
}
9096

9197
/// Inner type of a field and type of wrapper.

src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extern crate rustc_middle;
2323
use rustc_middle::ty::Ty;
2424

2525
extern crate rustc_errors;
26-
use rustc_errors::Applicability;
26+
use rustc_errors::{Applicability, MultiSpan};
2727

2828
extern crate rustc_session;
2929

@@ -140,7 +140,7 @@ struct CodeNotProvided {}
140140
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
141141
struct MessageWrongType {
142142
#[primary_span]
143-
//~^ ERROR `#[primary_span]` attribute can only be applied to fields of type `Span`
143+
//~^ ERROR `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
144144
foo: String,
145145
}
146146

@@ -165,7 +165,7 @@ struct ErrorWithField {
165165
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
166166
struct ErrorWithMessageAppliedToField {
167167
#[label(typeck::label)]
168-
//~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span`
168+
//~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
169169
name: String,
170170
}
171171

@@ -208,7 +208,7 @@ struct LabelOnSpan {
208208
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
209209
struct LabelOnNonSpan {
210210
#[label(typeck::label)]
211-
//~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span`
211+
//~^ ERROR the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
212212
id: u32,
213213
}
214214

@@ -552,3 +552,10 @@ struct LintsGood {
552552
//~^ ERROR only `#[lint(..)]` is supported
553553
struct ErrorsBad {
554554
}
555+
556+
#[derive(SessionDiagnostic)]
557+
#[error(typeck::ambiguous_lifetime_bound, code = "E0123")]
558+
struct ErrorWithMultiSpan {
559+
#[primary_span]
560+
span: MultiSpan,
561+
}

src/test/ui-fulldeps/session-diagnostic/diagnostic-derive.stderr

+3-3
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ LL | | struct SlugNotProvided {}
233233
|
234234
= help: specify the slug as the first argument to the attribute, such as `#[error(typeck::example_error)]`
235235

236-
error: the `#[primary_span]` attribute can only be applied to fields of type `Span`
236+
error: the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
237237
--> $DIR/diagnostic-derive.rs:142:5
238238
|
239239
LL | #[primary_span]
@@ -247,7 +247,7 @@ LL | #[nonsense]
247247
|
248248
= help: only `skip_arg`, `primary_span`, `label`, `note`, `help` and `subdiagnostic` are valid field attributes
249249

250-
error: the `#[label(...)]` attribute can only be applied to fields of type `Span`
250+
error: the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
251251
--> $DIR/diagnostic-derive.rs:167:5
252252
|
253253
LL | #[label(typeck::label)]
@@ -279,7 +279,7 @@ LL | #[derive(SessionDiagnostic)]
279279
= note: if you intended to print `}`, you can escape it using `}}`
280280
= note: this error originates in the derive macro `SessionDiagnostic` (in Nightly builds, run with -Z macro-backtrace for more info)
281281

282-
error: the `#[label(...)]` attribute can only be applied to fields of type `Span`
282+
error: the `#[label(...)]` attribute can only be applied to fields of type `Span` or `MultiSpan`
283283
--> $DIR/diagnostic-derive.rs:210:5
284284
|
285285
LL | #[label(typeck::label)]

src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ enum V {
244244
//~^ ERROR label without `#[primary_span]` field
245245
struct W {
246246
#[primary_span]
247-
//~^ ERROR the `#[primary_span]` attribute can only be applied to fields of type `Span`
247+
//~^ ERROR the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
248248
span: String,
249249
}
250250

src/test/ui-fulldeps/session-diagnostic/subdiagnostic-derive.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ error: subdiagnostic kind not specified
120120
LL | B {
121121
| ^
122122

123-
error: the `#[primary_span]` attribute can only be applied to fields of type `Span`
123+
error: the `#[primary_span]` attribute can only be applied to fields of type `Span` or `MultiSpan`
124124
--> $DIR/subdiagnostic-derive.rs:246:5
125125
|
126126
LL | #[primary_span]

0 commit comments

Comments
 (0)