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+ } ;
24use proc_macro:: Span ;
35use proc_macro2:: TokenStream ;
46use quote:: { format_ident, quote, ToTokens } ;
57use std:: collections:: { BTreeSet , HashMap } ;
8+ use std:: fmt;
69use std:: str:: FromStr ;
710use syn:: { spanned:: Spanned , Attribute , Meta , Type , TypeTuple } ;
11+ use syn:: { MetaList , MetaNameValue , NestedMeta , Path } ;
812use synstructure:: { BindingInfo , Structure } ;
913
14+ use super :: error:: invalid_nested_attr;
15+
1016/// Checks whether the type name of `ty` matches `name`.
1117///
1218/// 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> {
172178/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
173179/// for error reporting if they are set more than once.
174180pub ( crate ) trait SetOnce < T > {
175- fn set_once ( & mut self , _ : ( T , Span ) ) ;
181+ fn set_once ( & mut self , value : T , span : Span ) ;
176182
177183 fn value ( self ) -> Option < T > ;
184+ fn value_ref ( & self ) -> Option < & T > ;
178185}
179186
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 ) {
182192 match self {
183193 None => {
184194 * self = Some ( ( value, span) ) ;
@@ -194,6 +204,10 @@ impl<T> SetOnce<T> for Option<(T, Span)> {
194204 fn value ( self ) -> Option < T > {
195205 self . map ( |( v, _) | v)
196206 }
207+
208+ fn value_ref ( & self ) -> Option < & T > {
209+ self . as_ref ( ) . map ( |( v, _) | v)
210+ }
197211}
198212
199213pub ( crate ) trait HasFieldMap {
@@ -303,6 +317,7 @@ pub(crate) trait HasFieldMap {
303317
304318/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
305319/// the user's selection of applicability if specified in an attribute.
320+ #[ derive( Clone , Copy ) ]
306321pub ( crate ) enum Applicability {
307322 MachineApplicable ,
308323 MaybeIncorrect ,
@@ -359,3 +374,250 @@ pub(crate) fn build_field_mapping<'a>(structure: &Structure<'a>) -> HashMap<Stri
359374
360375 fields_map
361376}
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