1use std::cell::RefCell;
2use std::collections::{BTreeSet, HashSet};
3use std::fmt;
4use std::str::FromStr;
5
6use indexmap::IndexMap;
7use proc_macro::Span;
8use proc_macro2::{Ident, TokenStream};
9use quote::{ToTokens, format_ident, quote};
10use syn::parse::ParseStream;
11use syn::punctuated::Punctuated;
12use syn::spanned::Spanned;
13use syn::{Attribute, Field, LitStr, Meta, Path, Token, Type, TypeTuple, parenthesized};
14use synstructure::{BindingInfo, VariantInfo};
15
16use super::error::invalid_attr;
17use crate::diagnostics::error::{
18 DiagnosticDeriveError, span_err, throw_invalid_attr, throw_span_err,
19};
20use crate::diagnostics::message::Message;
21
22thread_local! {
23 pub(crate) static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0);
24}
25
26pub(crate) fn new_code_ident() -> syn::Ident {
28 CODE_IDENT_COUNT.with(|count| {
29 let ident = format_ident!("__code_{}", *count.borrow());
30 *count.borrow_mut() += 1;
31 ident
32 })
33}
34
35pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool {
40 if let Type::Path(ty) = ty {
41 ty.path
42 .segments
43 .iter()
44 .map(|s| s.ident.to_string())
45 .rev()
46 .zip(name.iter().rev())
47 .all(|(x, y)| &x.as_str() == y)
48 } else {
49 false
50 }
51}
52
53pub(crate) fn type_is_unit(ty: &Type) -> bool {
55 if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false }
56}
57
58pub(crate) fn type_is_bool(ty: &Type) -> bool {
60 type_matches_path(ty, &["bool"])
61}
62
63pub(crate) fn report_type_error(
65 attr: &Attribute,
66 ty_name: &str,
67) -> Result<!, DiagnosticDeriveError> {
68 let name = attr.path().segments.last().unwrap().ident.to_string();
69 let meta = &attr.meta;
70
71 throw_span_err!(
72 attr.span().unwrap(),
73 &format!(
74 "the `#[{}{}]` attribute can only be applied to fields of type {}",
75 name,
76 match meta {
77 Meta::Path(_) => "",
78 Meta::NameValue(_) => " = ...",
79 Meta::List(_) => "(...)",
80 },
81 ty_name
82 )
83 );
84}
85
86fn report_error_if_not_applied_to_ty(
88 attr: &Attribute,
89 info: &FieldInfo<'_>,
90 path: &[&str],
91 ty_name: &str,
92) -> Result<(), DiagnosticDeriveError> {
93 if !type_matches_path(info.ty.inner_type(), path) {
94 report_type_error(attr, ty_name)?;
95 }
96
97 Ok(())
98}
99
100pub(crate) fn report_error_if_not_applied_to_applicability(
102 attr: &Attribute,
103 info: &FieldInfo<'_>,
104) -> Result<(), DiagnosticDeriveError> {
105 report_error_if_not_applied_to_ty(
106 attr,
107 info,
108 &["rustc_errors", "Applicability"],
109 "`Applicability`",
110 )
111}
112
113pub(crate) fn report_error_if_not_applied_to_span(
115 attr: &Attribute,
116 info: &FieldInfo<'_>,
117) -> Result<(), DiagnosticDeriveError> {
118 if !type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"])
119 && !type_matches_path(info.ty.inner_type(), &["rustc_errors", "MultiSpan"])
120 {
121 report_type_error(attr, "`Span` or `MultiSpan`")?;
122 }
123
124 Ok(())
125}
126
127#[derive(Copy, Clone)]
129pub(crate) enum FieldInnerTy<'ty> {
130 Option(&'ty Type),
132 Vec(&'ty Type),
134 Plain(&'ty Type),
136}
137
138impl<'ty> FieldInnerTy<'ty> {
139 pub(crate) fn from_type(ty: &'ty Type) -> Self {
145 fn single_generic_type(ty: &Type) -> &Type {
146 let Type::Path(ty_path) = ty else {
147 panic!("expected path type");
148 };
149
150 let path = &ty_path.path;
151 let ty = path.segments.last().unwrap();
152 let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments else {
153 panic!("expected bracketed generic arguments");
154 };
155
156 assert_eq!(bracketed.args.len(), 1);
157
158 let syn::GenericArgument::Type(ty) = &bracketed.args[0] else {
159 panic!("expected generic parameter to be a type generic");
160 };
161
162 ty
163 }
164
165 if type_matches_path(ty, &["std", "option", "Option"]) {
166 FieldInnerTy::Option(single_generic_type(ty))
167 } else if type_matches_path(ty, &["std", "vec", "Vec"]) {
168 FieldInnerTy::Vec(single_generic_type(ty))
169 } else {
170 FieldInnerTy::Plain(ty)
171 }
172 }
173
174 pub(crate) fn will_iterate(&self) -> bool {
177 match self {
178 FieldInnerTy::Vec(..) => true,
179 FieldInnerTy::Option(..) | FieldInnerTy::Plain(_) => false,
180 }
181 }
182
183 pub(crate) fn inner_type(&self) -> &'ty Type {
185 match self {
186 FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) | FieldInnerTy::Plain(inner) => {
187 inner
188 }
189 }
190 }
191
192 pub(crate) fn with(&self, binding: impl ToTokens, inner: impl ToTokens) -> TokenStream {
194 match self {
195 FieldInnerTy::Option(..) => quote! {
196 if let Some(#binding) = #binding {
197 #inner
198 }
199 },
200 FieldInnerTy::Vec(..) => quote! {
201 for #binding in #binding {
202 #inner
203 }
204 },
205 FieldInnerTy::Plain(t) if type_is_bool(t) => quote! {
206 if #binding {
207 #inner
208 }
209 },
210 FieldInnerTy::Plain(..) => quote! { #inner },
211 }
212 }
213
214 pub(crate) fn span(&self) -> proc_macro2::Span {
215 match self {
216 FieldInnerTy::Option(ty) | FieldInnerTy::Vec(ty) | FieldInnerTy::Plain(ty) => ty.span(),
217 }
218 }
219}
220
221pub(crate) struct FieldInfo<'a> {
224 pub(crate) binding: &'a BindingInfo<'a>,
225 pub(crate) ty: FieldInnerTy<'a>,
226 pub(crate) span: &'a proc_macro2::Span,
227}
228
229pub(crate) trait SetOnce<T> {
232 fn set_once(&mut self, value: T, span: Span);
233
234 fn value(self) -> Option<T>;
235 fn value_ref(&self) -> Option<&T>;
236}
237
238pub(super) type SpannedOption<T> = Option<(T, Span)>;
240
241impl<T> SetOnce<T> for SpannedOption<T> {
242 fn set_once(&mut self, value: T, span: Span) {
243 match self {
244 None => {
245 *self = Some((value, span));
246 }
247 Some((_, prev_span)) => {
248 span_err(span, "attribute specified multiple times")
249 .span_note(*prev_span, "previously specified here")
250 .emit();
251 }
252 }
253 }
254
255 fn value(self) -> Option<T> {
256 self.map(|(v, _)| v)
257 }
258
259 fn value_ref(&self) -> Option<&T> {
260 self.as_ref().map(|(v, _)| v)
261 }
262}
263
264pub(super) type FieldMap = IndexMap<String, (syn::Ident, TokenStream)>;
265
266pub(super) fn build_format(
289 field_map: &FieldMap,
290 input: &str,
291 span: proc_macro2::Span,
292) -> TokenStream {
293 let mut referenced_fields: BTreeSet<String> = BTreeSet::new();
297
298 let mut it = input.chars().peekable();
300
301 while let Some(c) = it.next() {
305 if c != '{' {
306 continue;
307 }
308 if *it.peek().unwrap_or(&'\0') == '{' {
309 assert_eq!(it.next().unwrap(), '{');
310 continue;
311 }
312 let mut eat_argument = || -> Option<String> {
313 let mut result = String::new();
314 while let Some(c) = it.next() {
320 result.push(c);
321 let next = *it.peek().unwrap_or(&'\0');
322 if next == '}' {
323 break;
324 } else if next == ':' {
325 assert_eq!(it.next().unwrap(), ':');
327 break;
328 }
329 }
330 while it.next()? != '}' {
332 continue;
333 }
334 Some(result)
335 };
336
337 if let Some(referenced_field) = eat_argument() {
338 referenced_fields.insert(referenced_field);
339 }
340 }
341
342 let args = referenced_fields.into_iter().map(|field: String| {
346 let field_ident = format_ident!("{}", field);
347 let value = match field_map.get(&field) {
348 Some(value) => value.1.clone(),
349 None => {
351 span_err(span.unwrap(), format!("`{field}` doesn't refer to a field on this type"))
352 .emit();
353 quote! {
354 "{#field}"
355 }
356 }
357 };
358 quote! {
359 #field_ident = #value
360 }
361 });
362 quote! {
363 format!(#input #(,#args)*)
364 }
365}
366
367#[derive(Clone, Copy)]
370pub(crate) enum Applicability {
371 MachineApplicable,
372 MaybeIncorrect,
373 HasPlaceholders,
374 Unspecified,
375}
376
377impl FromStr for Applicability {
378 type Err = ();
379
380 fn from_str(s: &str) -> Result<Self, Self::Err> {
381 match s {
382 "machine-applicable" => Ok(Applicability::MachineApplicable),
383 "maybe-incorrect" => Ok(Applicability::MaybeIncorrect),
384 "has-placeholders" => Ok(Applicability::HasPlaceholders),
385 "unspecified" => Ok(Applicability::Unspecified),
386 _ => Err(()),
387 }
388 }
389}
390
391impl quote::ToTokens for Applicability {
392 fn to_tokens(&self, tokens: &mut TokenStream) {
393 tokens.extend(match self {
394 Applicability::MachineApplicable => {
395 quote! { rustc_errors::Applicability::MachineApplicable }
396 }
397 Applicability::MaybeIncorrect => {
398 quote! { rustc_errors::Applicability::MaybeIncorrect }
399 }
400 Applicability::HasPlaceholders => {
401 quote! { rustc_errors::Applicability::HasPlaceholders }
402 }
403 Applicability::Unspecified => {
404 quote! { rustc_errors::Applicability::Unspecified }
405 }
406 });
407 }
408}
409
410pub(super) fn build_field_mapping(variant: &VariantInfo<'_>) -> FieldMap {
413 let mut fields_map = FieldMap::new();
414 for binding in variant.bindings() {
415 if let Some(ident) = &binding.ast().ident {
416 fields_map.insert(ident.to_string(), (ident.clone(), quote! { #binding }));
417 }
418 }
419 fields_map
420}
421
422#[derive(Copy, Clone, Debug)]
423pub(super) enum AllowMultipleAlternatives {
424 No,
425 Yes,
426}
427
428fn parse_suggestion_values(
429 nested: ParseStream<'_>,
430 allow_multiple: AllowMultipleAlternatives,
431) -> syn::Result<Vec<LitStr>> {
432 if nested.parse::<Token![=]>().is_ok() {
433 return Ok(vec![nested.parse::<LitStr>()?]);
434 }
435
436 let content;
437 parenthesized!(content in nested);
438 if let AllowMultipleAlternatives::No = allow_multiple {
439 span_err(content.span().unwrap(), "expected exactly one string literal for `code = ...`")
440 .emit();
441 return Ok(vec![]);
442 }
443
444 let literals = Punctuated::<LitStr, Token![,]>::parse_terminated(&content);
445 Ok(match literals {
446 Ok(p) if p.is_empty() => {
447 span_err(
448 content.span().unwrap(),
449 "expected at least one string literal for `code(...)`",
450 )
451 .emit();
452 vec![]
453 }
454 Ok(p) => p.into_iter().collect(),
455 Err(_) => {
456 span_err(content.span().unwrap(), "`code(...)` must contain only string literals")
457 .emit();
458 vec![]
459 }
460 })
461}
462
463pub(super) fn build_suggestion_code(
466 code_field: &Ident,
467 nested: ParseStream<'_>,
468 fields: &FieldMap,
469 allow_multiple: AllowMultipleAlternatives,
470) -> Result<TokenStream, syn::Error> {
471 let values = parse_suggestion_values(nested, allow_multiple)?;
472
473 Ok(if let AllowMultipleAlternatives::Yes = allow_multiple {
474 let formatted_strings: Vec<_> = values
475 .into_iter()
476 .map(|value| build_format(fields, &value.value(), value.span()))
477 .collect();
478 quote! { let #code_field = [#(#formatted_strings),*].into_iter(); }
479 } else if let [value] = values.as_slice() {
480 let formatted_str = build_format(fields, &value.value(), value.span());
481 quote! { let #code_field = #formatted_str; }
482 } else {
483 quote! { let #code_field = String::new(); }
485 })
486}
487
488#[derive(Clone, Copy, PartialEq)]
490pub(super) enum SuggestionKind {
491 Normal,
492 Short,
493 Hidden,
494 Verbose,
495 ToolOnly,
496}
497
498impl FromStr for SuggestionKind {
499 type Err = ();
500
501 fn from_str(s: &str) -> Result<Self, Self::Err> {
502 match s {
503 "normal" => Ok(SuggestionKind::Normal),
504 "short" => Ok(SuggestionKind::Short),
505 "hidden" => Ok(SuggestionKind::Hidden),
506 "verbose" => Ok(SuggestionKind::Verbose),
507 "tool-only" => Ok(SuggestionKind::ToolOnly),
508 _ => Err(()),
509 }
510 }
511}
512
513impl fmt::Display for SuggestionKind {
514 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
515 match self {
516 SuggestionKind::Normal => write!(f, "normal"),
517 SuggestionKind::Short => write!(f, "short"),
518 SuggestionKind::Hidden => write!(f, "hidden"),
519 SuggestionKind::Verbose => write!(f, "verbose"),
520 SuggestionKind::ToolOnly => write!(f, "tool-only"),
521 }
522 }
523}
524
525impl SuggestionKind {
526 pub(crate) fn to_suggestion_style(&self) -> TokenStream {
527 match self {
528 SuggestionKind::Normal => {
529 quote! { rustc_errors::SuggestionStyle::ShowCode }
530 }
531 SuggestionKind::Short => {
532 quote! { rustc_errors::SuggestionStyle::HideCodeInline }
533 }
534 SuggestionKind::Hidden => {
535 quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
536 }
537 SuggestionKind::Verbose => {
538 quote! { rustc_errors::SuggestionStyle::ShowAlways }
539 }
540 SuggestionKind::ToolOnly => {
541 quote! { rustc_errors::SuggestionStyle::CompletelyHidden }
542 }
543 }
544 }
545
546 fn from_suffix(s: &str) -> Option<Self> {
547 match s {
548 "" => Some(SuggestionKind::Normal),
549 "_short" => Some(SuggestionKind::Short),
550 "_hidden" => Some(SuggestionKind::Hidden),
551 "_verbose" => Some(SuggestionKind::Verbose),
552 _ => None,
553 }
554 }
555}
556
557#[derive(Clone)]
559pub(super) enum SubdiagnosticKind {
560 Label,
562 Note,
564 NoteOnce,
566 Help,
568 HelpOnce,
570 Warn,
572 Suggestion {
574 suggestion_kind: SuggestionKind,
575 applicability: SpannedOption<Applicability>,
576 code_field: syn::Ident,
579 code_init: TokenStream,
582 },
583 MultipartSuggestion {
585 suggestion_kind: SuggestionKind,
586 applicability: SpannedOption<Applicability>,
587 },
588}
589
590pub(super) struct SubdiagnosticVariant {
591 pub(super) kind: SubdiagnosticKind,
592 pub(super) message: Option<Message>,
593}
594
595impl SubdiagnosticVariant {
596 pub(super) fn from_attr(
600 attr: &Attribute,
601 fields: &FieldMap,
602 used_fields: &mut HashSet<proc_macro2::Ident>,
603 ) -> Result<Option<SubdiagnosticVariant>, DiagnosticDeriveError> {
604 if is_doc_comment(attr) {
606 return Ok(None);
607 }
608
609 let span = attr.span().unwrap();
610
611 let name = attr.path().segments.last().unwrap().ident.to_string();
612 let name = name.as_str();
613
614 let mut kind = match name {
615 "label" => SubdiagnosticKind::Label,
616 "note" => SubdiagnosticKind::Note,
617 "note_once" => SubdiagnosticKind::NoteOnce,
618 "help" => SubdiagnosticKind::Help,
619 "help_once" => SubdiagnosticKind::HelpOnce,
620 "warning" => SubdiagnosticKind::Warn,
621 _ => {
622 if let Some(suggestion_kind) =
625 name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix)
626 {
627 if suggestion_kind != SuggestionKind::Normal {
628 invalid_attr(attr)
629 .help(format!(
630 r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"#
631 ))
632 .emit();
633 }
634
635 SubdiagnosticKind::Suggestion {
636 suggestion_kind: SuggestionKind::Normal,
637 applicability: None,
638 code_field: new_code_ident(),
639 code_init: TokenStream::new(),
640 }
641 } else if let Some(suggestion_kind) =
642 name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix)
643 {
644 if suggestion_kind != SuggestionKind::Normal {
645 invalid_attr(attr)
646 .help(format!(
647 r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"#
648 ))
649 .emit();
650 }
651
652 SubdiagnosticKind::MultipartSuggestion {
653 suggestion_kind: SuggestionKind::Normal,
654 applicability: None,
655 }
656 } else {
657 throw_invalid_attr!(attr);
658 }
659 }
660 };
661
662 let list = match &attr.meta {
663 Meta::List(list) => {
664 list
667 }
668 Meta::Path(_) => {
669 match kind {
675 SubdiagnosticKind::Label
676 | SubdiagnosticKind::Note
677 | SubdiagnosticKind::NoteOnce
678 | SubdiagnosticKind::Help
679 | SubdiagnosticKind::HelpOnce
680 | SubdiagnosticKind::Warn
681 | SubdiagnosticKind::MultipartSuggestion { .. } => {
682 return Ok(Some(SubdiagnosticVariant { kind, message: None }));
683 }
684 SubdiagnosticKind::Suggestion { .. } => {
685 throw_span_err!(span, "suggestion without `code = \"...\"`")
686 }
687 }
688 }
689 _ => {
690 throw_invalid_attr!(attr)
691 }
692 };
693
694 let mut code = None;
695 let mut suggestion_kind = None;
696
697 let mut message = None;
698
699 list.parse_args_with(|input: ParseStream<'_>| {
700 let mut is_first = true;
701 while !input.is_empty() {
702 if input.peek(LitStr) {
704 let inline_message = input.parse::<LitStr>()?;
705 if !inline_message.suffix().is_empty() {
706 span_err(
707 inline_message.span().unwrap(),
708 "Inline message is not allowed to have a suffix",
709 ).emit();
710 }
711 if !input.is_empty() { input.parse::<Token![,]>()?; }
712 if is_first {
713 message = Some(Message::new(
714 attr.span(),
715 inline_message.span(),
716 inline_message.value(),
717 fields,
718 used_fields,
719 ));
720 is_first = false;
721 } else {
722 span_err(inline_message.span().unwrap(), "a diagnostic message must be the first argument to the attribute").emit();
723 }
724 continue
725 }
726 is_first = false;
727
728 let arg_name: Path = input.parse::<Path>()?;
730 let arg_name_span = arg_name.span().unwrap();
731 match (arg_name.require_ident()?.to_string().as_str(), &mut kind) {
732 ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
733 let code_init = build_suggestion_code(
734 &code_field,
735 &input,
736 fields,
737 AllowMultipleAlternatives::Yes,
738 )?;
739 code.set_once(code_init, arg_name_span);
740 }
741 (
742 "applicability",
743 SubdiagnosticKind::Suggestion { applicability, .. }
744 | SubdiagnosticKind::MultipartSuggestion { applicability, .. },
745 ) => {
746 input.parse::<Token![=]>()?;
747 let value = input.parse::<LitStr>()?;
748 let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| {
749 span_err(value.span().unwrap(), "invalid applicability").emit();
750 Applicability::Unspecified
751 });
752 applicability.set_once(value, span);
753 }
754 (
755 "style",
756 SubdiagnosticKind::Suggestion { .. }
757 | SubdiagnosticKind::MultipartSuggestion { .. },
758 ) => {
759 input.parse::<Token![=]>()?;
760 let value = input.parse::<LitStr>()?;
761
762 let value = value.value().parse().unwrap_or_else(|()| {
763 span_err(value.span().unwrap(), "invalid suggestion style")
764 .help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`")
765 .emit();
766 SuggestionKind::Normal
767 });
768
769 suggestion_kind.set_once(value, span);
770 }
771
772
773 (_, SubdiagnosticKind::Suggestion { .. }) => {
775 span_err(arg_name_span, "invalid nested attribute")
776 .help(
777 "only `style`, `code` and `applicability` are valid nested attributes",
778 )
779 .emit();
780 let _ = input.parse::<TokenStream>();
782 }
783 (_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
784 span_err(arg_name_span, "invalid nested attribute")
785 .help("only `style` and `applicability` are valid nested attributes")
786 .emit();
787 let _ = input.parse::<TokenStream>();
789 }
790 _ => {
791 span_err(arg_name_span, "no nested attribute expected here").emit();
792 let _ = input.parse::<TokenStream>();
794 }
795 }
796
797 if input.is_empty() { break }
798 input.parse::<Token![,]>()?;
799 }
800 Ok(())
801 })?;
802
803 match kind {
804 SubdiagnosticKind::Suggestion {
805 ref code_field,
806 ref mut code_init,
807 suggestion_kind: ref mut kind_field,
808 ..
809 } => {
810 if let Some(kind) = suggestion_kind.value() {
811 *kind_field = kind;
812 }
813
814 *code_init = if let Some(init) = code.value() {
815 init
816 } else {
817 span_err(span, "suggestion without `code = \"...\"`").emit();
818 quote! { let #code_field = std::iter::empty(); }
819 };
820 }
821 SubdiagnosticKind::MultipartSuggestion {
822 suggestion_kind: ref mut kind_field, ..
823 } => {
824 if let Some(kind) = suggestion_kind.value() {
825 *kind_field = kind;
826 }
827 }
828 SubdiagnosticKind::Label
829 | SubdiagnosticKind::Note
830 | SubdiagnosticKind::NoteOnce
831 | SubdiagnosticKind::Help
832 | SubdiagnosticKind::HelpOnce
833 | SubdiagnosticKind::Warn => {}
834 }
835
836 Ok(Some(SubdiagnosticVariant { kind, message }))
837 }
838}
839
840impl quote::IdentFragment for SubdiagnosticKind {
841 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
842 match self {
843 SubdiagnosticKind::Label => write!(f, "label"),
844 SubdiagnosticKind::Note => write!(f, "note"),
845 SubdiagnosticKind::NoteOnce => write!(f, "note_once"),
846 SubdiagnosticKind::Help => write!(f, "help"),
847 SubdiagnosticKind::HelpOnce => write!(f, "help_once"),
848 SubdiagnosticKind::Warn => write!(f, "warn"),
849 SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestions_with_style"),
850 SubdiagnosticKind::MultipartSuggestion { .. } => {
851 write!(f, "multipart_suggestion_with_style")
852 }
853 }
854 }
855
856 fn span(&self) -> Option<proc_macro2::Span> {
857 None
858 }
859}
860
861pub(super) fn should_generate_arg(field: &Field) -> bool {
864 field.attrs.iter().all(|attr| is_doc_comment(attr))
866}
867
868pub(super) fn is_doc_comment(attr: &Attribute) -> bool {
869 attr.path().segments.last().unwrap().ident == "doc"
870}