Skip to main content

flux_macros/diagnostics/
utils.rs

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