@@ -2,7 +2,7 @@ use crate::diagnostics::error::{
2
2
span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError ,
3
3
} ;
4
4
use proc_macro:: Span ;
5
- use proc_macro2:: TokenStream ;
5
+ use proc_macro2:: { Ident , TokenStream } ;
6
6
use quote:: { format_ident, quote, ToTokens } ;
7
7
use std:: cell:: RefCell ;
8
8
use std:: collections:: { BTreeSet , HashMap } ;
@@ -395,6 +395,82 @@ pub(super) fn build_field_mapping<'v>(variant: &VariantInfo<'v>) -> HashMap<Stri
395
395
fields_map
396
396
}
397
397
398
+ #[ derive( Copy , Clone , Debug ) ]
399
+ pub ( super ) enum AllowMultipleAlternatives {
400
+ No ,
401
+ Yes ,
402
+ }
403
+
404
+ /// Constructs the `format!()` invocation(s) necessary for a `#[suggestion*(code = "foo")]` or
405
+ /// `#[suggestion*(code("foo", "bar"))]` attribute field
406
+ pub ( super ) fn build_suggestion_code (
407
+ code_field : & Ident ,
408
+ meta : & Meta ,
409
+ fields : & impl HasFieldMap ,
410
+ allow_multiple : AllowMultipleAlternatives ,
411
+ ) -> TokenStream {
412
+ let values = match meta {
413
+ // `code = "foo"`
414
+ Meta :: NameValue ( MetaNameValue { lit : syn:: Lit :: Str ( s) , .. } ) => vec ! [ s] ,
415
+ // `code("foo", "bar")`
416
+ Meta :: List ( MetaList { nested, .. } ) => {
417
+ if let AllowMultipleAlternatives :: No = allow_multiple {
418
+ span_err (
419
+ meta. span ( ) . unwrap ( ) ,
420
+ "expected exactly one string literal for `code = ...`" ,
421
+ )
422
+ . emit ( ) ;
423
+ vec ! [ ]
424
+ } else if nested. is_empty ( ) {
425
+ span_err (
426
+ meta. span ( ) . unwrap ( ) ,
427
+ "expected at least one string literal for `code(...)`" ,
428
+ )
429
+ . emit ( ) ;
430
+ vec ! [ ]
431
+ } else {
432
+ nested
433
+ . into_iter ( )
434
+ . filter_map ( |item| {
435
+ if let NestedMeta :: Lit ( syn:: Lit :: Str ( s) ) = item {
436
+ Some ( s)
437
+ } else {
438
+ span_err (
439
+ item. span ( ) . unwrap ( ) ,
440
+ "`code(...)` must contain only string literals" ,
441
+ )
442
+ . emit ( ) ;
443
+ None
444
+ }
445
+ } )
446
+ . collect ( )
447
+ }
448
+ }
449
+ _ => {
450
+ span_err (
451
+ meta. span ( ) . unwrap ( ) ,
452
+ r#"`code = "..."`/`code(...)` must contain only string literals"# ,
453
+ )
454
+ . emit ( ) ;
455
+ vec ! [ ]
456
+ }
457
+ } ;
458
+
459
+ if let AllowMultipleAlternatives :: Yes = allow_multiple {
460
+ let formatted_strings: Vec < _ > = values
461
+ . into_iter ( )
462
+ . map ( |value| fields. build_format ( & value. value ( ) , value. span ( ) ) )
463
+ . collect ( ) ;
464
+ quote ! { let #code_field = [ #( #formatted_strings) , * ] . into_iter( ) ; }
465
+ } else if let [ value] = values. as_slice ( ) {
466
+ let formatted_str = fields. build_format ( & value. value ( ) , value. span ( ) ) ;
467
+ quote ! { let #code_field = #formatted_str; }
468
+ } else {
469
+ // error handled previously
470
+ quote ! { let #code_field = String :: new( ) ; }
471
+ }
472
+ }
473
+
398
474
/// Possible styles for suggestion subdiagnostics.
399
475
#[ derive( Clone , Copy ) ]
400
476
pub ( super ) enum SuggestionKind {
@@ -571,28 +647,35 @@ impl SubdiagnosticKind {
571
647
let nested_name = meta. path ( ) . segments . last ( ) . unwrap ( ) . ident . to_string ( ) ;
572
648
let nested_name = nested_name. as_str ( ) ;
573
649
574
- let value = match meta {
575
- Meta :: NameValue ( MetaNameValue { lit : syn:: Lit :: Str ( value) , .. } ) => value,
650
+ let string_value = match meta {
651
+ Meta :: NameValue ( MetaNameValue { lit : syn:: Lit :: Str ( value) , .. } ) => Some ( value) ,
652
+
576
653
Meta :: Path ( _) => throw_invalid_nested_attr ! ( attr, & nested_attr, |diag| {
577
654
diag. help( "a diagnostic slug must be the first argument to the attribute" )
578
655
} ) ,
579
- _ => {
580
- invalid_nested_attr ( attr, & nested_attr) . emit ( ) ;
581
- continue ;
582
- }
656
+ _ => None ,
583
657
} ;
584
658
585
659
match ( nested_name, & mut kind) {
586
660
( "code" , SubdiagnosticKind :: Suggestion { code_field, .. } ) => {
587
- let formatted_str = fields. build_format ( & value. value ( ) , value. span ( ) ) ;
588
- let code_init = quote ! { let #code_field = #formatted_str; } ;
661
+ let code_init = build_suggestion_code (
662
+ code_field,
663
+ meta,
664
+ fields,
665
+ AllowMultipleAlternatives :: Yes ,
666
+ ) ;
589
667
code. set_once ( code_init, span) ;
590
668
}
591
669
(
592
670
"applicability" ,
593
671
SubdiagnosticKind :: Suggestion { ref mut applicability, .. }
594
672
| SubdiagnosticKind :: MultipartSuggestion { ref mut applicability, .. } ,
595
673
) => {
674
+ let Some ( value) = string_value else {
675
+ invalid_nested_attr ( attr, & nested_attr) . emit ( ) ;
676
+ continue ;
677
+ } ;
678
+
596
679
let value = Applicability :: from_str ( & value. value ( ) ) . unwrap_or_else ( |( ) | {
597
680
span_err ( span, "invalid applicability" ) . emit ( ) ;
598
681
Applicability :: Unspecified
@@ -623,7 +706,7 @@ impl SubdiagnosticKind {
623
706
init
624
707
} else {
625
708
span_err ( span, "suggestion without `code = \" ...\" `" ) . emit ( ) ;
626
- quote ! { let #code_field: String = unreachable! ( ) ; }
709
+ quote ! { let #code_field = std :: iter :: empty ( ) ; }
627
710
} ;
628
711
}
629
712
SubdiagnosticKind :: Label
@@ -644,7 +727,7 @@ impl quote::IdentFragment for SubdiagnosticKind {
644
727
SubdiagnosticKind :: Note => write ! ( f, "note" ) ,
645
728
SubdiagnosticKind :: Help => write ! ( f, "help" ) ,
646
729
SubdiagnosticKind :: Warn => write ! ( f, "warn" ) ,
647
- SubdiagnosticKind :: Suggestion { .. } => write ! ( f, "suggestion_with_style " ) ,
730
+ SubdiagnosticKind :: Suggestion { .. } => write ! ( f, "suggestions_with_style " ) ,
648
731
SubdiagnosticKind :: MultipartSuggestion { .. } => {
649
732
write ! ( f, "multipart_suggestion_with_style" )
650
733
}
0 commit comments