]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_macros/src/diagnostics/utils.rs
New upstream version 1.68.2+dfsg1
[rustc.git] / compiler / rustc_macros / src / diagnostics / utils.rs
CommitLineData
2b03887a
FG
1use crate::diagnostics::error::{
2 span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
3};
04454e1e 4use proc_macro::Span;
2b03887a 5use proc_macro2::{Ident, TokenStream};
04454e1e 6use quote::{format_ident, quote, ToTokens};
2b03887a 7use std::cell::RefCell;
064997fb 8use std::collections::{BTreeSet, HashMap};
2b03887a 9use std::fmt;
04454e1e 10use std::str::FromStr;
2b03887a
FG
11use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple};
12use syn::{MetaList, MetaNameValue, NestedMeta, Path};
13use synstructure::{BindingInfo, VariantInfo};
14
487cf647 15use super::error::{invalid_attr, invalid_nested_attr};
2b03887a
FG
16
17thread_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.
22pub(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}
04454e1e
FG
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.
34pub(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 `()`.
49pub(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`.
54pub(crate) fn report_type_error(
55 attr: &Attribute,
56 ty_name: &str,
064997fb 57) -> Result<!, DiagnosticDeriveError> {
04454e1e
FG
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`.
77fn report_error_if_not_applied_to_ty(
78 attr: &Attribute,
79 info: &FieldInfo<'_>,
80 path: &[&str],
81 ty_name: &str,
064997fb 82) -> Result<(), DiagnosticDeriveError> {
487cf647 83 if !type_matches_path(info.ty, path) {
04454e1e
FG
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`.
91pub(crate) fn report_error_if_not_applied_to_applicability(
92 attr: &Attribute,
93 info: &FieldInfo<'_>,
064997fb 94) -> Result<(), DiagnosticDeriveError> {
04454e1e
FG
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`.
104pub(crate) fn report_error_if_not_applied_to_span(
105 attr: &Attribute,
106 info: &FieldInfo<'_>,
064997fb 107) -> Result<(), DiagnosticDeriveError> {
487cf647
FG
108 if !type_matches_path(info.ty, &["rustc_span", "Span"])
109 && !type_matches_path(info.ty, &["rustc_errors", "MultiSpan"])
064997fb
FG
110 {
111 report_type_error(attr, "`Span` or `MultiSpan`")?;
112 }
113
114 Ok(())
04454e1e
FG
115}
116
117/// Inner type of a field and type of wrapper.
118pub(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
127impl<'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
2b03887a
FG
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
04454e1e
FG
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.
195pub(crate) struct FieldInfo<'a> {
04454e1e
FG
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.
203pub(crate) trait SetOnce<T> {
2b03887a 204 fn set_once(&mut self, value: T, span: Span);
064997fb
FG
205
206 fn value(self) -> Option<T>;
2b03887a 207 fn value_ref(&self) -> Option<&T>;
04454e1e
FG
208}
209
2b03887a
FG
210/// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`].
211pub(super) type SpannedOption<T> = Option<(T, Span)>;
212
213impl<T> SetOnce<T> for SpannedOption<T> {
214 fn set_once(&mut self, value: T, span: Span) {
04454e1e
FG
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 }
064997fb
FG
226
227 fn value(self) -> Option<T> {
228 self.map(|(v, _)| v)
229 }
2b03887a
FG
230
231 fn value_ref(&self) -> Option<&T> {
232 self.as_ref().map(|(v, _)| v)
233 }
04454e1e
FG
234}
235
2b03887a
FG
236pub(super) type FieldMap = HashMap<String, TokenStream>;
237
04454e1e
FG
238pub(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() {
f2b60f7d
FG
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;
04454e1e 300 }
04454e1e 301 }
f2b60f7d
FG
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);
04454e1e
FG
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(),
9c376795 325 &format!("`{field}` doesn't refer to a field on this type"),
04454e1e
FG
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.
2b03887a 345#[derive(Clone, Copy)]
04454e1e
FG
346pub(crate) enum Applicability {
347 MachineApplicable,
348 MaybeIncorrect,
349 HasPlaceholders,
350 Unspecified,
351}
352
353impl 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
367impl 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}
064997fb
FG
385
386/// Build the mapping of field names to fields. This allows attributes to peek values from
387/// other fields.
9c376795 388pub(super) fn build_field_mapping(variant: &VariantInfo<'_>) -> HashMap<String, TokenStream> {
2b03887a
FG
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)]
399pub(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
406pub(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()
064997fb
FG
447 }
448 }
2b03887a
FG
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(); }
064997fb 471 }
2b03887a 472}
064997fb 473
2b03887a 474/// Possible styles for suggestion subdiagnostics.
487cf647 475#[derive(Clone, Copy, PartialEq)]
2b03887a 476pub(super) enum SuggestionKind {
2b03887a 477 Normal,
2b03887a 478 Short,
2b03887a 479 Hidden,
2b03887a 480 Verbose,
487cf647 481 ToolOnly,
2b03887a
FG
482}
483
484impl FromStr for SuggestionKind {
485 type Err = ();
486
487 fn from_str(s: &str) -> Result<Self, Self::Err> {
488 match s {
487cf647
FG
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),
2b03887a
FG
494 _ => Err(()),
495 }
496 }
497}
498
487cf647
FG
499impl 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
2b03887a
FG
511impl 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 }
487cf647
FG
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,
2b03887a
FG
539 }
540 }
541}
542
543/// Types of subdiagnostics that can be created using attributes
544#[derive(Clone)]
545pub(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
572impl 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()?;
487cf647 591
2b03887a
FG
592 let mut kind = match name {
593 "label" => SubdiagnosticKind::Label,
594 "note" => SubdiagnosticKind::Note,
595 "help" => SubdiagnosticKind::Help,
596 "warning" => SubdiagnosticKind::Warn,
597 _ => {
487cf647
FG
598 // Recover old `#[(multipart_)suggestion_*]` syntaxes
599 // FIXME(#100717): remove
2b03887a 600 if let Some(suggestion_kind) =
487cf647 601 name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix)
2b03887a 602 {
487cf647
FG
603 if suggestion_kind != SuggestionKind::Normal {
604 invalid_attr(attr, &meta)
605 .help(format!(
9c376795 606 r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"#
487cf647
FG
607 ))
608 .emit();
609 }
610
2b03887a 611 SubdiagnosticKind::Suggestion {
487cf647 612 suggestion_kind: SuggestionKind::Normal,
2b03887a
FG
613 applicability: None,
614 code_field: new_code_ident(),
615 code_init: TokenStream::new(),
616 }
617 } else if let Some(suggestion_kind) =
487cf647 618 name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix)
2b03887a 619 {
487cf647
FG
620 if suggestion_kind != SuggestionKind::Normal {
621 invalid_attr(attr, &meta)
622 .help(format!(
9c376795 623 r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"#
487cf647
FG
624 ))
625 .emit();
626 }
627
628 SubdiagnosticKind::MultipartSuggestion {
629 suggestion_kind: SuggestionKind::Normal,
630 applicability: None,
631 }
2b03887a
FG
632 } else {
633 throw_invalid_attr!(attr, &meta);
634 }
635 }
636 };
637
638 let nested = match meta {
639 Meta::List(MetaList { ref nested, .. }) => {
640 // An attribute with properties, such as `#[suggestion(code = "...")]` or
641 // `#[error(some::slug)]`
642 nested
643 }
644 Meta::Path(_) => {
645 // An attribute without a slug or other properties, such as `#[note]` - return
646 // without further processing.
647 //
648 // Only allow this if there are no mandatory properties, such as `code = "..."` in
649 // `#[suggestion(...)]`
650 match kind {
651 SubdiagnosticKind::Label
652 | SubdiagnosticKind::Note
653 | SubdiagnosticKind::Help
654 | SubdiagnosticKind::Warn
655 | SubdiagnosticKind::MultipartSuggestion { .. } => {
656 return Ok(Some((kind, None)));
657 }
658 SubdiagnosticKind::Suggestion { .. } => {
659 throw_span_err!(span, "suggestion without `code = \"...\"`")
660 }
661 }
662 }
663 _ => {
664 throw_invalid_attr!(attr, &meta)
665 }
666 };
667
668 let mut code = None;
487cf647 669 let mut suggestion_kind = None;
2b03887a
FG
670
671 let mut nested_iter = nested.into_iter().peekable();
672
673 // Peek at the first nested attribute: if it's a slug path, consume it.
674 let slug = if let Some(NestedMeta::Meta(Meta::Path(path))) = nested_iter.peek() {
675 let path = path.clone();
676 // Advance the iterator.
677 nested_iter.next();
678 Some(path)
679 } else {
680 None
681 };
682
683 for nested_attr in nested_iter {
684 let meta = match nested_attr {
685 NestedMeta::Meta(ref meta) => meta,
686 NestedMeta::Lit(_) => {
487cf647 687 invalid_nested_attr(attr, nested_attr).emit();
2b03887a
FG
688 continue;
689 }
690 };
691
692 let span = meta.span().unwrap();
693 let nested_name = meta.path().segments.last().unwrap().ident.to_string();
694 let nested_name = nested_name.as_str();
695
696 let string_value = match meta {
697 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => Some(value),
698
487cf647 699 Meta::Path(_) => throw_invalid_nested_attr!(attr, nested_attr, |diag| {
2b03887a
FG
700 diag.help("a diagnostic slug must be the first argument to the attribute")
701 }),
702 _ => None,
703 };
704
705 match (nested_name, &mut kind) {
706 ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
707 let code_init = build_suggestion_code(
708 code_field,
709 meta,
710 fields,
711 AllowMultipleAlternatives::Yes,
712 );
713 code.set_once(code_init, span);
714 }
715 (
716 "applicability",
717 SubdiagnosticKind::Suggestion { ref mut applicability, .. }
718 | SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. },
719 ) => {
720 let Some(value) = string_value else {
487cf647 721 invalid_nested_attr(attr, nested_attr).emit();
2b03887a
FG
722 continue;
723 };
724
725 let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| {
726 span_err(span, "invalid applicability").emit();
727 Applicability::Unspecified
728 });
729 applicability.set_once(value, span);
730 }
487cf647
FG
731 (
732 "style",
733 SubdiagnosticKind::Suggestion { .. }
734 | SubdiagnosticKind::MultipartSuggestion { .. },
735 ) => {
736 let Some(value) = string_value else {
737 invalid_nested_attr(attr, nested_attr).emit();
738 continue;
739 };
740
741 let value = value.value().parse().unwrap_or_else(|()| {
742 span_err(value.span().unwrap(), "invalid suggestion style")
743 .help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`")
744 .emit();
745 SuggestionKind::Normal
746 });
747
748 suggestion_kind.set_once(value, span);
749 }
2b03887a
FG
750
751 // Invalid nested attribute
752 (_, SubdiagnosticKind::Suggestion { .. }) => {
487cf647
FG
753 invalid_nested_attr(attr, nested_attr)
754 .help(
755 "only `style`, `code` and `applicability` are valid nested attributes",
756 )
2b03887a
FG
757 .emit();
758 }
759 (_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
487cf647
FG
760 invalid_nested_attr(attr, nested_attr)
761 .help("only `style` and `applicability` are valid nested attributes")
2b03887a
FG
762 .emit()
763 }
764 _ => {
487cf647 765 invalid_nested_attr(attr, nested_attr).emit();
2b03887a
FG
766 }
767 }
768 }
769
770 match kind {
487cf647
FG
771 SubdiagnosticKind::Suggestion {
772 ref code_field,
773 ref mut code_init,
774 suggestion_kind: ref mut kind_field,
775 ..
776 } => {
777 if let Some(kind) = suggestion_kind.value() {
778 *kind_field = kind;
779 }
780
2b03887a
FG
781 *code_init = if let Some(init) = code.value() {
782 init
783 } else {
784 span_err(span, "suggestion without `code = \"...\"`").emit();
785 quote! { let #code_field = std::iter::empty(); }
786 };
787 }
487cf647
FG
788 SubdiagnosticKind::MultipartSuggestion {
789 suggestion_kind: ref mut kind_field, ..
790 } => {
791 if let Some(kind) = suggestion_kind.value() {
792 *kind_field = kind;
793 }
794 }
2b03887a
FG
795 SubdiagnosticKind::Label
796 | SubdiagnosticKind::Note
797 | SubdiagnosticKind::Help
487cf647 798 | SubdiagnosticKind::Warn => {}
2b03887a
FG
799 }
800
801 Ok(Some((kind, slug)))
802 }
803}
804
805impl quote::IdentFragment for SubdiagnosticKind {
806 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
807 match self {
808 SubdiagnosticKind::Label => write!(f, "label"),
809 SubdiagnosticKind::Note => write!(f, "note"),
810 SubdiagnosticKind::Help => write!(f, "help"),
811 SubdiagnosticKind::Warn => write!(f, "warn"),
812 SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestions_with_style"),
813 SubdiagnosticKind::MultipartSuggestion { .. } => {
814 write!(f, "multipart_suggestion_with_style")
815 }
816 }
817 }
818
819 fn span(&self) -> Option<proc_macro2::Span> {
820 None
821 }
822}
823
824/// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
825/// call (like `span_label`).
826pub(super) fn should_generate_set_arg(field: &Field) -> bool {
827 field.attrs.is_empty()
828}
829
830pub(super) fn is_doc_comment(attr: &Attribute) -> bool {
487cf647 831 attr.path.segments.last().unwrap().ident == "doc"
064997fb 832}