@@ -13,7 +13,7 @@ use quote::{format_ident, quote};
13
13
use std:: collections:: HashMap ;
14
14
use std:: str:: FromStr ;
15
15
use syn:: { spanned:: Spanned , Attribute , Meta , MetaList , MetaNameValue , Type } ;
16
- use synstructure:: Structure ;
16
+ use synstructure:: { BindingInfo , Structure } ;
17
17
18
18
/// The central struct for constructing the `into_diagnostic` method from an annotated struct.
19
19
pub ( crate ) struct SessionDiagnosticDerive < ' a > {
@@ -71,55 +71,42 @@ impl<'a> SessionDiagnosticDerive<'a> {
71
71
}
72
72
} ;
73
73
74
+ // Keep track of which fields are subdiagnostics or have no attributes.
75
+ let mut subdiagnostics_or_empty = std:: collections:: HashSet :: new ( ) ;
76
+
74
77
// Generates calls to `span_label` and similar functions based on the attributes
75
78
// on fields. Code for suggestions uses formatting machinery and the value of
76
79
// other fields - because any given field can be referenced multiple times, it
77
- // should be accessed through a borrow. When passing fields to `set_arg` (which
78
- // happens below) for Fluent, we want to move the data, so that has to happen
79
- // in a separate pass over the fields.
80
- let attrs = structure. each ( |field_binding| {
81
- let field = field_binding. ast ( ) ;
82
- let result = field. attrs . iter ( ) . map ( |attr| {
83
- builder
84
- . generate_field_attr_code (
85
- attr,
86
- FieldInfo {
87
- vis : & field. vis ,
88
- binding : field_binding,
89
- ty : & field. ty ,
90
- span : & field. span ( ) ,
91
- } ,
92
- )
93
- . unwrap_or_else ( |v| v. to_compile_error ( ) )
94
- } ) ;
95
-
96
- quote ! { #( #result) ; * }
97
- } ) ;
80
+ // should be accessed through a borrow. When passing fields to `add_subdiagnostic`
81
+ // or `set_arg` (which happens below) for Fluent, we want to move the data, so that
82
+ // has to happen in a separate pass over the fields.
83
+ let attrs = structure
84
+ . clone ( )
85
+ . filter ( |field_binding| {
86
+ let attrs = & field_binding. ast ( ) . attrs ;
87
+
88
+ ( !attrs. is_empty ( )
89
+ && attrs. iter ( ) . all ( |attr| {
90
+ "subdiagnostic"
91
+ != attr. path . segments . last ( ) . unwrap ( ) . ident . to_string ( )
92
+ } ) )
93
+ || {
94
+ subdiagnostics_or_empty. insert ( field_binding. binding . clone ( ) ) ;
95
+ false
96
+ }
97
+ } )
98
+ . each ( |field_binding| builder. generate_field_attrs_code ( field_binding) ) ;
98
99
99
- // When generating `set_arg` calls, move data rather than borrow it to avoid
100
- // requiring clones - this must therefore be the last use of each field (for
101
- // example, any formatting machinery that might refer to a field should be
102
- // generated already).
103
100
structure. bind_with ( |_| synstructure:: BindStyle :: Move ) ;
104
- let args = structure. each ( |field_binding| {
105
- let field = field_binding. ast ( ) ;
106
- // When a field has attributes like `#[label]` or `#[note]` then it doesn't
107
- // need to be passed as an argument to the diagnostic. But when a field has no
108
- // attributes then it must be passed as an argument to the diagnostic so that
109
- // it can be referred to by Fluent messages.
110
- if field. attrs . is_empty ( ) {
111
- let diag = & builder. diag ;
112
- let ident = field_binding. ast ( ) . ident . as_ref ( ) . unwrap ( ) ;
113
- quote ! {
114
- #diag. set_arg(
115
- stringify!( #ident) ,
116
- #field_binding
117
- ) ;
118
- }
119
- } else {
120
- quote ! { }
121
- }
122
- } ) ;
101
+ // When a field has attributes like `#[label]` or `#[note]` then it doesn't
102
+ // need to be passed as an argument to the diagnostic. But when a field has no
103
+ // attributes or a `#[subdiagnostic]` attribute then it must be passed as an
104
+ // argument to the diagnostic so that it can be referred to by Fluent messages.
105
+ let args = structure
106
+ . filter ( |field_binding| {
107
+ subdiagnostics_or_empty. contains ( & field_binding. binding )
108
+ } )
109
+ . each ( |field_binding| builder. generate_field_attrs_code ( field_binding) ) ;
123
110
124
111
let span = ast. span ( ) . unwrap ( ) ;
125
112
let ( diag, sess) = ( & builder. diag , & builder. sess ) ;
@@ -347,36 +334,60 @@ impl SessionDiagnosticDeriveBuilder {
347
334
Ok ( tokens. drain ( ..) . collect ( ) )
348
335
}
349
336
350
- fn generate_field_attr_code (
351
- & mut self ,
352
- attr : & syn:: Attribute ,
353
- info : FieldInfo < ' _ > ,
354
- ) -> Result < TokenStream , SessionDiagnosticDeriveError > {
355
- let field_binding = & info. binding . binding ;
337
+ fn generate_field_attrs_code ( & mut self , binding_info : & BindingInfo < ' _ > ) -> TokenStream {
338
+ let field = binding_info. ast ( ) ;
339
+ let field_binding = & binding_info. binding ;
356
340
357
- let inner_ty = FieldInnerTy :: from_type ( & info. ty ) ;
358
- let name = attr. path . segments . last ( ) . unwrap ( ) . ident . to_string ( ) ;
359
- let ( binding, needs_destructure) = match ( name. as_str ( ) , & inner_ty) {
360
- // `primary_span` can accept a `Vec<Span>` so don't destructure that.
361
- ( "primary_span" , FieldInnerTy :: Vec ( _) ) => ( quote ! { #field_binding. clone( ) } , false ) ,
362
- _ => ( quote ! { * #field_binding } , true ) ,
363
- } ;
364
-
365
- let generated_code = self . generate_inner_field_code (
366
- attr,
367
- FieldInfo {
368
- vis : info. vis ,
369
- binding : info. binding ,
370
- ty : inner_ty. inner_type ( ) . unwrap_or ( & info. ty ) ,
371
- span : info. span ,
372
- } ,
373
- binding,
374
- ) ?;
341
+ let inner_ty = FieldInnerTy :: from_type ( & field. ty ) ;
375
342
376
- if needs_destructure {
377
- Ok ( inner_ty. with ( field_binding, generated_code) )
343
+ // When generating `set_arg` or `add_subdiagnostic` calls, move data rather than
344
+ // borrow it to avoid requiring clones - this must therefore be the last use of
345
+ // each field (for example, any formatting machinery that might refer to a field
346
+ // should be generated already).
347
+ if field. attrs . is_empty ( ) {
348
+ let diag = & self . diag ;
349
+ let ident = field. ident . as_ref ( ) . unwrap ( ) ;
350
+ quote ! {
351
+ #diag. set_arg(
352
+ stringify!( #ident) ,
353
+ #field_binding
354
+ ) ;
355
+ }
378
356
} else {
379
- Ok ( generated_code)
357
+ field
358
+ . attrs
359
+ . iter ( )
360
+ . map ( move |attr| {
361
+ let name = attr. path . segments . last ( ) . unwrap ( ) . ident . to_string ( ) ;
362
+ let ( binding, needs_destructure) = match ( name. as_str ( ) , & inner_ty) {
363
+ // `primary_span` can accept a `Vec<Span>` so don't destructure that.
364
+ ( "primary_span" , FieldInnerTy :: Vec ( _) ) => {
365
+ ( quote ! { #field_binding. clone( ) } , false )
366
+ }
367
+ // `subdiagnostics` are not derefed because they are bound by value.
368
+ ( "subdiagnostic" , _) => ( quote ! { #field_binding } , true ) ,
369
+ _ => ( quote ! { * #field_binding } , true ) ,
370
+ } ;
371
+
372
+ let generated_code = self
373
+ . generate_inner_field_code (
374
+ attr,
375
+ FieldInfo {
376
+ binding : binding_info,
377
+ ty : inner_ty. inner_type ( ) . unwrap_or ( & field. ty ) ,
378
+ span : & field. span ( ) ,
379
+ } ,
380
+ binding,
381
+ )
382
+ . unwrap_or_else ( |v| v. to_compile_error ( ) ) ;
383
+
384
+ if needs_destructure {
385
+ inner_ty. with ( field_binding, generated_code)
386
+ } else {
387
+ generated_code
388
+ }
389
+ } )
390
+ . collect ( )
380
391
}
381
392
}
382
393
0 commit comments