Skip to main content

rustc_macros/diagnostics/
utils.rs

1use std::cell::RefCell;
2use std::collections::{BTreeSet, HashMap};
3use std::fmt;
4use std::str::FromStr;
5
6use proc_macro::Span;
7use proc_macro2::{Ident, TokenStream};
8use quote::{ToTokens, format_ident, quote};
9use syn::parse::ParseStream;
10use syn::punctuated::Punctuated;
11use syn::spanned::Spanned;
12use syn::{Attribute, Field, LitStr, Meta, Path, Token, Type, TypeTuple, parenthesized};
13use synstructure::{BindingInfo, VariantInfo};
14
15use super::error::invalid_attr;
16use crate::diagnostics::error::{
17    DiagnosticDeriveError, span_err, throw_invalid_attr, throw_span_err,
18};
19use crate::diagnostics::message::Message;
20
21thread_local! {
22    pub(crate) static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0);
23}
24
25/// Returns an ident of the form `__code_N` where `N` is incremented once with every call.
26pub(crate) fn new_code_ident() -> syn::Ident {
27    CODE_IDENT_COUNT.with(|count| {
28        let ident = format_ident!("__code_{}", *count.borrow());
29        *count.borrow_mut() += 1;
30        ident
31    })
32}
33
34/// Checks whether the type name of `ty` matches `name`.
35///
36/// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or
37/// `a::b::c::Foo`. This reasonably allows qualified names to be used in the macro.
38pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool {
39    if let Type::Path(ty) = ty {
40        ty.path
41            .segments
42            .iter()
43            .map(|s| s.ident.to_string())
44            .rev()
45            .zip(name.iter().rev())
46            .all(|(x, y)| &x.as_str() == y)
47    } else {
48        false
49    }
50}
51
52/// Checks whether the type `ty` is `()`.
53pub(crate) fn type_is_unit(ty: &Type) -> bool {
54    if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false }
55}
56
57/// Checks whether the type `ty` is `bool`.
58pub(crate) fn type_is_bool(ty: &Type) -> bool {
59    type_matches_path(ty, &["bool"])
60}
61
62/// Reports a type error for field with `attr`.
63pub(crate) fn report_type_error(
64    attr: &Attribute,
65    ty_name: &str,
66) -> Result<!, DiagnosticDeriveError> {
67    let name = attr.path().segments.last().unwrap().ident.to_string();
68    let meta = &attr.meta;
69
70    throw_span_err!(
71        attr.span().unwrap(),
72        &format!(
73            "the `#[{}{}]` attribute can only be applied to fields of type {}",
74            name,
75            match meta {
76                Meta::Path(_) => "",
77                Meta::NameValue(_) => " = ...",
78                Meta::List(_) => "(...)",
79            },
80            ty_name
81        )
82    );
83}
84
85/// Reports an error if the field's type does not match `path`.
86fn report_error_if_not_applied_to_ty(
87    attr: &Attribute,
88    info: &FieldInfo<'_>,
89    path: &[&str],
90    ty_name: &str,
91) -> Result<(), DiagnosticDeriveError> {
92    if !type_matches_path(info.ty.inner_type(), path) {
93        report_type_error(attr, ty_name)?;
94    }
95
96    Ok(())
97}
98
99/// Reports an error if the field's type is not `Applicability`.
100pub(crate) fn report_error_if_not_applied_to_applicability(
101    attr: &Attribute,
102    info: &FieldInfo<'_>,
103) -> Result<(), DiagnosticDeriveError> {
104    report_error_if_not_applied_to_ty(
105        attr,
106        info,
107        &["rustc_errors", "Applicability"],
108        "`Applicability`",
109    )
110}
111
112/// Reports an error if the field's type is not `Span`.
113pub(crate) fn report_error_if_not_applied_to_span(
114    attr: &Attribute,
115    info: &FieldInfo<'_>,
116) -> Result<(), DiagnosticDeriveError> {
117    if !type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"])
118        && !type_matches_path(info.ty.inner_type(), &["rustc_errors", "MultiSpan"])
119    {
120        report_type_error(attr, "`Span` or `MultiSpan`")?;
121    }
122
123    Ok(())
124}
125
126/// Inner type of a field and type of wrapper.
127#[derive(Copy, Clone)]
128pub(crate) enum FieldInnerTy<'ty> {
129    /// Field is wrapped in a `Option<$inner>`.
130    Option(&'ty Type),
131    /// Field is wrapped in a `Vec<$inner>`.
132    Vec(&'ty Type),
133    /// Field isn't wrapped in an outer type.
134    Plain(&'ty Type),
135}
136
137impl<'ty> FieldInnerTy<'ty> {
138    /// Returns inner type for a field, if there is one.
139    ///
140    /// - If `ty` is an `Option<Inner>`, returns `FieldInnerTy::Option(Inner)`.
141    /// - If `ty` is a `Vec<Inner>`, returns `FieldInnerTy::Vec(Inner)`.
142    /// - Otherwise returns `FieldInnerTy::Plain(ty)`.
143    pub(crate) fn from_type(ty: &'ty Type) -> Self {
144        fn single_generic_type(ty: &Type) -> &Type {
145            let Type::Path(ty_path) = ty else {
146                panic!("expected path type");
147            };
148
149            let path = &ty_path.path;
150            let ty = path.segments.last().unwrap();
151            let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments else {
152                panic!("expected bracketed generic arguments");
153            };
154
155            assert_eq!(bracketed.args.len(), 1);
156
157            let syn::GenericArgument::Type(ty) = &bracketed.args[0] else {
158                panic!("expected generic parameter to be a type generic");
159            };
160
161            ty
162        }
163
164        if type_matches_path(ty, &["std", "option", "Option"]) {
165            FieldInnerTy::Option(single_generic_type(ty))
166        } else if type_matches_path(ty, &["std", "vec", "Vec"]) {
167            FieldInnerTy::Vec(single_generic_type(ty))
168        } else {
169            FieldInnerTy::Plain(ty)
170        }
171    }
172
173    /// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e.
174    /// that cloning might be required for values moved in the loop body).
175    pub(crate) fn will_iterate(&self) -> bool {
176        match self {
177            FieldInnerTy::Vec(..) => true,
178            FieldInnerTy::Option(..) | FieldInnerTy::Plain(_) => false,
179        }
180    }
181
182    /// Returns the inner type.
183    pub(crate) fn inner_type(&self) -> &'ty Type {
184        match self {
185            FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) | FieldInnerTy::Plain(inner) => {
186                inner
187            }
188        }
189    }
190
191    /// Surrounds `inner` with destructured wrapper type, exposing inner type as `binding`.
192    pub(crate) fn with(&self, binding: impl ToTokens, inner: impl ToTokens) -> TokenStream {
193        match self {
194            FieldInnerTy::Option(..) => quote! {
195                if let Some(#binding) = #binding {
196                    #inner
197                }
198            },
199            FieldInnerTy::Vec(..) => quote! {
200                for #binding in #binding {
201                    #inner
202                }
203            },
204            FieldInnerTy::Plain(t) if type_is_bool(t) => quote! {
205                if #binding {
206                    #inner
207                }
208            },
209            FieldInnerTy::Plain(..) => quote! { #inner },
210        }
211    }
212
213    pub(crate) fn span(&self) -> proc_macro2::Span {
214        match self {
215            FieldInnerTy::Option(ty) | FieldInnerTy::Vec(ty) | FieldInnerTy::Plain(ty) => ty.span(),
216        }
217    }
218}
219
220/// Field information passed to the builder. Deliberately omits attrs to discourage the
221/// `generate_*` methods from walking the attributes themselves.
222pub(crate) struct FieldInfo<'a> {
223    pub(crate) binding: &'a BindingInfo<'a>,
224    pub(crate) ty: FieldInnerTy<'a>,
225    pub(crate) span: &'a proc_macro2::Span,
226}
227
228/// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
229/// for error reporting if they are set more than once.
230pub(crate) trait SetOnce<T> {
231    fn set_once(&mut self, value: T, span: Span);
232
233    fn value(self) -> Option<T>;
234    fn value_ref(&self) -> Option<&T>;
235}
236
237/// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`].
238pub(super) type SpannedOption<T> = Option<(T, Span)>;
239
240impl<T> SetOnce<T> for SpannedOption<T> {
241    fn set_once(&mut self, value: T, span: Span) {
242        match self {
243            None => {
244                *self = Some((value, span));
245            }
246            Some((_, prev_span)) => {
247                span_err(span, "attribute specified multiple times")
248                    .span_note(*prev_span, "previously specified here")
249                    .emit();
250            }
251        }
252    }
253
254    fn value(self) -> Option<T> {
255        self.map(|(v, _)| v)
256    }
257
258    fn value_ref(&self) -> Option<&T> {
259        self.as_ref().map(|(v, _)| v)
260    }
261}
262
263pub(super) type FieldMap = HashMap<String, TokenStream>;
264
265/// In the strings in the attributes supplied to this macro, we want callers to be able to
266/// reference fields in the format string. For example:
267///
268/// ```ignore (not-usage-example)
269/// /// Suggest `==` when users wrote `===`.
270/// #[suggestion("example message", code = "{lhs} == {rhs}")]
271/// struct NotJavaScriptEq {
272///     #[primary_span]
273///     span: Span,
274///     lhs: Ident,
275///     rhs: Ident,
276/// }
277/// ```
278///
279/// We want to automatically pick up that `{lhs}` refers `self.lhs` and `{rhs}` refers to
280/// `self.rhs`, then generate this call to `format!`:
281///
282/// ```ignore (not-usage-example)
283/// format!("{lhs} == {rhs}", lhs = self.lhs, rhs = self.rhs)
284/// ```
285///
286/// This function builds the entire call to `format!`.
287pub(super) fn build_format(
288    field_map: &FieldMap,
289    input: &str,
290    span: proc_macro2::Span,
291) -> TokenStream {
292    // This set is used later to generate the final format string. To keep builds reproducible,
293    // the iteration order needs to be deterministic, hence why we use a `BTreeSet` here
294    // instead of a `HashSet`.
295    let mut referenced_fields: BTreeSet<String> = BTreeSet::new();
296
297    // At this point, we can start parsing the format string.
298    let mut it = input.chars().peekable();
299
300    // Once the start of a format string has been found, process the format string and spit out
301    // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so
302    // the next call to `it.next()` retrieves the next character.
303    while let Some(c) = it.next() {
304        if c != '{' {
305            continue;
306        }
307        if *it.peek().unwrap_or(&'\0') == '{' {
308            assert_eq!(it.next().unwrap(), '{');
309            continue;
310        }
311        let mut eat_argument = || -> Option<String> {
312            let mut result = String::new();
313            // Format specifiers look like:
314            //
315            //   format   := '{' [ argument ] [ ':' format_spec ] '}' .
316            //
317            // Therefore, we only need to eat until ':' or '}' to find the argument.
318            while let Some(c) = it.next() {
319                result.push(c);
320                let next = *it.peek().unwrap_or(&'\0');
321                if next == '}' {
322                    break;
323                } else if next == ':' {
324                    // Eat the ':' character.
325                    assert_eq!(it.next().unwrap(), ':');
326                    break;
327                }
328            }
329            // Eat until (and including) the matching '}'
330            while it.next()? != '}' {
331                continue;
332            }
333            Some(result)
334        };
335
336        if let Some(referenced_field) = eat_argument() {
337            referenced_fields.insert(referenced_field);
338        }
339    }
340
341    // At this point, `referenced_fields` contains a set of the unique fields that were
342    // referenced in the format string. Generate the corresponding "x = self.x" format
343    // string parameters:
344    let args = referenced_fields.into_iter().map(|field: String| {
345        let field_ident = format_ident!("{}", field);
346        let value = match field_map.get(&field) {
347            Some(value) => value.clone(),
348            // This field doesn't exist. Emit a diagnostic.
349            None => {
350                span_err(span.unwrap(), format!("`{field}` doesn't refer to a field on this type"))
351                    .emit();
352                quote! {
353                    "{#field}"
354                }
355            }
356        };
357        quote! {
358            #field_ident = #value
359        }
360    });
361    quote! {
362        format!(#input #(,#args)*)
363    }
364}
365
366/// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
367/// the user's selection of applicability if specified in an attribute.
368#[derive(Clone, Copy)]
369pub(crate) enum Applicability {
370    MachineApplicable,
371    MaybeIncorrect,
372    HasPlaceholders,
373    Unspecified,
374}
375
376impl FromStr for Applicability {
377    type Err = ();
378
379    fn from_str(s: &str) -> Result<Self, Self::Err> {
380        match s {
381            "machine-applicable" => Ok(Applicability::MachineApplicable),
382            "maybe-incorrect" => Ok(Applicability::MaybeIncorrect),
383            "has-placeholders" => Ok(Applicability::HasPlaceholders),
384            "unspecified" => Ok(Applicability::Unspecified),
385            _ => Err(()),
386        }
387    }
388}
389
390impl quote::ToTokens for Applicability {
391    fn to_tokens(&self, tokens: &mut TokenStream) {
392        tokens.extend(match self {
393            Applicability::MachineApplicable => {
394                quote! { rustc_errors::Applicability::MachineApplicable }
395            }
396            Applicability::MaybeIncorrect => {
397                quote! { rustc_errors::Applicability::MaybeIncorrect }
398            }
399            Applicability::HasPlaceholders => {
400                quote! { rustc_errors::Applicability::HasPlaceholders }
401            }
402            Applicability::Unspecified => {
403                quote! { rustc_errors::Applicability::Unspecified }
404            }
405        });
406    }
407}
408
409/// Build the mapping of field names to fields. This allows attributes to peek values from
410/// other fields.
411pub(super) fn build_field_mapping(variant: &VariantInfo<'_>) -> HashMap<String, TokenStream> {
412    let mut fields_map = FieldMap::new();
413    for binding in variant.bindings() {
414        if let Some(ident) = &binding.ast().ident {
415            fields_map.insert(ident.to_string(), quote! { #binding });
416        }
417    }
418    fields_map
419}
420
421#[derive(Copy, Clone, Debug)]
422pub(super) enum AllowMultipleAlternatives {
423    No,
424    Yes,
425}
426
427fn parse_suggestion_values(
428    nested: ParseStream<'_>,
429    allow_multiple: AllowMultipleAlternatives,
430) -> syn::Result<Vec<LitStr>> {
431    if nested.parse::<Token![=]>().is_ok() {
432        return Ok(vec![nested.parse::<LitStr>()?]);
433    }
434
435    let content;
436    parenthesized!(content in nested);
437    if let AllowMultipleAlternatives::No = allow_multiple {
438        span_err(content.span().unwrap(), "expected exactly one string literal for `code = ...`")
439            .emit();
440        return Ok(vec![]);
441    }
442
443    let literals = Punctuated::<LitStr, Token![,]>::parse_terminated(&content);
444    Ok(match literals {
445        Ok(p) if p.is_empty() => {
446            span_err(
447                content.span().unwrap(),
448                "expected at least one string literal for `code(...)`",
449            )
450            .emit();
451            vec![]
452        }
453        Ok(p) => p.into_iter().collect(),
454        Err(_) => {
455            span_err(content.span().unwrap(), "`code(...)` must contain only string literals")
456                .emit();
457            vec![]
458        }
459    })
460}
461
462/// Constructs the `format!()` invocation(s) necessary for a `#[suggestion*(code = "foo")]` or
463/// `#[suggestion*(code("foo", "bar"))]` attribute field
464pub(super) fn build_suggestion_code(
465    code_field: &Ident,
466    nested: ParseStream<'_>,
467    fields: &FieldMap,
468    allow_multiple: AllowMultipleAlternatives,
469) -> Result<TokenStream, syn::Error> {
470    let values = parse_suggestion_values(nested, allow_multiple)?;
471
472    Ok(if let AllowMultipleAlternatives::Yes = allow_multiple {
473        let formatted_strings: Vec<_> = values
474            .into_iter()
475            .map(|value| build_format(fields, &value.value(), value.span()))
476            .collect();
477        quote! { let #code_field = [#(#formatted_strings),*].into_iter(); }
478    } else if let [value] = values.as_slice() {
479        let formatted_str = build_format(fields, &value.value(), value.span());
480        quote! { let #code_field = #formatted_str; }
481    } else {
482        // error handled previously
483        quote! { let #code_field = String::new(); }
484    })
485}
486
487/// Possible styles for suggestion subdiagnostics.
488#[derive(Clone, Copy, PartialEq)]
489pub(super) enum SuggestionKind {
490    Normal,
491    Short,
492    Hidden,
493    Verbose,
494    ToolOnly,
495}
496
497impl FromStr for SuggestionKind {
498    type Err = ();
499
500    fn from_str(s: &str) -> Result<Self, Self::Err> {
501        match s {
502            "normal" => Ok(SuggestionKind::Normal),
503            "short" => Ok(SuggestionKind::Short),
504            "hidden" => Ok(SuggestionKind::Hidden),
505            "verbose" => Ok(SuggestionKind::Verbose),
506            "tool-only" => Ok(SuggestionKind::ToolOnly),
507            _ => Err(()),
508        }
509    }
510}
511
512impl fmt::Display for SuggestionKind {
513    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514        match self {
515            SuggestionKind::Normal => write!(f, "normal"),
516            SuggestionKind::Short => write!(f, "short"),
517            SuggestionKind::Hidden => write!(f, "hidden"),
518            SuggestionKind::Verbose => write!(f, "verbose"),
519            SuggestionKind::ToolOnly => write!(f, "tool-only"),
520        }
521    }
522}
523
524impl SuggestionKind {
525    pub(crate) fn to_suggestion_style(&self) -> TokenStream {
526        match self {
527            SuggestionKind::Normal => {
528                quote! { rustc_errors::SuggestionStyle::ShowCode }
529            }
530            SuggestionKind::Short => {
531                quote! { rustc_errors::SuggestionStyle::HideCodeInline }
532            }
533            SuggestionKind::Hidden => {
534                quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
535            }
536            SuggestionKind::Verbose => {
537                quote! { rustc_errors::SuggestionStyle::ShowAlways }
538            }
539            SuggestionKind::ToolOnly => {
540                quote! { rustc_errors::SuggestionStyle::CompletelyHidden }
541            }
542        }
543    }
544
545    fn from_suffix(s: &str) -> Option<Self> {
546        match s {
547            "" => Some(SuggestionKind::Normal),
548            "_short" => Some(SuggestionKind::Short),
549            "_hidden" => Some(SuggestionKind::Hidden),
550            "_verbose" => Some(SuggestionKind::Verbose),
551            _ => None,
552        }
553    }
554}
555
556/// Types of subdiagnostics that can be created using attributes
557#[derive(Clone)]
558pub(super) enum SubdiagnosticKind {
559    /// `#[label(...)]`
560    Label,
561    /// `#[note(...)]`
562    Note,
563    /// `#[note_once(...)]`
564    NoteOnce,
565    /// `#[help(...)]`
566    Help,
567    /// `#[help_once(...)]`
568    HelpOnce,
569    /// `#[warning(...)]`
570    Warn,
571    /// `#[suggestion{,_short,_hidden,_verbose}]`
572    Suggestion {
573        suggestion_kind: SuggestionKind,
574        applicability: SpannedOption<Applicability>,
575        /// Identifier for variable used for formatted code, e.g. `___code_0`. Enables separation
576        /// of formatting and diagnostic emission so that `arg` calls can happen in-between..
577        code_field: syn::Ident,
578        /// Initialization logic for `code_field`'s variable, e.g.
579        /// `let __formatted_code = /* whatever */;`
580        code_init: TokenStream,
581    },
582    /// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
583    MultipartSuggestion {
584        suggestion_kind: SuggestionKind,
585        applicability: SpannedOption<Applicability>,
586    },
587}
588
589pub(super) struct SubdiagnosticVariant {
590    pub(super) kind: SubdiagnosticKind,
591    pub(super) message: Option<Message>,
592}
593
594impl SubdiagnosticVariant {
595    /// Constructs a `SubdiagnosticVariant` from a field or type attribute such as `#[note]`,
596    /// `#[error("add parenthesis")]` or `#[suggestion(code = "...")]`. Returns the
597    /// `SubdiagnosticKind` and the diagnostic message, if specified.
598    pub(super) fn from_attr(
599        attr: &Attribute,
600        fields: &FieldMap,
601    ) -> Result<Option<SubdiagnosticVariant>, DiagnosticDeriveError> {
602        // Always allow documentation comments.
603        if is_doc_comment(attr) {
604            return Ok(None);
605        }
606
607        let span = attr.span().unwrap();
608
609        let name = attr.path().segments.last().unwrap().ident.to_string();
610        let name = name.as_str();
611
612        let mut kind = match name {
613            "label" => SubdiagnosticKind::Label,
614            "note" => SubdiagnosticKind::Note,
615            "note_once" => SubdiagnosticKind::NoteOnce,
616            "help" => SubdiagnosticKind::Help,
617            "help_once" => SubdiagnosticKind::HelpOnce,
618            "warning" => SubdiagnosticKind::Warn,
619            _ => {
620                // Recover old `#[(multipart_)suggestion_*]` syntaxes
621                // FIXME(#100717): remove
622                if let Some(suggestion_kind) =
623                    name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix)
624                {
625                    if suggestion_kind != SuggestionKind::Normal {
626                        invalid_attr(attr)
627                            .help(format!(
628                                r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"#
629                            ))
630                            .emit();
631                    }
632
633                    SubdiagnosticKind::Suggestion {
634                        suggestion_kind: SuggestionKind::Normal,
635                        applicability: None,
636                        code_field: new_code_ident(),
637                        code_init: TokenStream::new(),
638                    }
639                } else if let Some(suggestion_kind) =
640                    name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix)
641                {
642                    if suggestion_kind != SuggestionKind::Normal {
643                        invalid_attr(attr)
644                            .help(format!(
645                                r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"#
646                            ))
647                            .emit();
648                    }
649
650                    SubdiagnosticKind::MultipartSuggestion {
651                        suggestion_kind: SuggestionKind::Normal,
652                        applicability: None,
653                    }
654                } else {
655                    throw_invalid_attr!(attr);
656                }
657            }
658        };
659
660        let list = match &attr.meta {
661            Meta::List(list) => {
662                // An attribute with properties, such as `#[suggestion(code = "...")]` or
663                // `#[error("message")]`
664                list
665            }
666            Meta::Path(_) => {
667                // An attribute without a message or other properties, such as `#[note]` - return
668                // without further processing.
669                //
670                // Only allow this if there are no mandatory properties, such as `code = "..."` in
671                // `#[suggestion(...)]`
672                match kind {
673                    SubdiagnosticKind::Label
674                    | SubdiagnosticKind::Note
675                    | SubdiagnosticKind::NoteOnce
676                    | SubdiagnosticKind::Help
677                    | SubdiagnosticKind::HelpOnce
678                    | SubdiagnosticKind::Warn
679                    | SubdiagnosticKind::MultipartSuggestion { .. } => {
680                        return Ok(Some(SubdiagnosticVariant { kind, message: None }));
681                    }
682                    SubdiagnosticKind::Suggestion { .. } => {
683                        throw_span_err!(span, "suggestion without `code = \"...\"`")
684                    }
685                }
686            }
687            _ => {
688                throw_invalid_attr!(attr)
689            }
690        };
691
692        let mut code = None;
693        let mut suggestion_kind = None;
694
695        let mut message = None;
696
697        list.parse_args_with(|input: ParseStream<'_>| {
698            let mut is_first = true;
699            while !input.is_empty() {
700                // Try to parse an inline diagnostic message
701                if input.peek(LitStr) {
702                    let inline_message = input.parse::<LitStr>()?;
703                    if !inline_message.suffix().is_empty() {
704                        span_err(
705                            inline_message.span().unwrap(),
706                            "Inline message is not allowed to have a suffix",
707                        ).emit();
708                    }
709                    if !input.is_empty() { input.parse::<Token![,]>()?; }
710                    if is_first {
711                        message = Some(Message { attr_span: attr.span(), message_span: inline_message.span(), value: inline_message.value() });
712                        is_first = false;
713                    } else {
714                        span_err(inline_message.span().unwrap(), "a diagnostic message must be the first argument to the attribute").emit();
715                    }
716                    continue
717                }
718                is_first = false;
719
720                // Try to parse an argument
721                let arg_name: Path = input.parse::<Path>()?;
722                let arg_name_span = arg_name.span().unwrap();
723                match (arg_name.require_ident()?.to_string().as_str(), &mut kind) {
724                    ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
725                        let code_init = build_suggestion_code(
726                            &code_field,
727                            &input,
728                            fields,
729                            AllowMultipleAlternatives::Yes,
730                        )?;
731                        code.set_once(code_init, arg_name_span);
732                    }
733                    (
734                        "applicability",
735                        SubdiagnosticKind::Suggestion { applicability, .. }
736                        | SubdiagnosticKind::MultipartSuggestion { applicability, .. },
737                    ) => {
738                        input.parse::<Token![=]>()?;
739                        let value = input.parse::<LitStr>()?;
740                        let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| {
741                            span_err(value.span().unwrap(), "invalid applicability").emit();
742                            Applicability::Unspecified
743                        });
744                        applicability.set_once(value, span);
745                    }
746                    (
747                        "style",
748                        SubdiagnosticKind::Suggestion { .. }
749                        | SubdiagnosticKind::MultipartSuggestion { .. },
750                    ) => {
751                        input.parse::<Token![=]>()?;
752                        let value = input.parse::<LitStr>()?;
753
754                        let value = value.value().parse().unwrap_or_else(|()| {
755                            span_err(value.span().unwrap(), "invalid suggestion style")
756                                .help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`")
757                                .emit();
758                            SuggestionKind::Normal
759                        });
760
761                        suggestion_kind.set_once(value, span);
762                    }
763
764
765                    // Invalid nested attribute
766                    (_, SubdiagnosticKind::Suggestion { .. }) => {
767                        span_err(arg_name_span, "invalid nested attribute")
768                            .help(
769                                "only `style`, `code` and `applicability` are valid nested attributes",
770                            )
771                            .emit();
772                        // Consume the rest of the input to avoid spamming errors
773                        let _ = input.parse::<TokenStream>();
774                    }
775                    (_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
776                        span_err(arg_name_span, "invalid nested attribute")
777                            .help("only `style` and `applicability` are valid nested attributes")
778                            .emit();
779                        // Consume the rest of the input to avoid spamming errors
780                        let _ = input.parse::<TokenStream>();
781                    }
782                    _ => {
783                        span_err(arg_name_span, "no nested attribute expected here").emit();
784                        // Consume the rest of the input to avoid spamming errors
785                        let _ = input.parse::<TokenStream>();
786                    }
787                }
788
789                if input.is_empty() { break }
790                input.parse::<Token![,]>()?;
791            }
792            Ok(())
793        })?;
794
795        match kind {
796            SubdiagnosticKind::Suggestion {
797                ref code_field,
798                ref mut code_init,
799                suggestion_kind: ref mut kind_field,
800                ..
801            } => {
802                if let Some(kind) = suggestion_kind.value() {
803                    *kind_field = kind;
804                }
805
806                *code_init = if let Some(init) = code.value() {
807                    init
808                } else {
809                    span_err(span, "suggestion without `code = \"...\"`").emit();
810                    quote! { let #code_field = std::iter::empty(); }
811                };
812            }
813            SubdiagnosticKind::MultipartSuggestion {
814                suggestion_kind: ref mut kind_field, ..
815            } => {
816                if let Some(kind) = suggestion_kind.value() {
817                    *kind_field = kind;
818                }
819            }
820            SubdiagnosticKind::Label
821            | SubdiagnosticKind::Note
822            | SubdiagnosticKind::NoteOnce
823            | SubdiagnosticKind::Help
824            | SubdiagnosticKind::HelpOnce
825            | SubdiagnosticKind::Warn => {}
826        }
827
828        Ok(Some(SubdiagnosticVariant { kind, message }))
829    }
830}
831
832impl quote::IdentFragment for SubdiagnosticKind {
833    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
834        match self {
835            SubdiagnosticKind::Label => write!(f, "label"),
836            SubdiagnosticKind::Note => write!(f, "note"),
837            SubdiagnosticKind::NoteOnce => write!(f, "note_once"),
838            SubdiagnosticKind::Help => write!(f, "help"),
839            SubdiagnosticKind::HelpOnce => write!(f, "help_once"),
840            SubdiagnosticKind::Warn => write!(f, "warn"),
841            SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestions_with_style"),
842            SubdiagnosticKind::MultipartSuggestion { .. } => {
843                write!(f, "multipart_suggestion_with_style")
844            }
845        }
846    }
847
848    fn span(&self) -> Option<proc_macro2::Span> {
849        None
850    }
851}
852
853/// Returns `true` if `field` should generate a `arg` call rather than any other diagnostic
854/// call (like `span_label`).
855pub(super) fn should_generate_arg(field: &Field) -> bool {
856    // Perhaps this should be an exhaustive list...
857    field.attrs.iter().all(|attr| is_doc_comment(attr))
858}
859
860pub(super) fn is_doc_comment(attr: &Attribute) -> bool {
861    attr.path().segments.last().unwrap().ident == "doc"
862}