1
- use crate :: diagnostics:: error:: { span_err, throw_span_err, DiagnosticDeriveError } ;
1
+ use crate :: diagnostics:: error:: {
2
+ span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError ,
3
+ } ;
2
4
use proc_macro:: Span ;
3
5
use proc_macro2:: TokenStream ;
4
6
use quote:: { format_ident, quote, ToTokens } ;
5
7
use std:: collections:: { BTreeSet , HashMap } ;
8
+ use std:: fmt;
6
9
use std:: str:: FromStr ;
7
10
use syn:: { spanned:: Spanned , Attribute , Meta , Type , TypeTuple } ;
11
+ use syn:: { MetaList , MetaNameValue , NestedMeta , Path } ;
8
12
use synstructure:: { BindingInfo , Structure } ;
9
13
14
+ use super :: error:: invalid_nested_attr;
15
+
10
16
/// Checks whether the type name of `ty` matches `name`.
11
17
///
12
18
/// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
@@ -172,13 +178,17 @@ pub(crate) struct FieldInfo<'a> {
172
178
/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
173
179
/// for error reporting if they are set more than once.
174
180
pub ( crate ) trait SetOnce < T > {
175
- fn set_once ( & mut self , _ : ( T , Span ) ) ;
181
+ fn set_once ( & mut self , value : T , span : Span ) ;
176
182
177
183
fn value ( self ) -> Option < T > ;
184
+ fn value_ref ( & self ) -> Option < & T > ;
178
185
}
179
186
180
- impl < T > SetOnce < T > for Option < ( T , Span ) > {
181
- fn set_once ( & mut self , ( value, span) : ( T , Span ) ) {
187
+ /// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`].
188
+ pub ( super ) type SpannedOption < T > = Option < ( T , Span ) > ;
189
+
190
+ impl < T > SetOnce < T > for SpannedOption < T > {
191
+ fn set_once ( & mut self , value : T , span : Span ) {
182
192
match self {
183
193
None => {
184
194
* self = Some ( ( value, span) ) ;
@@ -194,6 +204,10 @@ impl<T> SetOnce<T> for Option<(T, Span)> {
194
204
fn value ( self ) -> Option < T > {
195
205
self . map ( |( v, _) | v)
196
206
}
207
+
208
+ fn value_ref ( & self ) -> Option < & T > {
209
+ self . as_ref ( ) . map ( |( v, _) | v)
210
+ }
197
211
}
198
212
199
213
pub ( crate ) trait HasFieldMap {
@@ -303,6 +317,7 @@ pub(crate) trait HasFieldMap {
303
317
304
318
/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
305
319
/// the user's selection of applicability if specified in an attribute.
320
+ #[ derive( Clone , Copy ) ]
306
321
pub ( crate ) enum Applicability {
307
322
MachineApplicable ,
308
323
MaybeIncorrect ,
@@ -359,3 +374,250 @@ pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap<Stri
359
374
360
375
fields_map
361
376
}
377
+
378
+ /// Possible styles for suggestion subdiagnostics.
379
+ #[ derive( Clone , Copy ) ]
380
+ pub ( super ) enum SuggestionKind {
381
+ /// `#[suggestion]`
382
+ Normal ,
383
+ /// `#[suggestion_short]`
384
+ Short ,
385
+ /// `#[suggestion_hidden]`
386
+ Hidden ,
387
+ /// `#[suggestion_verbose]`
388
+ Verbose ,
389
+ }
390
+
391
+ impl FromStr for SuggestionKind {
392
+ type Err = ( ) ;
393
+
394
+ fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
395
+ match s {
396
+ "" => Ok ( SuggestionKind :: Normal ) ,
397
+ "_short" => Ok ( SuggestionKind :: Short ) ,
398
+ "_hidden" => Ok ( SuggestionKind :: Hidden ) ,
399
+ "_verbose" => Ok ( SuggestionKind :: Verbose ) ,
400
+ _ => Err ( ( ) ) ,
401
+ }
402
+ }
403
+ }
404
+
405
+ impl SuggestionKind {
406
+ pub fn to_suggestion_style ( & self ) -> TokenStream {
407
+ match self {
408
+ SuggestionKind :: Normal => {
409
+ quote ! { rustc_errors:: SuggestionStyle :: ShowCode }
410
+ }
411
+ SuggestionKind :: Short => {
412
+ quote ! { rustc_errors:: SuggestionStyle :: HideCodeInline }
413
+ }
414
+ SuggestionKind :: Hidden => {
415
+ quote ! { rustc_errors:: SuggestionStyle :: HideCodeAlways }
416
+ }
417
+ SuggestionKind :: Verbose => {
418
+ quote ! { rustc_errors:: SuggestionStyle :: ShowAlways }
419
+ }
420
+ }
421
+ }
422
+ }
423
+
424
+ /// Types of subdiagnostics that can be created using attributes
425
+ #[ derive( Clone ) ]
426
+ pub ( super ) enum SubdiagnosticKind {
427
+ /// `#[label(...)]`
428
+ Label ,
429
+ /// `#[note(...)]`
430
+ Note ,
431
+ /// `#[help(...)]`
432
+ Help ,
433
+ /// `#[warning(...)]`
434
+ Warn ,
435
+ /// `#[suggestion{,_short,_hidden,_verbose}]`
436
+ Suggestion {
437
+ suggestion_kind : SuggestionKind ,
438
+ applicability : SpannedOption < Applicability > ,
439
+ code : TokenStream ,
440
+ } ,
441
+ /// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
442
+ MultipartSuggestion {
443
+ suggestion_kind : SuggestionKind ,
444
+ applicability : SpannedOption < Applicability > ,
445
+ } ,
446
+ }
447
+
448
+ impl SubdiagnosticKind {
449
+ /// Constructs a `SubdiagnosticKind` from a field or type attribute such as `#[note]`,
450
+ /// `#[error(parser::add_paren)]` or `#[suggestion(code = "...")]`. Returns the
451
+ /// `SubdiagnosticKind` and the diagnostic slug, if specified.
452
+ pub ( super ) fn from_attr (
453
+ attr : & Attribute ,
454
+ fields : & impl HasFieldMap ,
455
+ ) -> Result < ( SubdiagnosticKind , Option < Path > ) , DiagnosticDeriveError > {
456
+ let span = attr. span ( ) . unwrap ( ) ;
457
+
458
+ let name = attr. path . segments . last ( ) . unwrap ( ) . ident . to_string ( ) ;
459
+ let name = name. as_str ( ) ;
460
+
461
+ let meta = attr. parse_meta ( ) ?;
462
+ let mut kind = match name {
463
+ "label" => SubdiagnosticKind :: Label ,
464
+ "note" => SubdiagnosticKind :: Note ,
465
+ "help" => SubdiagnosticKind :: Help ,
466
+ "warning" => SubdiagnosticKind :: Warn ,
467
+ _ => {
468
+ if let Some ( suggestion_kind) =
469
+ name. strip_prefix ( "suggestion" ) . and_then ( |s| s. parse ( ) . ok ( ) )
470
+ {
471
+ SubdiagnosticKind :: Suggestion {
472
+ suggestion_kind,
473
+ applicability : None ,
474
+ code : TokenStream :: new ( ) ,
475
+ }
476
+ } else if let Some ( suggestion_kind) =
477
+ name. strip_prefix ( "multipart_suggestion" ) . and_then ( |s| s. parse ( ) . ok ( ) )
478
+ {
479
+ SubdiagnosticKind :: MultipartSuggestion { suggestion_kind, applicability : None }
480
+ } else {
481
+ throw_invalid_attr ! ( attr, & meta) ;
482
+ }
483
+ }
484
+ } ;
485
+
486
+ let nested = match meta {
487
+ Meta :: List ( MetaList { ref nested, .. } ) => {
488
+ // An attribute with properties, such as `#[suggestion(code = "...")]` or
489
+ // `#[error(some::slug)]`
490
+ nested
491
+ }
492
+ Meta :: Path ( _) => {
493
+ // An attribute without a slug or other properties, such as `#[note]` - return
494
+ // without further processing.
495
+ //
496
+ // Only allow this if there are no mandatory properties, such as `code = "..."` in
497
+ // `#[suggestion(...)]`
498
+ match kind {
499
+ SubdiagnosticKind :: Label
500
+ | SubdiagnosticKind :: Note
501
+ | SubdiagnosticKind :: Help
502
+ | SubdiagnosticKind :: Warn
503
+ | SubdiagnosticKind :: MultipartSuggestion { .. } => return Ok ( ( kind, None ) ) ,
504
+ SubdiagnosticKind :: Suggestion { .. } => {
505
+ throw_span_err ! ( span, "suggestion without `code = \" ...\" `" )
506
+ }
507
+ }
508
+ }
509
+ _ => {
510
+ throw_invalid_attr ! ( attr, & meta)
511
+ }
512
+ } ;
513
+
514
+ let mut code = None ;
515
+
516
+ let mut nested_iter = nested. into_iter ( ) . peekable ( ) ;
517
+
518
+ // Peek at the first nested attribute: if it's a slug path, consume it.
519
+ let slug = if let Some ( NestedMeta :: Meta ( Meta :: Path ( path) ) ) = nested_iter. peek ( ) {
520
+ let path = path. clone ( ) ;
521
+ // Advance the iterator.
522
+ nested_iter. next ( ) ;
523
+ Some ( path)
524
+ } else {
525
+ None
526
+ } ;
527
+
528
+ for nested_attr in nested_iter {
529
+ let meta = match nested_attr {
530
+ NestedMeta :: Meta ( ref meta) => meta,
531
+ NestedMeta :: Lit ( _) => {
532
+ invalid_nested_attr ( attr, & nested_attr) . emit ( ) ;
533
+ continue ;
534
+ }
535
+ } ;
536
+
537
+ let span = meta. span ( ) . unwrap ( ) ;
538
+ let nested_name = meta. path ( ) . segments . last ( ) . unwrap ( ) . ident . to_string ( ) ;
539
+ let nested_name = nested_name. as_str ( ) ;
540
+
541
+ let value = match meta {
542
+ Meta :: NameValue ( MetaNameValue { lit : syn:: Lit :: Str ( value) , .. } ) => value,
543
+ Meta :: Path ( _) => throw_invalid_nested_attr ! ( attr, & nested_attr, |diag| {
544
+ diag. help( "a diagnostic slug must be the first argument to the attribute" )
545
+ } ) ,
546
+ _ => {
547
+ invalid_nested_attr ( attr, & nested_attr) . emit ( ) ;
548
+ continue ;
549
+ }
550
+ } ;
551
+
552
+ match ( nested_name, & mut kind) {
553
+ ( "code" , SubdiagnosticKind :: Suggestion { .. } ) => {
554
+ let formatted_str = fields. build_format ( & value. value ( ) , value. span ( ) ) ;
555
+ code. set_once ( formatted_str, span) ;
556
+ }
557
+ (
558
+ "applicability" ,
559
+ SubdiagnosticKind :: Suggestion { ref mut applicability, .. }
560
+ | SubdiagnosticKind :: MultipartSuggestion { ref mut applicability, .. } ,
561
+ ) => {
562
+ let value = Applicability :: from_str ( & value. value ( ) ) . unwrap_or_else ( |( ) | {
563
+ span_err ( span, "invalid applicability" ) . emit ( ) ;
564
+ Applicability :: Unspecified
565
+ } ) ;
566
+ applicability. set_once ( value, span) ;
567
+ }
568
+
569
+ // Invalid nested attribute
570
+ ( _, SubdiagnosticKind :: Suggestion { .. } ) => {
571
+ invalid_nested_attr ( attr, & nested_attr)
572
+ . help ( "only `code` and `applicability` are valid nested attributes" )
573
+ . emit ( ) ;
574
+ }
575
+ ( _, SubdiagnosticKind :: MultipartSuggestion { .. } ) => {
576
+ invalid_nested_attr ( attr, & nested_attr)
577
+ . help ( "only `applicability` is a valid nested attributes" )
578
+ . emit ( )
579
+ }
580
+ _ => {
581
+ invalid_nested_attr ( attr, & nested_attr) . emit ( ) ;
582
+ }
583
+ }
584
+ }
585
+
586
+ match kind {
587
+ SubdiagnosticKind :: Suggestion { code : ref mut code_field, .. } => {
588
+ * code_field = if let Some ( ( code, _) ) = code {
589
+ code
590
+ } else {
591
+ span_err ( span, "suggestion without `code = \" ...\" `" ) . emit ( ) ;
592
+ quote ! { "" }
593
+ }
594
+ }
595
+ SubdiagnosticKind :: Label
596
+ | SubdiagnosticKind :: Note
597
+ | SubdiagnosticKind :: Help
598
+ | SubdiagnosticKind :: Warn
599
+ | SubdiagnosticKind :: MultipartSuggestion { .. } => { }
600
+ }
601
+
602
+ Ok ( ( kind, slug) )
603
+ }
604
+ }
605
+
606
+ impl quote:: IdentFragment for SubdiagnosticKind {
607
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
608
+ match self {
609
+ SubdiagnosticKind :: Label => write ! ( f, "label" ) ,
610
+ SubdiagnosticKind :: Note => write ! ( f, "note" ) ,
611
+ SubdiagnosticKind :: Help => write ! ( f, "help" ) ,
612
+ SubdiagnosticKind :: Warn => write ! ( f, "warn" ) ,
613
+ SubdiagnosticKind :: Suggestion { .. } => write ! ( f, "suggestion_with_style" ) ,
614
+ SubdiagnosticKind :: MultipartSuggestion { .. } => {
615
+ write ! ( f, "multipart_suggestion_with_style" )
616
+ }
617
+ }
618
+ }
619
+
620
+ fn span ( & self ) -> Option < proc_macro2:: Span > {
621
+ None
622
+ }
623
+ }
0 commit comments