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