@@ -13,7 +13,8 @@ use quote::{format_ident, quote};
1313use std:: collections:: HashMap ;
1414use std:: str:: FromStr ;
1515use 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} ;
1819use 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 (
0 commit comments