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