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