@@ -13,7 +13,8 @@ use quote::{format_ident, quote};
13
13
use std:: collections:: HashMap ;
14
14
use std:: str:: FromStr ;
15
15
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 ,
17
18
} ;
18
19
use synstructure:: { BindingInfo , Structure } ;
19
20
@@ -80,8 +81,8 @@ impl DiagnosticDeriveBuilder {
80
81
}
81
82
82
83
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 ( ) ;
85
86
86
87
// Generates calls to `span_label` and similar functions based on the attributes
87
88
// on fields. Code for suggestions uses formatting machinery and the value of
@@ -92,16 +93,11 @@ impl DiagnosticDeriveBuilder {
92
93
let attrs = structure
93
94
. clone ( )
94
95
. 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
+ }
105
101
} )
106
102
. each ( |field_binding| self . generate_field_attrs_code ( field_binding) ) ;
107
103
@@ -111,12 +107,41 @@ impl DiagnosticDeriveBuilder {
111
107
// attributes or a `#[subdiagnostic]` attribute then it must be passed as an
112
108
// argument to the diagnostic so that it can be referred to by Fluent messages.
113
109
let args = structure
114
- . filter ( |field_binding| subdiagnostics_or_empty . contains ( & field_binding. binding ) )
110
+ . filter ( |field_binding| needs_moved . contains ( & field_binding. binding ) )
115
111
. each ( |field_binding| self . generate_field_attrs_code ( field_binding) ) ;
116
112
117
113
( attrs, args)
118
114
}
119
115
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
+
120
145
/// Establishes state in the `DiagnosticDeriveBuilder` resulting from the struct
121
146
/// attributes like `#[error(..)`, such as the diagnostic kind and slug. Generates
122
147
/// diagnostic builder calls for setting error code and creating note/help messages.
@@ -227,57 +252,55 @@ impl DiagnosticDeriveBuilder {
227
252
let field = binding_info. ast ( ) ;
228
253
let field_binding = & binding_info. binding ;
229
254
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) {
237
256
let diag = & self . diag ;
238
257
let ident = field. ident . as_ref ( ) . unwrap ( ) ;
239
- quote ! {
258
+ return quote ! {
240
259
#diag. set_arg(
241
260
stringify!( #ident) ,
242
261
#field_binding
243
262
) ;
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
+ } ;
280
264
}
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 ( )
281
304
}
282
305
283
306
fn generate_inner_field_code (
0 commit comments