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