1 use crate::diagnostics::error::{
2 span_err, throw_invalid_attr, throw_invalid_nested_attr, throw_span_err, DiagnosticDeriveError,
5 use proc_macro2::{Ident, TokenStream};
6 use quote::{format_ident, quote, ToTokens};
7 use std::cell::RefCell;
8 use std::collections::{BTreeSet, HashMap};
10 use std::str::FromStr;
11 use syn::{spanned::Spanned, Attribute, Field, Meta, Type, TypeTuple};
12 use syn::{MetaList, MetaNameValue, NestedMeta, Path};
13 use synstructure::{BindingInfo, VariantInfo};
15 use super::error::{invalid_attr, invalid_nested_attr};
18 pub static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0);
21 /// Returns an ident of the form `__code_N` where `N` is incremented once with every call.
22 pub(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;
30 /// Checks whether the type name of `ty` matches `name`.
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.
34 pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool {
35 if let Type::Path(ty) = ty {
39 .map(|s| s.ident.to_string())
41 .zip(name.iter().rev())
42 .all(|(x, y)| &x.as_str() == y)
48 /// Checks whether the type `ty` is `()`.
49 pub(crate) fn type_is_unit(ty: &Type) -> bool {
50 if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false }
53 /// Reports a type error for field with `attr`.
54 pub(crate) fn report_type_error(
57 ) -> Result<!, DiagnosticDeriveError> {
58 let name = attr.path.segments.last().unwrap().ident.to_string();
59 let meta = attr.parse_meta()?;
64 "the `#[{}{}]` attribute can only be applied to fields of type {}",
68 Meta::NameValue(_) => " = ...",
69 Meta::List(_) => "(...)",
76 /// Reports an error if the field's type does not match `path`.
77 fn report_error_if_not_applied_to_ty(
82 ) -> Result<(), DiagnosticDeriveError> {
83 if !type_matches_path(info.ty, path) {
84 report_type_error(attr, ty_name)?;
90 /// Reports an error if the field's type is not `Applicability`.
91 pub(crate) fn report_error_if_not_applied_to_applicability(
94 ) -> Result<(), DiagnosticDeriveError> {
95 report_error_if_not_applied_to_ty(
98 &["rustc_errors", "Applicability"],
103 /// Reports an error if the field's type is not `Span`.
104 pub(crate) fn report_error_if_not_applied_to_span(
106 info: &FieldInfo<'_>,
107 ) -> Result<(), DiagnosticDeriveError> {
108 if !type_matches_path(info.ty, &["rustc_span", "Span"])
109 && !type_matches_path(info.ty, &["rustc_errors", "MultiSpan"])
111 report_type_error(attr, "`Span` or `MultiSpan`")?;
117 /// Inner type of a field and type of wrapper.
118 pub(crate) enum FieldInnerTy<'ty> {
119 /// Field is wrapped in a `Option<$inner>`.
121 /// Field is wrapped in a `Vec<$inner>`.
123 /// Field isn't wrapped in an outer type.
127 impl<'ty> FieldInnerTy<'ty> {
128 /// Returns inner type for a field, if there is one.
130 /// - If `ty` is an `Option`, returns `FieldInnerTy::Option { inner: (inner type) }`.
131 /// - If `ty` is a `Vec`, returns `FieldInnerTy::Vec { inner: (inner type) }`.
132 /// - Otherwise returns `None`.
133 pub(crate) fn from_type(ty: &'ty Type) -> Self {
134 let variant: &dyn Fn(&'ty Type) -> FieldInnerTy<'ty> =
135 if type_matches_path(ty, &["std", "option", "Option"]) {
136 &FieldInnerTy::Option
137 } else if type_matches_path(ty, &["std", "vec", "Vec"]) {
140 return FieldInnerTy::None;
143 if let Type::Path(ty_path) = ty {
144 let path = &ty_path.path;
145 let ty = path.segments.iter().last().unwrap();
146 if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments {
147 if bracketed.args.len() == 1 {
148 if let syn::GenericArgument::Type(ty) = &bracketed.args[0] {
158 /// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e.
159 /// that cloning might be required for values moved in the loop body).
160 pub(crate) fn will_iterate(&self) -> bool {
162 FieldInnerTy::Vec(..) => true,
163 FieldInnerTy::Option(..) | FieldInnerTy::None => false,
167 /// Returns `Option` containing inner type if there is one.
168 pub(crate) fn inner_type(&self) -> Option<&'ty Type> {
170 FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) => Some(inner),
171 FieldInnerTy::None => None,
175 /// Surrounds `inner` with destructured wrapper type, exposing inner type as `binding`.
176 pub(crate) fn with(&self, binding: impl ToTokens, inner: impl ToTokens) -> TokenStream {
178 FieldInnerTy::Option(..) => quote! {
179 if let Some(#binding) = #binding {
183 FieldInnerTy::Vec(..) => quote! {
184 for #binding in #binding {
188 FieldInnerTy::None => quote! { #inner },
193 /// Field information passed to the builder. Deliberately omits attrs to discourage the
194 /// `generate_*` methods from walking the attributes themselves.
195 pub(crate) struct FieldInfo<'a> {
196 pub(crate) binding: &'a BindingInfo<'a>,
197 pub(crate) ty: &'a Type,
198 pub(crate) span: &'a proc_macro2::Span,
201 /// Small helper trait for abstracting over `Option` fields that contain a value and a `Span`
202 /// for error reporting if they are set more than once.
203 pub(crate) trait SetOnce<T> {
204 fn set_once(&mut self, value: T, span: Span);
206 fn value(self) -> Option<T>;
207 fn value_ref(&self) -> Option<&T>;
210 /// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`].
211 pub(super) type SpannedOption<T> = Option<(T, Span)>;
213 impl<T> SetOnce<T> for SpannedOption<T> {
214 fn set_once(&mut self, value: T, span: Span) {
217 *self = Some((value, span));
219 Some((_, prev_span)) => {
220 span_err(span, "specified multiple times")
221 .span_note(*prev_span, "previously specified here")
227 fn value(self) -> Option<T> {
231 fn value_ref(&self) -> Option<&T> {
232 self.as_ref().map(|(v, _)| v)
236 pub(super) type FieldMap = HashMap<String, TokenStream>;
238 pub(crate) trait HasFieldMap {
239 /// Returns the binding for the field with the given name, if it exists on the type.
240 fn get_field_binding(&self, field: &String) -> Option<&TokenStream>;
242 /// In the strings in the attributes supplied to this macro, we want callers to be able to
243 /// reference fields in the format string. For example:
245 /// ```ignore (not-usage-example)
246 /// /// Suggest `==` when users wrote `===`.
247 /// #[suggestion(slug = "parser-not-javascript-eq", code = "{lhs} == {rhs}")]
248 /// struct NotJavaScriptEq {
256 /// We want to automatically pick up that `{lhs}` refers `self.lhs` and `{rhs}` refers to
257 /// `self.rhs`, then generate this call to `format!`:
259 /// ```ignore (not-usage-example)
260 /// format!("{lhs} == {rhs}", lhs = self.lhs, rhs = self.rhs)
263 /// This function builds the entire call to `format!`.
264 fn build_format(&self, input: &str, span: proc_macro2::Span) -> TokenStream {
265 // This set is used later to generate the final format string. To keep builds reproducible,
266 // the iteration order needs to be deterministic, hence why we use a `BTreeSet` here
267 // instead of a `HashSet`.
268 let mut referenced_fields: BTreeSet<String> = BTreeSet::new();
270 // At this point, we can start parsing the format string.
271 let mut it = input.chars().peekable();
273 // Once the start of a format string has been found, process the format string and spit out
274 // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so
275 // the next call to `it.next()` retrieves the next character.
276 while let Some(c) = it.next() {
280 if *it.peek().unwrap_or(&'\0') == '{' {
281 assert_eq!(it.next().unwrap(), '{');
284 let mut eat_argument = || -> Option<String> {
285 let mut result = String::new();
286 // Format specifiers look like:
288 // format := '{' [ argument ] [ ':' format_spec ] '}' .
290 // Therefore, we only need to eat until ':' or '}' to find the argument.
291 while let Some(c) = it.next() {
293 let next = *it.peek().unwrap_or(&'\0');
296 } else if next == ':' {
297 // Eat the ':' character.
298 assert_eq!(it.next().unwrap(), ':');
302 // Eat until (and including) the matching '}'
303 while it.next()? != '}' {
309 if let Some(referenced_field) = eat_argument() {
310 referenced_fields.insert(referenced_field);
314 // At this point, `referenced_fields` contains a set of the unique fields that were
315 // referenced in the format string. Generate the corresponding "x = self.x" format
316 // string parameters:
317 let args = referenced_fields.into_iter().map(|field: String| {
318 let field_ident = format_ident!("{}", field);
319 let value = match self.get_field_binding(&field) {
320 Some(value) => value.clone(),
321 // This field doesn't exist. Emit a diagnostic.
325 &format!("`{field}` doesn't refer to a field on this type"),
334 #field_ident = #value
338 format!(#input #(,#args)*)
343 /// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent
344 /// the user's selection of applicability if specified in an attribute.
345 #[derive(Clone, Copy)]
346 pub(crate) enum Applicability {
353 impl FromStr for Applicability {
356 fn from_str(s: &str) -> Result<Self, Self::Err> {
358 "machine-applicable" => Ok(Applicability::MachineApplicable),
359 "maybe-incorrect" => Ok(Applicability::MaybeIncorrect),
360 "has-placeholders" => Ok(Applicability::HasPlaceholders),
361 "unspecified" => Ok(Applicability::Unspecified),
367 impl quote::ToTokens for Applicability {
368 fn to_tokens(&self, tokens: &mut TokenStream) {
369 tokens.extend(match self {
370 Applicability::MachineApplicable => {
371 quote! { rustc_errors::Applicability::MachineApplicable }
373 Applicability::MaybeIncorrect => {
374 quote! { rustc_errors::Applicability::MaybeIncorrect }
376 Applicability::HasPlaceholders => {
377 quote! { rustc_errors::Applicability::HasPlaceholders }
379 Applicability::Unspecified => {
380 quote! { rustc_errors::Applicability::Unspecified }
386 /// Build the mapping of field names to fields. This allows attributes to peek values from
388 pub(super) fn build_field_mapping(variant: &VariantInfo<'_>) -> HashMap<String, TokenStream> {
389 let mut fields_map = FieldMap::new();
390 for binding in variant.bindings() {
391 if let Some(ident) = &binding.ast().ident {
392 fields_map.insert(ident.to_string(), quote! { #binding });
398 #[derive(Copy, Clone, Debug)]
399 pub(super) enum AllowMultipleAlternatives {
404 /// Constructs the `format!()` invocation(s) necessary for a `#[suggestion*(code = "foo")]` or
405 /// `#[suggestion*(code("foo", "bar"))]` attribute field
406 pub(super) fn build_suggestion_code(
409 fields: &impl HasFieldMap,
410 allow_multiple: AllowMultipleAlternatives,
412 let values = match meta {
414 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(s), .. }) => vec![s],
415 // `code("foo", "bar")`
416 Meta::List(MetaList { nested, .. }) => {
417 if let AllowMultipleAlternatives::No = allow_multiple {
419 meta.span().unwrap(),
420 "expected exactly one string literal for `code = ...`",
424 } else if nested.is_empty() {
426 meta.span().unwrap(),
427 "expected at least one string literal for `code(...)`",
435 if let NestedMeta::Lit(syn::Lit::Str(s)) = item {
439 item.span().unwrap(),
440 "`code(...)` must contain only string literals",
451 meta.span().unwrap(),
452 r#"`code = "..."`/`code(...)` must contain only string literals"#,
459 if let AllowMultipleAlternatives::Yes = allow_multiple {
460 let formatted_strings: Vec<_> = values
462 .map(|value| fields.build_format(&value.value(), value.span()))
464 quote! { let #code_field = [#(#formatted_strings),*].into_iter(); }
465 } else if let [value] = values.as_slice() {
466 let formatted_str = fields.build_format(&value.value(), value.span());
467 quote! { let #code_field = #formatted_str; }
469 // error handled previously
470 quote! { let #code_field = String::new(); }
474 /// Possible styles for suggestion subdiagnostics.
475 #[derive(Clone, Copy, PartialEq)]
476 pub(super) enum SuggestionKind {
484 impl FromStr for SuggestionKind {
487 fn from_str(s: &str) -> Result<Self, Self::Err> {
489 "normal" => Ok(SuggestionKind::Normal),
490 "short" => Ok(SuggestionKind::Short),
491 "hidden" => Ok(SuggestionKind::Hidden),
492 "verbose" => Ok(SuggestionKind::Verbose),
493 "tool-only" => Ok(SuggestionKind::ToolOnly),
499 impl fmt::Display for SuggestionKind {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
502 SuggestionKind::Normal => write!(f, "normal"),
503 SuggestionKind::Short => write!(f, "short"),
504 SuggestionKind::Hidden => write!(f, "hidden"),
505 SuggestionKind::Verbose => write!(f, "verbose"),
506 SuggestionKind::ToolOnly => write!(f, "tool-only"),
511 impl SuggestionKind {
512 pub fn to_suggestion_style(&self) -> TokenStream {
514 SuggestionKind::Normal => {
515 quote! { rustc_errors::SuggestionStyle::ShowCode }
517 SuggestionKind::Short => {
518 quote! { rustc_errors::SuggestionStyle::HideCodeInline }
520 SuggestionKind::Hidden => {
521 quote! { rustc_errors::SuggestionStyle::HideCodeAlways }
523 SuggestionKind::Verbose => {
524 quote! { rustc_errors::SuggestionStyle::ShowAlways }
526 SuggestionKind::ToolOnly => {
527 quote! { rustc_errors::SuggestionStyle::CompletelyHidden }
532 fn from_suffix(s: &str) -> Option<Self> {
534 "" => Some(SuggestionKind::Normal),
535 "_short" => Some(SuggestionKind::Short),
536 "_hidden" => Some(SuggestionKind::Hidden),
537 "_verbose" => Some(SuggestionKind::Verbose),
543 /// Types of subdiagnostics that can be created using attributes
545 pub(super) enum SubdiagnosticKind {
552 /// `#[warning(...)]`
554 /// `#[suggestion{,_short,_hidden,_verbose}]`
556 suggestion_kind: SuggestionKind,
557 applicability: SpannedOption<Applicability>,
558 /// Identifier for variable used for formatted code, e.g. `___code_0`. Enables separation
559 /// of formatting and diagnostic emission so that `set_arg` calls can happen in-between..
560 code_field: syn::Ident,
561 /// Initialization logic for `code_field`'s variable, e.g.
562 /// `let __formatted_code = /* whatever */;`
563 code_init: TokenStream,
565 /// `#[multipart_suggestion{,_short,_hidden,_verbose}]`
566 MultipartSuggestion {
567 suggestion_kind: SuggestionKind,
568 applicability: SpannedOption<Applicability>,
572 impl SubdiagnosticKind {
573 /// Constructs a `SubdiagnosticKind` from a field or type attribute such as `#[note]`,
574 /// `#[error(parser::add_paren)]` or `#[suggestion(code = "...")]`. Returns the
575 /// `SubdiagnosticKind` and the diagnostic slug, if specified.
576 pub(super) fn from_attr(
578 fields: &impl HasFieldMap,
579 ) -> Result<Option<(SubdiagnosticKind, Option<Path>)>, DiagnosticDeriveError> {
580 // Always allow documentation comments.
581 if is_doc_comment(attr) {
585 let span = attr.span().unwrap();
587 let name = attr.path.segments.last().unwrap().ident.to_string();
588 let name = name.as_str();
590 let meta = attr.parse_meta()?;
592 let mut kind = match name {
593 "label" => SubdiagnosticKind::Label,
594 "note" => SubdiagnosticKind::Note,
595 "help" => SubdiagnosticKind::Help,
596 "warning" => SubdiagnosticKind::Warn,
598 // Recover old `#[(multipart_)suggestion_*]` syntaxes
599 // FIXME(#100717): remove
600 if let Some(suggestion_kind) =
601 name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix)
603 if suggestion_kind != SuggestionKind::Normal {
604 invalid_attr(attr, &meta)
606 r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"#
611 SubdiagnosticKind::Suggestion {
612 suggestion_kind: SuggestionKind::Normal,
614 code_field: new_code_ident(),
615 code_init: TokenStream::new(),
617 } else if let Some(suggestion_kind) =
618 name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix)
620 if suggestion_kind != SuggestionKind::Normal {
621 invalid_attr(attr, &meta)
623 r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"#
628 SubdiagnosticKind::MultipartSuggestion {
629 suggestion_kind: SuggestionKind::Normal,
633 throw_invalid_attr!(attr, &meta);
638 let nested = match meta {
639 Meta::List(MetaList { ref nested, .. }) => {
640 // An attribute with properties, such as `#[suggestion(code = "...")]` or
641 // `#[error(some::slug)]`
645 // An attribute without a slug or other properties, such as `#[note]` - return
646 // without further processing.
648 // Only allow this if there are no mandatory properties, such as `code = "..."` in
649 // `#[suggestion(...)]`
651 SubdiagnosticKind::Label
652 | SubdiagnosticKind::Note
653 | SubdiagnosticKind::Help
654 | SubdiagnosticKind::Warn
655 | SubdiagnosticKind::MultipartSuggestion { .. } => {
656 return Ok(Some((kind, None)));
658 SubdiagnosticKind::Suggestion { .. } => {
659 throw_span_err!(span, "suggestion without `code = \"...\"`")
664 throw_invalid_attr!(attr, &meta)
669 let mut suggestion_kind = None;
671 let mut nested_iter = nested.into_iter().peekable();
673 // Peek at the first nested attribute: if it's a slug path, consume it.
674 let slug = if let Some(NestedMeta::Meta(Meta::Path(path))) = nested_iter.peek() {
675 let path = path.clone();
676 // Advance the iterator.
683 for nested_attr in nested_iter {
684 let meta = match nested_attr {
685 NestedMeta::Meta(ref meta) => meta,
686 NestedMeta::Lit(_) => {
687 invalid_nested_attr(attr, nested_attr).emit();
692 let span = meta.span().unwrap();
693 let nested_name = meta.path().segments.last().unwrap().ident.to_string();
694 let nested_name = nested_name.as_str();
696 let string_value = match meta {
697 Meta::NameValue(MetaNameValue { lit: syn::Lit::Str(value), .. }) => Some(value),
699 Meta::Path(_) => throw_invalid_nested_attr!(attr, nested_attr, |diag| {
700 diag.help("a diagnostic slug must be the first argument to the attribute")
705 match (nested_name, &mut kind) {
706 ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => {
707 let code_init = build_suggestion_code(
711 AllowMultipleAlternatives::Yes,
713 code.set_once(code_init, span);
717 SubdiagnosticKind::Suggestion { ref mut applicability, .. }
718 | SubdiagnosticKind::MultipartSuggestion { ref mut applicability, .. },
720 let Some(value) = string_value else {
721 invalid_nested_attr(attr, nested_attr).emit();
725 let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| {
726 span_err(span, "invalid applicability").emit();
727 Applicability::Unspecified
729 applicability.set_once(value, span);
733 SubdiagnosticKind::Suggestion { .. }
734 | SubdiagnosticKind::MultipartSuggestion { .. },
736 let Some(value) = string_value else {
737 invalid_nested_attr(attr, nested_attr).emit();
741 let value = value.value().parse().unwrap_or_else(|()| {
742 span_err(value.span().unwrap(), "invalid suggestion style")
743 .help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`")
745 SuggestionKind::Normal
748 suggestion_kind.set_once(value, span);
751 // Invalid nested attribute
752 (_, SubdiagnosticKind::Suggestion { .. }) => {
753 invalid_nested_attr(attr, nested_attr)
755 "only `style`, `code` and `applicability` are valid nested attributes",
759 (_, SubdiagnosticKind::MultipartSuggestion { .. }) => {
760 invalid_nested_attr(attr, nested_attr)
761 .help("only `style` and `applicability` are valid nested attributes")
765 invalid_nested_attr(attr, nested_attr).emit();
771 SubdiagnosticKind::Suggestion {
774 suggestion_kind: ref mut kind_field,
777 if let Some(kind) = suggestion_kind.value() {
781 *code_init = if let Some(init) = code.value() {
784 span_err(span, "suggestion without `code = \"...\"`").emit();
785 quote! { let #code_field = std::iter::empty(); }
788 SubdiagnosticKind::MultipartSuggestion {
789 suggestion_kind: ref mut kind_field, ..
791 if let Some(kind) = suggestion_kind.value() {
795 SubdiagnosticKind::Label
796 | SubdiagnosticKind::Note
797 | SubdiagnosticKind::Help
798 | SubdiagnosticKind::Warn => {}
801 Ok(Some((kind, slug)))
805 impl quote::IdentFragment for SubdiagnosticKind {
806 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
808 SubdiagnosticKind::Label => write!(f, "label"),
809 SubdiagnosticKind::Note => write!(f, "note"),
810 SubdiagnosticKind::Help => write!(f, "help"),
811 SubdiagnosticKind::Warn => write!(f, "warn"),
812 SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestions_with_style"),
813 SubdiagnosticKind::MultipartSuggestion { .. } => {
814 write!(f, "multipart_suggestion_with_style")
819 fn span(&self) -> Option<proc_macro2::Span> {
824 /// Returns `true` if `field` should generate a `set_arg` call rather than any other diagnostic
825 /// call (like `span_label`).
826 pub(super) fn should_generate_set_arg(field: &Field) -> bool {
827 field.attrs.is_empty()
830 pub(super) fn is_doc_comment(attr: &Attribute) -> bool {
831 attr.path.segments.last().unwrap().ident == "doc"