@@ -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 {
@@ -564,28 +640,35 @@ impl SubdiagnosticKind {
564
640
let nested_name = meta. path ( ) . segments . last ( ) . unwrap ( ) . ident . to_string ( ) ;
565
641
let nested_name = nested_name. as_str ( ) ;
566
642
567
- let value = match meta {
568
- Meta :: NameValue ( MetaNameValue { lit : syn:: Lit :: Str ( value) , .. } ) => value,
643
+ let string_value = match meta {
644
+ Meta :: NameValue ( MetaNameValue { lit : syn:: Lit :: Str ( value) , .. } ) => Some ( value) ,
645
+
569
646
Meta :: Path ( _) => throw_invalid_nested_attr ! ( attr, & nested_attr, |diag| {
570
647
diag. help( "a diagnostic slug must be the first argument to the attribute" )
571
648
} ) ,
572
- _ => {
573
- invalid_nested_attr ( attr, & nested_attr) . emit ( ) ;
574
- continue ;
575
- }
649
+ _ => None ,
576
650
} ;
577
651
578
652
match ( nested_name, & mut kind) {
579
653
( "code" , SubdiagnosticKind :: Suggestion { code_field, .. } ) => {
580
- let formatted_str = fields. build_format ( & value. value ( ) , value. span ( ) ) ;
581
- let code_init = quote ! { let #code_field = #formatted_str; } ;
654
+ let code_init = build_suggestion_code (
655
+ code_field,
656
+ meta,
657
+ fields,
658
+ AllowMultipleAlternatives :: Yes ,
659
+ ) ;
582
660
code. set_once ( code_init, span) ;
583
661
}
584
662
(
585
663
"applicability" ,
586
664
SubdiagnosticKind :: Suggestion { ref mut applicability, .. }
587
665
| SubdiagnosticKind :: MultipartSuggestion { ref mut applicability, .. } ,
588
666
) => {
667
+ let Some ( value) = string_value else {
668
+ invalid_nested_attr ( attr, & nested_attr) . emit ( ) ;
669
+ continue ;
670
+ } ;
671
+
589
672
let value = Applicability :: from_str ( & value. value ( ) ) . unwrap_or_else ( |( ) | {
590
673
span_err ( span, "invalid applicability" ) . emit ( ) ;
591
674
Applicability :: Unspecified
@@ -616,7 +699,7 @@ impl SubdiagnosticKind {
616
699
init
617
700
} else {
618
701
span_err ( span, "suggestion without `code = \" ...\" `" ) . emit ( ) ;
619
- quote ! { let #code_field: String = unreachable! ( ) ; }
702
+ quote ! { let #code_field = std :: iter :: empty ( ) ; }
620
703
} ;
621
704
}
622
705
SubdiagnosticKind :: Label
@@ -637,7 +720,7 @@ impl quote::IdentFragment for SubdiagnosticKind {
637
720
SubdiagnosticKind :: Note => write ! ( f, "note" ) ,
638
721
SubdiagnosticKind :: Help => write ! ( f, "help" ) ,
639
722
SubdiagnosticKind :: Warn => write ! ( f, "warn" ) ,
640
- SubdiagnosticKind :: Suggestion { .. } => write ! ( f, "suggestion_with_style " ) ,
723
+ SubdiagnosticKind :: Suggestion { .. } => write ! ( f, "suggestions_with_style " ) ,
641
724
SubdiagnosticKind :: MultipartSuggestion { .. } => {
642
725
write ! ( f, "multipart_suggestion_with_style" )
643
726
}
0 commit comments