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