|  | use std::cell::RefCell; | 
|  | use std::collections::{BTreeSet, HashMap}; | 
|  | use std::fmt; | 
|  | use std::str::FromStr; | 
|  |  | 
|  | use proc_macro::Span; | 
|  | use proc_macro2::{Ident, TokenStream}; | 
|  | use quote::{ToTokens, format_ident, quote}; | 
|  | use syn::meta::ParseNestedMeta; | 
|  | use syn::punctuated::Punctuated; | 
|  | use syn::spanned::Spanned; | 
|  | use syn::{Attribute, Field, LitStr, Meta, Path, Token, Type, TypeTuple, parenthesized}; | 
|  | use synstructure::{BindingInfo, VariantInfo}; | 
|  |  | 
|  | use super::error::invalid_attr; | 
|  | use crate::diagnostics::error::{ | 
|  | DiagnosticDeriveError, span_err, throw_invalid_attr, throw_span_err, | 
|  | }; | 
|  |  | 
|  | thread_local! { | 
|  | pub(crate) static CODE_IDENT_COUNT: RefCell<u32> = RefCell::new(0); | 
|  | } | 
|  |  | 
|  | /// Returns an ident of the form `__code_N` where `N` is incremented once with every call. | 
|  | pub(crate) fn new_code_ident() -> syn::Ident { | 
|  | CODE_IDENT_COUNT.with(|count| { | 
|  | let ident = format_ident!("__code_{}", *count.borrow()); | 
|  | *count.borrow_mut() += 1; | 
|  | ident | 
|  | }) | 
|  | } | 
|  |  | 
|  | /// Checks whether the type name of `ty` matches `name`. | 
|  | /// | 
|  | /// Given some struct at `a::b::c::Foo`, this will return true for `c::Foo`, `b::c::Foo`, or | 
|  | /// `a::b::c::Foo`. This reasonably allows qualified names to be used in the macro. | 
|  | pub(crate) fn type_matches_path(ty: &Type, name: &[&str]) -> bool { | 
|  | if let Type::Path(ty) = ty { | 
|  | ty.path | 
|  | .segments | 
|  | .iter() | 
|  | .map(|s| s.ident.to_string()) | 
|  | .rev() | 
|  | .zip(name.iter().rev()) | 
|  | .all(|(x, y)| &x.as_str() == y) | 
|  | } else { | 
|  | false | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Checks whether the type `ty` is `()`. | 
|  | pub(crate) fn type_is_unit(ty: &Type) -> bool { | 
|  | if let Type::Tuple(TypeTuple { elems, .. }) = ty { elems.is_empty() } else { false } | 
|  | } | 
|  |  | 
|  | /// Checks whether the type `ty` is `bool`. | 
|  | pub(crate) fn type_is_bool(ty: &Type) -> bool { | 
|  | type_matches_path(ty, &["bool"]) | 
|  | } | 
|  |  | 
|  | /// Reports a type error for field with `attr`. | 
|  | pub(crate) fn report_type_error( | 
|  | attr: &Attribute, | 
|  | ty_name: &str, | 
|  | ) -> Result<!, DiagnosticDeriveError> { | 
|  | let name = attr.path().segments.last().unwrap().ident.to_string(); | 
|  | let meta = &attr.meta; | 
|  |  | 
|  | throw_span_err!( | 
|  | attr.span().unwrap(), | 
|  | &format!( | 
|  | "the `#[{}{}]` attribute can only be applied to fields of type {}", | 
|  | name, | 
|  | match meta { | 
|  | Meta::Path(_) => "", | 
|  | Meta::NameValue(_) => " = ...", | 
|  | Meta::List(_) => "(...)", | 
|  | }, | 
|  | ty_name | 
|  | ) | 
|  | ); | 
|  | } | 
|  |  | 
|  | /// Reports an error if the field's type does not match `path`. | 
|  | fn report_error_if_not_applied_to_ty( | 
|  | attr: &Attribute, | 
|  | info: &FieldInfo<'_>, | 
|  | path: &[&str], | 
|  | ty_name: &str, | 
|  | ) -> Result<(), DiagnosticDeriveError> { | 
|  | if !type_matches_path(info.ty.inner_type(), path) { | 
|  | report_type_error(attr, ty_name)?; | 
|  | } | 
|  |  | 
|  | Ok(()) | 
|  | } | 
|  |  | 
|  | /// Reports an error if the field's type is not `Applicability`. | 
|  | pub(crate) fn report_error_if_not_applied_to_applicability( | 
|  | attr: &Attribute, | 
|  | info: &FieldInfo<'_>, | 
|  | ) -> Result<(), DiagnosticDeriveError> { | 
|  | report_error_if_not_applied_to_ty( | 
|  | attr, | 
|  | info, | 
|  | &["rustc_errors", "Applicability"], | 
|  | "`Applicability`", | 
|  | ) | 
|  | } | 
|  |  | 
|  | /// Reports an error if the field's type is not `Span`. | 
|  | pub(crate) fn report_error_if_not_applied_to_span( | 
|  | attr: &Attribute, | 
|  | info: &FieldInfo<'_>, | 
|  | ) -> Result<(), DiagnosticDeriveError> { | 
|  | if !type_matches_path(info.ty.inner_type(), &["rustc_span", "Span"]) | 
|  | && !type_matches_path(info.ty.inner_type(), &["rustc_errors", "MultiSpan"]) | 
|  | { | 
|  | report_type_error(attr, "`Span` or `MultiSpan`")?; | 
|  | } | 
|  |  | 
|  | Ok(()) | 
|  | } | 
|  |  | 
|  | /// Inner type of a field and type of wrapper. | 
|  | #[derive(Copy, Clone)] | 
|  | pub(crate) enum FieldInnerTy<'ty> { | 
|  | /// Field is wrapped in a `Option<$inner>`. | 
|  | Option(&'ty Type), | 
|  | /// Field is wrapped in a `Vec<$inner>`. | 
|  | Vec(&'ty Type), | 
|  | /// Field isn't wrapped in an outer type. | 
|  | Plain(&'ty Type), | 
|  | } | 
|  |  | 
|  | impl<'ty> FieldInnerTy<'ty> { | 
|  | /// Returns inner type for a field, if there is one. | 
|  | /// | 
|  | /// - If `ty` is an `Option<Inner>`, returns `FieldInnerTy::Option(Inner)`. | 
|  | /// - If `ty` is a `Vec<Inner>`, returns `FieldInnerTy::Vec(Inner)`. | 
|  | /// - Otherwise returns `FieldInnerTy::Plain(ty)`. | 
|  | pub(crate) fn from_type(ty: &'ty Type) -> Self { | 
|  | fn single_generic_type(ty: &Type) -> &Type { | 
|  | let Type::Path(ty_path) = ty else { | 
|  | panic!("expected path type"); | 
|  | }; | 
|  |  | 
|  | let path = &ty_path.path; | 
|  | let ty = path.segments.last().unwrap(); | 
|  | let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments else { | 
|  | panic!("expected bracketed generic arguments"); | 
|  | }; | 
|  |  | 
|  | assert_eq!(bracketed.args.len(), 1); | 
|  |  | 
|  | let syn::GenericArgument::Type(ty) = &bracketed.args[0] else { | 
|  | panic!("expected generic parameter to be a type generic"); | 
|  | }; | 
|  |  | 
|  | ty | 
|  | } | 
|  |  | 
|  | if type_matches_path(ty, &["std", "option", "Option"]) { | 
|  | FieldInnerTy::Option(single_generic_type(ty)) | 
|  | } else if type_matches_path(ty, &["std", "vec", "Vec"]) { | 
|  | FieldInnerTy::Vec(single_generic_type(ty)) | 
|  | } else { | 
|  | FieldInnerTy::Plain(ty) | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Returns `true` if `FieldInnerTy::with` will result in iteration for this inner type (i.e. | 
|  | /// that cloning might be required for values moved in the loop body). | 
|  | pub(crate) fn will_iterate(&self) -> bool { | 
|  | match self { | 
|  | FieldInnerTy::Vec(..) => true, | 
|  | FieldInnerTy::Option(..) | FieldInnerTy::Plain(_) => false, | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Returns the inner type. | 
|  | pub(crate) fn inner_type(&self) -> &'ty Type { | 
|  | match self { | 
|  | FieldInnerTy::Option(inner) | FieldInnerTy::Vec(inner) | FieldInnerTy::Plain(inner) => { | 
|  | inner | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Surrounds `inner` with destructured wrapper type, exposing inner type as `binding`. | 
|  | pub(crate) fn with(&self, binding: impl ToTokens, inner: impl ToTokens) -> TokenStream { | 
|  | match self { | 
|  | FieldInnerTy::Option(..) => quote! { | 
|  | if let Some(#binding) = #binding { | 
|  | #inner | 
|  | } | 
|  | }, | 
|  | FieldInnerTy::Vec(..) => quote! { | 
|  | for #binding in #binding { | 
|  | #inner | 
|  | } | 
|  | }, | 
|  | FieldInnerTy::Plain(t) if type_is_bool(t) => quote! { | 
|  | if #binding { | 
|  | #inner | 
|  | } | 
|  | }, | 
|  | FieldInnerTy::Plain(..) => quote! { #inner }, | 
|  | } | 
|  | } | 
|  |  | 
|  | pub(crate) fn span(&self) -> proc_macro2::Span { | 
|  | match self { | 
|  | FieldInnerTy::Option(ty) | FieldInnerTy::Vec(ty) | FieldInnerTy::Plain(ty) => ty.span(), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Field information passed to the builder. Deliberately omits attrs to discourage the | 
|  | /// `generate_*` methods from walking the attributes themselves. | 
|  | pub(crate) struct FieldInfo<'a> { | 
|  | pub(crate) binding: &'a BindingInfo<'a>, | 
|  | pub(crate) ty: FieldInnerTy<'a>, | 
|  | pub(crate) span: &'a proc_macro2::Span, | 
|  | } | 
|  |  | 
|  | /// Small helper trait for abstracting over `Option` fields that contain a value and a `Span` | 
|  | /// for error reporting if they are set more than once. | 
|  | pub(crate) trait SetOnce<T> { | 
|  | fn set_once(&mut self, value: T, span: Span); | 
|  |  | 
|  | fn value(self) -> Option<T>; | 
|  | fn value_ref(&self) -> Option<&T>; | 
|  | } | 
|  |  | 
|  | /// An [`Option<T>`] that keeps track of the span that caused it to be set; used with [`SetOnce`]. | 
|  | pub(super) type SpannedOption<T> = Option<(T, Span)>; | 
|  |  | 
|  | impl<T> SetOnce<T> for SpannedOption<T> { | 
|  | fn set_once(&mut self, value: T, span: Span) { | 
|  | match self { | 
|  | None => { | 
|  | *self = Some((value, span)); | 
|  | } | 
|  | Some((_, prev_span)) => { | 
|  | span_err(span, "attribute specified multiple times") | 
|  | .span_note(*prev_span, "previously specified here") | 
|  | .emit(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | fn value(self) -> Option<T> { | 
|  | self.map(|(v, _)| v) | 
|  | } | 
|  |  | 
|  | fn value_ref(&self) -> Option<&T> { | 
|  | self.as_ref().map(|(v, _)| v) | 
|  | } | 
|  | } | 
|  |  | 
|  | pub(super) type FieldMap = HashMap<String, TokenStream>; | 
|  |  | 
|  | pub(crate) trait HasFieldMap { | 
|  | /// Returns the binding for the field with the given name, if it exists on the type. | 
|  | fn get_field_binding(&self, field: &String) -> Option<&TokenStream>; | 
|  |  | 
|  | /// In the strings in the attributes supplied to this macro, we want callers to be able to | 
|  | /// reference fields in the format string. For example: | 
|  | /// | 
|  | /// ```ignore (not-usage-example) | 
|  | /// /// Suggest `==` when users wrote `===`. | 
|  | /// #[suggestion(slug = "parser-not-javascript-eq", code = "{lhs} == {rhs}")] | 
|  | /// struct NotJavaScriptEq { | 
|  | ///     #[primary_span] | 
|  | ///     span: Span, | 
|  | ///     lhs: Ident, | 
|  | ///     rhs: Ident, | 
|  | /// } | 
|  | /// ``` | 
|  | /// | 
|  | /// We want to automatically pick up that `{lhs}` refers `self.lhs` and `{rhs}` refers to | 
|  | /// `self.rhs`, then generate this call to `format!`: | 
|  | /// | 
|  | /// ```ignore (not-usage-example) | 
|  | /// format!("{lhs} == {rhs}", lhs = self.lhs, rhs = self.rhs) | 
|  | /// ``` | 
|  | /// | 
|  | /// This function builds the entire call to `format!`. | 
|  | fn build_format(&self, input: &str, span: proc_macro2::Span) -> TokenStream { | 
|  | // This set is used later to generate the final format string. To keep builds reproducible, | 
|  | // the iteration order needs to be deterministic, hence why we use a `BTreeSet` here | 
|  | // instead of a `HashSet`. | 
|  | let mut referenced_fields: BTreeSet<String> = BTreeSet::new(); | 
|  |  | 
|  | // At this point, we can start parsing the format string. | 
|  | let mut it = input.chars().peekable(); | 
|  |  | 
|  | // Once the start of a format string has been found, process the format string and spit out | 
|  | // the referenced fields. Leaves `it` sitting on the closing brace of the format string, so | 
|  | // the next call to `it.next()` retrieves the next character. | 
|  | while let Some(c) = it.next() { | 
|  | if c != '{' { | 
|  | continue; | 
|  | } | 
|  | if *it.peek().unwrap_or(&'\0') == '{' { | 
|  | assert_eq!(it.next().unwrap(), '{'); | 
|  | continue; | 
|  | } | 
|  | let mut eat_argument = || -> Option<String> { | 
|  | let mut result = String::new(); | 
|  | // Format specifiers look like: | 
|  | // | 
|  | //   format   := '{' [ argument ] [ ':' format_spec ] '}' . | 
|  | // | 
|  | // Therefore, we only need to eat until ':' or '}' to find the argument. | 
|  | while let Some(c) = it.next() { | 
|  | result.push(c); | 
|  | let next = *it.peek().unwrap_or(&'\0'); | 
|  | if next == '}' { | 
|  | break; | 
|  | } else if next == ':' { | 
|  | // Eat the ':' character. | 
|  | assert_eq!(it.next().unwrap(), ':'); | 
|  | break; | 
|  | } | 
|  | } | 
|  | // Eat until (and including) the matching '}' | 
|  | while it.next()? != '}' { | 
|  | continue; | 
|  | } | 
|  | Some(result) | 
|  | }; | 
|  |  | 
|  | if let Some(referenced_field) = eat_argument() { | 
|  | referenced_fields.insert(referenced_field); | 
|  | } | 
|  | } | 
|  |  | 
|  | // At this point, `referenced_fields` contains a set of the unique fields that were | 
|  | // referenced in the format string. Generate the corresponding "x = self.x" format | 
|  | // string parameters: | 
|  | let args = referenced_fields.into_iter().map(|field: String| { | 
|  | let field_ident = format_ident!("{}", field); | 
|  | let value = match self.get_field_binding(&field) { | 
|  | Some(value) => value.clone(), | 
|  | // This field doesn't exist. Emit a diagnostic. | 
|  | None => { | 
|  | span_err( | 
|  | span.unwrap(), | 
|  | format!("`{field}` doesn't refer to a field on this type"), | 
|  | ) | 
|  | .emit(); | 
|  | quote! { | 
|  | "{#field}" | 
|  | } | 
|  | } | 
|  | }; | 
|  | quote! { | 
|  | #field_ident = #value | 
|  | } | 
|  | }); | 
|  | quote! { | 
|  | format!(#input #(,#args)*) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// `Applicability` of a suggestion - mirrors `rustc_errors::Applicability` - and used to represent | 
|  | /// the user's selection of applicability if specified in an attribute. | 
|  | #[derive(Clone, Copy)] | 
|  | pub(crate) enum Applicability { | 
|  | MachineApplicable, | 
|  | MaybeIncorrect, | 
|  | HasPlaceholders, | 
|  | Unspecified, | 
|  | } | 
|  |  | 
|  | impl FromStr for Applicability { | 
|  | type Err = (); | 
|  |  | 
|  | fn from_str(s: &str) -> Result<Self, Self::Err> { | 
|  | match s { | 
|  | "machine-applicable" => Ok(Applicability::MachineApplicable), | 
|  | "maybe-incorrect" => Ok(Applicability::MaybeIncorrect), | 
|  | "has-placeholders" => Ok(Applicability::HasPlaceholders), | 
|  | "unspecified" => Ok(Applicability::Unspecified), | 
|  | _ => Err(()), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | impl quote::ToTokens for Applicability { | 
|  | fn to_tokens(&self, tokens: &mut TokenStream) { | 
|  | tokens.extend(match self { | 
|  | Applicability::MachineApplicable => { | 
|  | quote! { rustc_errors::Applicability::MachineApplicable } | 
|  | } | 
|  | Applicability::MaybeIncorrect => { | 
|  | quote! { rustc_errors::Applicability::MaybeIncorrect } | 
|  | } | 
|  | Applicability::HasPlaceholders => { | 
|  | quote! { rustc_errors::Applicability::HasPlaceholders } | 
|  | } | 
|  | Applicability::Unspecified => { | 
|  | quote! { rustc_errors::Applicability::Unspecified } | 
|  | } | 
|  | }); | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Build the mapping of field names to fields. This allows attributes to peek values from | 
|  | /// other fields. | 
|  | pub(super) fn build_field_mapping(variant: &VariantInfo<'_>) -> HashMap<String, TokenStream> { | 
|  | let mut fields_map = FieldMap::new(); | 
|  | for binding in variant.bindings() { | 
|  | if let Some(ident) = &binding.ast().ident { | 
|  | fields_map.insert(ident.to_string(), quote! { #binding }); | 
|  | } | 
|  | } | 
|  | fields_map | 
|  | } | 
|  |  | 
|  | #[derive(Copy, Clone, Debug)] | 
|  | pub(super) enum AllowMultipleAlternatives { | 
|  | No, | 
|  | Yes, | 
|  | } | 
|  |  | 
|  | fn parse_suggestion_values( | 
|  | nested: ParseNestedMeta<'_>, | 
|  | allow_multiple: AllowMultipleAlternatives, | 
|  | ) -> syn::Result<Vec<LitStr>> { | 
|  | let values = if let Ok(val) = nested.value() { | 
|  | vec![val.parse()?] | 
|  | } else { | 
|  | let content; | 
|  | parenthesized!(content in nested.input); | 
|  |  | 
|  | if let AllowMultipleAlternatives::No = allow_multiple { | 
|  | span_err( | 
|  | nested.input.span().unwrap(), | 
|  | "expected exactly one string literal for `code = ...`", | 
|  | ) | 
|  | .emit(); | 
|  | vec![] | 
|  | } else { | 
|  | let literals = Punctuated::<LitStr, Token![,]>::parse_terminated(&content); | 
|  |  | 
|  | match literals { | 
|  | Ok(p) if p.is_empty() => { | 
|  | span_err( | 
|  | content.span().unwrap(), | 
|  | "expected at least one string literal for `code(...)`", | 
|  | ) | 
|  | .emit(); | 
|  | vec![] | 
|  | } | 
|  | Ok(p) => p.into_iter().collect(), | 
|  | Err(_) => { | 
|  | span_err( | 
|  | content.span().unwrap(), | 
|  | "`code(...)` must contain only string literals", | 
|  | ) | 
|  | .emit(); | 
|  | vec![] | 
|  | } | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | Ok(values) | 
|  | } | 
|  |  | 
|  | /// Constructs the `format!()` invocation(s) necessary for a `#[suggestion*(code = "foo")]` or | 
|  | /// `#[suggestion*(code("foo", "bar"))]` attribute field | 
|  | pub(super) fn build_suggestion_code( | 
|  | code_field: &Ident, | 
|  | nested: ParseNestedMeta<'_>, | 
|  | fields: &impl HasFieldMap, | 
|  | allow_multiple: AllowMultipleAlternatives, | 
|  | ) -> TokenStream { | 
|  | let values = match parse_suggestion_values(nested, allow_multiple) { | 
|  | Ok(x) => x, | 
|  | Err(e) => return e.into_compile_error(), | 
|  | }; | 
|  |  | 
|  | if let AllowMultipleAlternatives::Yes = allow_multiple { | 
|  | let formatted_strings: Vec<_> = values | 
|  | .into_iter() | 
|  | .map(|value| fields.build_format(&value.value(), value.span())) | 
|  | .collect(); | 
|  | quote! { let #code_field = [#(#formatted_strings),*].into_iter(); } | 
|  | } else if let [value] = values.as_slice() { | 
|  | let formatted_str = fields.build_format(&value.value(), value.span()); | 
|  | quote! { let #code_field = #formatted_str; } | 
|  | } else { | 
|  | // error handled previously | 
|  | quote! { let #code_field = String::new(); } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Possible styles for suggestion subdiagnostics. | 
|  | #[derive(Clone, Copy, PartialEq)] | 
|  | pub(super) enum SuggestionKind { | 
|  | Normal, | 
|  | Short, | 
|  | Hidden, | 
|  | Verbose, | 
|  | ToolOnly, | 
|  | } | 
|  |  | 
|  | impl FromStr for SuggestionKind { | 
|  | type Err = (); | 
|  |  | 
|  | fn from_str(s: &str) -> Result<Self, Self::Err> { | 
|  | match s { | 
|  | "normal" => Ok(SuggestionKind::Normal), | 
|  | "short" => Ok(SuggestionKind::Short), | 
|  | "hidden" => Ok(SuggestionKind::Hidden), | 
|  | "verbose" => Ok(SuggestionKind::Verbose), | 
|  | "tool-only" => Ok(SuggestionKind::ToolOnly), | 
|  | _ => Err(()), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | impl fmt::Display for SuggestionKind { | 
|  | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 
|  | match self { | 
|  | SuggestionKind::Normal => write!(f, "normal"), | 
|  | SuggestionKind::Short => write!(f, "short"), | 
|  | SuggestionKind::Hidden => write!(f, "hidden"), | 
|  | SuggestionKind::Verbose => write!(f, "verbose"), | 
|  | SuggestionKind::ToolOnly => write!(f, "tool-only"), | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | impl SuggestionKind { | 
|  | pub(crate) fn to_suggestion_style(&self) -> TokenStream { | 
|  | match self { | 
|  | SuggestionKind::Normal => { | 
|  | quote! { rustc_errors::SuggestionStyle::ShowCode } | 
|  | } | 
|  | SuggestionKind::Short => { | 
|  | quote! { rustc_errors::SuggestionStyle::HideCodeInline } | 
|  | } | 
|  | SuggestionKind::Hidden => { | 
|  | quote! { rustc_errors::SuggestionStyle::HideCodeAlways } | 
|  | } | 
|  | SuggestionKind::Verbose => { | 
|  | quote! { rustc_errors::SuggestionStyle::ShowAlways } | 
|  | } | 
|  | SuggestionKind::ToolOnly => { | 
|  | quote! { rustc_errors::SuggestionStyle::CompletelyHidden } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | fn from_suffix(s: &str) -> Option<Self> { | 
|  | match s { | 
|  | "" => Some(SuggestionKind::Normal), | 
|  | "_short" => Some(SuggestionKind::Short), | 
|  | "_hidden" => Some(SuggestionKind::Hidden), | 
|  | "_verbose" => Some(SuggestionKind::Verbose), | 
|  | _ => None, | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Types of subdiagnostics that can be created using attributes | 
|  | #[derive(Clone)] | 
|  | pub(super) enum SubdiagnosticKind { | 
|  | /// `#[label(...)]` | 
|  | Label, | 
|  | /// `#[note(...)]` | 
|  | Note, | 
|  | /// `#[note_once(...)]` | 
|  | NoteOnce, | 
|  | /// `#[help(...)]` | 
|  | Help, | 
|  | /// `#[help_once(...)]` | 
|  | HelpOnce, | 
|  | /// `#[warning(...)]` | 
|  | Warn, | 
|  | /// `#[suggestion{,_short,_hidden,_verbose}]` | 
|  | Suggestion { | 
|  | suggestion_kind: SuggestionKind, | 
|  | applicability: SpannedOption<Applicability>, | 
|  | /// Identifier for variable used for formatted code, e.g. `___code_0`. Enables separation | 
|  | /// of formatting and diagnostic emission so that `arg` calls can happen in-between.. | 
|  | code_field: syn::Ident, | 
|  | /// Initialization logic for `code_field`'s variable, e.g. | 
|  | /// `let __formatted_code = /* whatever */;` | 
|  | code_init: TokenStream, | 
|  | }, | 
|  | /// `#[multipart_suggestion{,_short,_hidden,_verbose}]` | 
|  | MultipartSuggestion { | 
|  | suggestion_kind: SuggestionKind, | 
|  | applicability: SpannedOption<Applicability>, | 
|  | }, | 
|  | } | 
|  |  | 
|  | pub(super) struct SubdiagnosticVariant { | 
|  | pub(super) kind: SubdiagnosticKind, | 
|  | pub(super) slug: Option<Path>, | 
|  | pub(super) no_span: bool, | 
|  | } | 
|  |  | 
|  | impl SubdiagnosticVariant { | 
|  | /// Constructs a `SubdiagnosticVariant` from a field or type attribute such as `#[note]`, | 
|  | /// `#[error(parser::add_paren, no_span)]` or `#[suggestion(code = "...")]`. Returns the | 
|  | /// `SubdiagnosticKind` and the diagnostic slug, if specified. | 
|  | pub(super) fn from_attr( | 
|  | attr: &Attribute, | 
|  | fields: &impl HasFieldMap, | 
|  | ) -> Result<Option<SubdiagnosticVariant>, DiagnosticDeriveError> { | 
|  | // Always allow documentation comments. | 
|  | if is_doc_comment(attr) { | 
|  | return Ok(None); | 
|  | } | 
|  |  | 
|  | let span = attr.span().unwrap(); | 
|  |  | 
|  | let name = attr.path().segments.last().unwrap().ident.to_string(); | 
|  | let name = name.as_str(); | 
|  |  | 
|  | let mut kind = match name { | 
|  | "label" => SubdiagnosticKind::Label, | 
|  | "note" => SubdiagnosticKind::Note, | 
|  | "note_once" => SubdiagnosticKind::NoteOnce, | 
|  | "help" => SubdiagnosticKind::Help, | 
|  | "help_once" => SubdiagnosticKind::HelpOnce, | 
|  | "warning" => SubdiagnosticKind::Warn, | 
|  | _ => { | 
|  | // Recover old `#[(multipart_)suggestion_*]` syntaxes | 
|  | // FIXME(#100717): remove | 
|  | if let Some(suggestion_kind) = | 
|  | name.strip_prefix("suggestion").and_then(SuggestionKind::from_suffix) | 
|  | { | 
|  | if suggestion_kind != SuggestionKind::Normal { | 
|  | invalid_attr(attr) | 
|  | .help(format!( | 
|  | r#"Use `#[suggestion(..., style = "{suggestion_kind}")]` instead"# | 
|  | )) | 
|  | .emit(); | 
|  | } | 
|  |  | 
|  | SubdiagnosticKind::Suggestion { | 
|  | suggestion_kind: SuggestionKind::Normal, | 
|  | applicability: None, | 
|  | code_field: new_code_ident(), | 
|  | code_init: TokenStream::new(), | 
|  | } | 
|  | } else if let Some(suggestion_kind) = | 
|  | name.strip_prefix("multipart_suggestion").and_then(SuggestionKind::from_suffix) | 
|  | { | 
|  | if suggestion_kind != SuggestionKind::Normal { | 
|  | invalid_attr(attr) | 
|  | .help(format!( | 
|  | r#"Use `#[multipart_suggestion(..., style = "{suggestion_kind}")]` instead"# | 
|  | )) | 
|  | .emit(); | 
|  | } | 
|  |  | 
|  | SubdiagnosticKind::MultipartSuggestion { | 
|  | suggestion_kind: SuggestionKind::Normal, | 
|  | applicability: None, | 
|  | } | 
|  | } else { | 
|  | throw_invalid_attr!(attr); | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | let list = match &attr.meta { | 
|  | Meta::List(list) => { | 
|  | // An attribute with properties, such as `#[suggestion(code = "...")]` or | 
|  | // `#[error(some::slug)]` | 
|  | list | 
|  | } | 
|  | Meta::Path(_) => { | 
|  | // An attribute without a slug or other properties, such as `#[note]` - return | 
|  | // without further processing. | 
|  | // | 
|  | // Only allow this if there are no mandatory properties, such as `code = "..."` in | 
|  | // `#[suggestion(...)]` | 
|  | match kind { | 
|  | SubdiagnosticKind::Label | 
|  | | SubdiagnosticKind::Note | 
|  | | SubdiagnosticKind::NoteOnce | 
|  | | SubdiagnosticKind::Help | 
|  | | SubdiagnosticKind::HelpOnce | 
|  | | SubdiagnosticKind::Warn | 
|  | | SubdiagnosticKind::MultipartSuggestion { .. } => { | 
|  | return Ok(Some(SubdiagnosticVariant { kind, slug: None, no_span: false })); | 
|  | } | 
|  | SubdiagnosticKind::Suggestion { .. } => { | 
|  | throw_span_err!(span, "suggestion without `code = \"...\"`") | 
|  | } | 
|  | } | 
|  | } | 
|  | _ => { | 
|  | throw_invalid_attr!(attr) | 
|  | } | 
|  | }; | 
|  |  | 
|  | let mut code = None; | 
|  | let mut suggestion_kind = None; | 
|  |  | 
|  | let mut first = true; | 
|  | let mut slug = None; | 
|  | let mut no_span = false; | 
|  |  | 
|  | list.parse_nested_meta(|nested| { | 
|  | if nested.input.is_empty() || nested.input.peek(Token![,]) { | 
|  | if first { | 
|  | slug = Some(nested.path); | 
|  | } else if nested.path.is_ident("no_span") { | 
|  | no_span = true; | 
|  | } else { | 
|  | span_err(nested.input.span().unwrap(), "a diagnostic slug must be the first argument to the attribute").emit(); | 
|  | } | 
|  |  | 
|  | first = false; | 
|  | return Ok(()); | 
|  | } | 
|  |  | 
|  | first = false; | 
|  |  | 
|  | let nested_name = nested.path.segments.last().unwrap().ident.to_string(); | 
|  | let nested_name = nested_name.as_str(); | 
|  |  | 
|  | let path_span = nested.path.span().unwrap(); | 
|  | let val_span = nested.input.span().unwrap(); | 
|  |  | 
|  | macro_rules! get_string { | 
|  | () => {{ | 
|  | let Ok(value) = nested.value().and_then(|x| x.parse::<LitStr>()) else { | 
|  | span_err(val_span, "expected `= \"xxx\"`").emit(); | 
|  | return Ok(()); | 
|  | }; | 
|  | value | 
|  | }}; | 
|  | } | 
|  |  | 
|  | let mut has_errors = false; | 
|  | let input = nested.input; | 
|  |  | 
|  | match (nested_name, &mut kind) { | 
|  | ("code", SubdiagnosticKind::Suggestion { code_field, .. }) => { | 
|  | let code_init = build_suggestion_code( | 
|  | code_field, | 
|  | nested, | 
|  | fields, | 
|  | AllowMultipleAlternatives::Yes, | 
|  | ); | 
|  | code.set_once(code_init, path_span); | 
|  | } | 
|  | ( | 
|  | "applicability", | 
|  | SubdiagnosticKind::Suggestion { applicability, .. } | 
|  | | SubdiagnosticKind::MultipartSuggestion { applicability, .. }, | 
|  | ) => { | 
|  | let value = get_string!(); | 
|  | let value = Applicability::from_str(&value.value()).unwrap_or_else(|()| { | 
|  | span_err(value.span().unwrap(), "invalid applicability").emit(); | 
|  | has_errors = true; | 
|  | Applicability::Unspecified | 
|  | }); | 
|  | applicability.set_once(value, span); | 
|  | } | 
|  | ( | 
|  | "style", | 
|  | SubdiagnosticKind::Suggestion { .. } | 
|  | | SubdiagnosticKind::MultipartSuggestion { .. }, | 
|  | ) => { | 
|  | let value = get_string!(); | 
|  |  | 
|  | let value = value.value().parse().unwrap_or_else(|()| { | 
|  | span_err(value.span().unwrap(), "invalid suggestion style") | 
|  | .help("valid styles are `normal`, `short`, `hidden`, `verbose` and `tool-only`") | 
|  | .emit(); | 
|  | has_errors = true; | 
|  | SuggestionKind::Normal | 
|  | }); | 
|  |  | 
|  | suggestion_kind.set_once(value, span); | 
|  | } | 
|  |  | 
|  | // Invalid nested attribute | 
|  | (_, SubdiagnosticKind::Suggestion { .. }) => { | 
|  | span_err(path_span, "invalid nested attribute") | 
|  | .help( | 
|  | "only `no_span`, `style`, `code` and `applicability` are valid nested attributes", | 
|  | ) | 
|  | .emit(); | 
|  | has_errors = true; | 
|  | } | 
|  | (_, SubdiagnosticKind::MultipartSuggestion { .. }) => { | 
|  | span_err(path_span, "invalid nested attribute") | 
|  | .help("only `no_span`, `style` and `applicability` are valid nested attributes") | 
|  | .emit(); | 
|  | has_errors = true; | 
|  | } | 
|  | _ => { | 
|  | span_err(path_span, "only `no_span` is a valid nested attribute").emit(); | 
|  | has_errors = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if has_errors { | 
|  | // Consume the rest of the input to avoid spamming errors | 
|  | let _ = input.parse::<TokenStream>(); | 
|  | } | 
|  |  | 
|  | Ok(()) | 
|  | })?; | 
|  |  | 
|  | match kind { | 
|  | SubdiagnosticKind::Suggestion { | 
|  | ref code_field, | 
|  | ref mut code_init, | 
|  | suggestion_kind: ref mut kind_field, | 
|  | .. | 
|  | } => { | 
|  | if let Some(kind) = suggestion_kind.value() { | 
|  | *kind_field = kind; | 
|  | } | 
|  |  | 
|  | *code_init = if let Some(init) = code.value() { | 
|  | init | 
|  | } else { | 
|  | span_err(span, "suggestion without `code = \"...\"`").emit(); | 
|  | quote! { let #code_field = std::iter::empty(); } | 
|  | }; | 
|  | } | 
|  | SubdiagnosticKind::MultipartSuggestion { | 
|  | suggestion_kind: ref mut kind_field, .. | 
|  | } => { | 
|  | if let Some(kind) = suggestion_kind.value() { | 
|  | *kind_field = kind; | 
|  | } | 
|  | } | 
|  | SubdiagnosticKind::Label | 
|  | | SubdiagnosticKind::Note | 
|  | | SubdiagnosticKind::NoteOnce | 
|  | | SubdiagnosticKind::Help | 
|  | | SubdiagnosticKind::HelpOnce | 
|  | | SubdiagnosticKind::Warn => {} | 
|  | } | 
|  |  | 
|  | Ok(Some(SubdiagnosticVariant { kind, slug, no_span })) | 
|  | } | 
|  | } | 
|  |  | 
|  | impl quote::IdentFragment for SubdiagnosticKind { | 
|  | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | 
|  | match self { | 
|  | SubdiagnosticKind::Label => write!(f, "label"), | 
|  | SubdiagnosticKind::Note => write!(f, "note"), | 
|  | SubdiagnosticKind::NoteOnce => write!(f, "note_once"), | 
|  | SubdiagnosticKind::Help => write!(f, "help"), | 
|  | SubdiagnosticKind::HelpOnce => write!(f, "help_once"), | 
|  | SubdiagnosticKind::Warn => write!(f, "warn"), | 
|  | SubdiagnosticKind::Suggestion { .. } => write!(f, "suggestions_with_style"), | 
|  | SubdiagnosticKind::MultipartSuggestion { .. } => { | 
|  | write!(f, "multipart_suggestion_with_style") | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | fn span(&self) -> Option<proc_macro2::Span> { | 
|  | None | 
|  | } | 
|  | } | 
|  |  | 
|  | /// Returns `true` if `field` should generate a `arg` call rather than any other diagnostic | 
|  | /// call (like `span_label`). | 
|  | pub(super) fn should_generate_arg(field: &Field) -> bool { | 
|  | // Perhaps this should be an exhaustive list... | 
|  | field.attrs.iter().all(|attr| is_doc_comment(attr)) | 
|  | } | 
|  |  | 
|  | pub(super) fn is_doc_comment(attr: &Attribute) -> bool { | 
|  | attr.path().segments.last().unwrap().ident == "doc" | 
|  | } |