@@ -13,7 +13,7 @@ use quote::{format_ident, quote};
1313use std:: collections:: HashMap ;
1414use std:: str:: FromStr ;
1515use syn:: { spanned:: Spanned , Attribute , Meta , MetaList , MetaNameValue , Type } ;
16- use synstructure:: Structure ;
16+ use synstructure:: { BindingInfo , Structure } ;
1717
1818/// The central struct for constructing the `into_diagnostic` method from an annotated struct.
1919pub ( crate ) struct SessionDiagnosticDerive < ' a > {
@@ -71,55 +71,42 @@ impl<'a> SessionDiagnosticDerive<'a> {
7171 }
7272 } ;
7373
74+ // Keep track of which fields are subdiagnostics or have no attributes.
75+ let mut subdiagnostics_or_empty = std:: collections:: HashSet :: new ( ) ;
76+
7477 // Generates calls to `span_label` and similar functions based on the attributes
7578 // on fields. Code for suggestions uses formatting machinery and the value of
7679 // 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) ) ;
9899
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).
103100 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) ) ;
123110
124111 let span = ast. span ( ) . unwrap ( ) ;
125112 let ( diag, sess) = ( & builder. diag , & builder. sess ) ;
@@ -347,36 +334,60 @@ impl SessionDiagnosticDeriveBuilder {
347334 Ok ( tokens. drain ( ..) . collect ( ) )
348335 }
349336
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 ;
356340
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 ) ;
375342
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+ }
378356 } 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 ( )
380391 }
381392 }
382393
0 commit comments