mirror of
				https://github.com/rust-lang/rust.git
				synced 2025-10-31 04:57:19 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			667 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			667 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| #![deny(unused_must_use)]
 | |
| use proc_macro::Diagnostic;
 | |
| use quote::{format_ident, quote};
 | |
| use syn::spanned::Spanned;
 | |
| 
 | |
| use std::collections::{BTreeSet, HashMap};
 | |
| 
 | |
| /// Implements #[derive(SessionDiagnostic)], which allows for errors to be specified as a struct, independent
 | |
| /// from the actual diagnostics emitting code.
 | |
| /// ```ignore (pseudo-rust)
 | |
| /// # extern crate rustc_errors;
 | |
| /// # use rustc_errors::Applicability;
 | |
| /// # extern crate rustc_span;
 | |
| /// # use rustc_span::{symbol::Ident, Span};
 | |
| /// # extern crate rust_middle;
 | |
| /// # use rustc_middle::ty::Ty;
 | |
| /// #[derive(SessionDiagnostic)]
 | |
| /// #[code = "E0505"]
 | |
| /// #[error = "cannot move out of {name} because it is borrowed"]
 | |
| /// pub struct MoveOutOfBorrowError<'tcx> {
 | |
| ///     pub name: Ident,
 | |
| ///     pub ty: Ty<'tcx>,
 | |
| ///     #[label = "cannot move out of borrow"]
 | |
| ///     pub span: Span,
 | |
| ///     #[label = "`{ty}` first borrowed here"]
 | |
| ///     pub other_span: Span,
 | |
| ///     #[suggestion(message = "consider cloning here", code = "{name}.clone()")]
 | |
| ///     pub opt_sugg: Option<(Span, Applicability)>
 | |
| /// }
 | |
| /// ```
 | |
| /// Then, later, to emit the error:
 | |
| ///
 | |
| /// ```ignore (pseudo-rust)
 | |
| /// sess.emit_err(MoveOutOfBorrowError {
 | |
| ///     expected,
 | |
| ///     actual,
 | |
| ///     span,
 | |
| ///     other_span,
 | |
| ///     opt_sugg: Some(suggestion, Applicability::MachineApplicable),
 | |
| /// });
 | |
| /// ```
 | |
| pub fn session_diagnostic_derive(s: synstructure::Structure<'_>) -> proc_macro2::TokenStream {
 | |
|     // Names for the diagnostic we build and the session we build it from.
 | |
|     let diag = format_ident!("diag");
 | |
|     let sess = format_ident!("sess");
 | |
| 
 | |
|     SessionDiagnosticDerive::new(diag, sess, s).into_tokens()
 | |
| }
 | |
| 
 | |
| // 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.
 | |
| fn type_matches_path(ty: &syn::Type, name: &[&str]) -> bool {
 | |
|     if let syn::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
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// The central struct for constructing the as_error method from an annotated struct.
 | |
| struct SessionDiagnosticDerive<'a> {
 | |
|     structure: synstructure::Structure<'a>,
 | |
|     builder: SessionDiagnosticDeriveBuilder<'a>,
 | |
| }
 | |
| 
 | |
| impl std::convert::From<syn::Error> for SessionDiagnosticDeriveError {
 | |
|     fn from(e: syn::Error) -> Self {
 | |
|         SessionDiagnosticDeriveError::SynError(e)
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Equivalent to rustc:errors::diagnostic::DiagnosticId, except stores the quoted expression to
 | |
| /// initialise the code with.
 | |
| enum DiagnosticId {
 | |
|     Error(proc_macro2::TokenStream),
 | |
|     Lint(proc_macro2::TokenStream),
 | |
| }
 | |
| 
 | |
| #[derive(Debug)]
 | |
| enum SessionDiagnosticDeriveError {
 | |
|     SynError(syn::Error),
 | |
|     ErrorHandled,
 | |
| }
 | |
| 
 | |
| impl SessionDiagnosticDeriveError {
 | |
|     fn to_compile_error(self) -> proc_macro2::TokenStream {
 | |
|         match self {
 | |
|             SessionDiagnosticDeriveError::SynError(e) => e.to_compile_error(),
 | |
|             SessionDiagnosticDeriveError::ErrorHandled => {
 | |
|                 // Return ! to avoid having to create a blank DiagnosticBuilder to return when an
 | |
|                 // error has already been emitted to the compiler.
 | |
|                 quote! {
 | |
|                     unreachable!()
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| fn span_err(span: impl proc_macro::MultiSpan, msg: &str) -> proc_macro::Diagnostic {
 | |
|     Diagnostic::spanned(span, proc_macro::Level::Error, msg)
 | |
| }
 | |
| 
 | |
| /// For methods that return a Result<_, SessionDiagnosticDeriveError>: emit a diagnostic on
 | |
| /// span $span with msg $msg (and, optionally, perform additional decoration using the FnOnce
 | |
| /// passed in `diag`). Then, return Err(ErrorHandled).
 | |
| macro_rules! throw_span_err {
 | |
|     ($span:expr, $msg:expr) => {{ throw_span_err!($span, $msg, |diag| diag) }};
 | |
|     ($span:expr, $msg:expr, $f:expr) => {{
 | |
|         return Err(_throw_span_err($span, $msg, $f));
 | |
|     }};
 | |
| }
 | |
| 
 | |
| /// When possible, prefer using throw_span_err! over using this function directly. This only exists
 | |
| /// as a function to constrain `f` to an impl FnOnce.
 | |
| fn _throw_span_err(
 | |
|     span: impl proc_macro::MultiSpan,
 | |
|     msg: &str,
 | |
|     f: impl FnOnce(proc_macro::Diagnostic) -> proc_macro::Diagnostic,
 | |
| ) -> SessionDiagnosticDeriveError {
 | |
|     let diag = span_err(span, msg);
 | |
|     f(diag).emit();
 | |
|     SessionDiagnosticDeriveError::ErrorHandled
 | |
| }
 | |
| 
 | |
| impl<'a> SessionDiagnosticDerive<'a> {
 | |
|     fn new(diag: syn::Ident, sess: syn::Ident, structure: synstructure::Structure<'a>) -> Self {
 | |
|         // Build the mapping of field names to fields. This allows attributes to peek values from
 | |
|         // other fields.
 | |
|         let mut fields_map = HashMap::new();
 | |
| 
 | |
|         // Convenience bindings.
 | |
|         let ast = structure.ast();
 | |
| 
 | |
|         if let syn::Data::Struct(syn::DataStruct { fields, .. }) = &ast.data {
 | |
|             for field in fields.iter() {
 | |
|                 if let Some(ident) = &field.ident {
 | |
|                     fields_map.insert(ident.to_string(), field);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         Self {
 | |
|             builder: SessionDiagnosticDeriveBuilder { diag, sess, fields: fields_map, kind: None },
 | |
|             structure,
 | |
|         }
 | |
|     }
 | |
|     fn into_tokens(self) -> proc_macro2::TokenStream {
 | |
|         let SessionDiagnosticDerive { structure, mut builder } = self;
 | |
| 
 | |
|         let ast = structure.ast();
 | |
|         let attrs = &ast.attrs;
 | |
| 
 | |
|         let implementation = {
 | |
|             if let syn::Data::Struct(..) = ast.data {
 | |
|                 let preamble = {
 | |
|                     let preamble = attrs.iter().map(|attr| {
 | |
|                         builder
 | |
|                             .generate_structure_code(attr)
 | |
|                             .unwrap_or_else(|v| v.to_compile_error())
 | |
|                     });
 | |
|                     quote! {
 | |
|                         #(#preamble)*;
 | |
|                     }
 | |
|                 };
 | |
| 
 | |
|                 let body = structure.each(|field_binding| {
 | |
|                     let field = field_binding.ast();
 | |
|                     let result = field.attrs.iter().map(|attr| {
 | |
|                         builder
 | |
|                             .generate_field_code(
 | |
|                                 attr,
 | |
|                                 FieldInfo {
 | |
|                                     vis: &field.vis,
 | |
|                                     binding: field_binding,
 | |
|                                     ty: &field.ty,
 | |
|                                     span: &field.span(),
 | |
|                                 },
 | |
|                             )
 | |
|                             .unwrap_or_else(|v| v.to_compile_error())
 | |
|                     });
 | |
|                     return quote! {
 | |
|                         #(#result);*
 | |
|                     };
 | |
|                 });
 | |
|                 // Finally, putting it altogether.
 | |
|                 match builder.kind {
 | |
|                     None => {
 | |
|                         span_err(ast.span().unwrap(), "`code` not specified")
 | |
|                         .help("use the [code = \"...\"] attribute to set this diagnostic's error code ")
 | |
|                         .emit();
 | |
|                         SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
 | |
|                     }
 | |
|                     Some((kind, _)) => match kind {
 | |
|                         DiagnosticId::Lint(_lint) => todo!(),
 | |
|                         DiagnosticId::Error(code) => {
 | |
|                             let (diag, sess) = (&builder.diag, &builder.sess);
 | |
|                             quote! {
 | |
|                                 let mut #diag = #sess.struct_err_with_code("", rustc_errors::DiagnosticId::Error(#code));
 | |
|                                 #preamble
 | |
|                                 match self {
 | |
|                                     #body
 | |
|                                 }
 | |
|                                 #diag
 | |
|                             }
 | |
|                         }
 | |
|                     },
 | |
|                 }
 | |
|             } else {
 | |
|                 span_err(
 | |
|                     ast.span().unwrap(),
 | |
|                     "`#[derive(SessionDiagnostic)]` can only be used on structs",
 | |
|                 )
 | |
|                 .emit();
 | |
|                 SessionDiagnosticDeriveError::ErrorHandled.to_compile_error()
 | |
|             }
 | |
|         };
 | |
| 
 | |
|         let sess = &builder.sess;
 | |
|         structure.gen_impl(quote! {
 | |
|             gen impl<'__session_diagnostic_sess> rustc_session::SessionDiagnostic<'__session_diagnostic_sess>
 | |
|                     for @Self
 | |
|             {
 | |
|                 fn into_diagnostic(
 | |
|                     self,
 | |
|                     #sess: &'__session_diagnostic_sess rustc_session::Session
 | |
|                 ) -> rustc_errors::DiagnosticBuilder<'__session_diagnostic_sess> {
 | |
|                     #implementation
 | |
|                 }
 | |
|             }
 | |
|         })
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Field information passed to the builder. Deliberately omits attrs to discourage the generate_*
 | |
| /// methods from walking the attributes themselves.
 | |
| struct FieldInfo<'a> {
 | |
|     vis: &'a syn::Visibility,
 | |
|     binding: &'a synstructure::BindingInfo<'a>,
 | |
|     ty: &'a syn::Type,
 | |
|     span: &'a proc_macro2::Span,
 | |
| }
 | |
| 
 | |
| /// Tracks persistent information required for building up the individual calls to diagnostic
 | |
| /// methods for the final generated method. This is a separate struct to SessionDerive only to be
 | |
| /// able to destructure and split self.builder and the self.structure up to avoid a double mut
 | |
| /// borrow later on.
 | |
| struct SessionDiagnosticDeriveBuilder<'a> {
 | |
|     /// Name of the session parameter that's passed in to the as_error method.
 | |
|     sess: syn::Ident,
 | |
| 
 | |
|     /// Store a map of field name to its corresponding field. This is built on construction of the
 | |
|     /// derive builder.
 | |
|     fields: HashMap<String, &'a syn::Field>,
 | |
| 
 | |
|     /// The identifier to use for the generated DiagnosticBuilder instance.
 | |
|     diag: syn::Ident,
 | |
| 
 | |
|     /// Whether this is a lint or an error. This dictates how the diag will be initialised. Span
 | |
|     /// stores at what Span the kind was first set at (for error reporting purposes, if the kind
 | |
|     /// was multiply specified).
 | |
|     kind: Option<(DiagnosticId, proc_macro2::Span)>,
 | |
| }
 | |
| 
 | |
| impl<'a> SessionDiagnosticDeriveBuilder<'a> {
 | |
|     fn generate_structure_code(
 | |
|         &mut self,
 | |
|         attr: &syn::Attribute,
 | |
|     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
 | |
|         Ok(match attr.parse_meta()? {
 | |
|             syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
 | |
|                 let formatted_str = self.build_format(&s.value(), attr.span());
 | |
|                 let name = attr.path.segments.last().unwrap().ident.to_string();
 | |
|                 let name = name.as_str();
 | |
|                 match name {
 | |
|                     "message" => {
 | |
|                         let diag = &self.diag;
 | |
|                         quote! {
 | |
|                             #diag.set_primary_message(#formatted_str);
 | |
|                         }
 | |
|                     }
 | |
|                     attr @ "error" | attr @ "lint" => {
 | |
|                         self.set_kind_once(
 | |
|                             if attr == "error" {
 | |
|                                 DiagnosticId::Error(formatted_str)
 | |
|                             } else if attr == "lint" {
 | |
|                                 DiagnosticId::Lint(formatted_str)
 | |
|                             } else {
 | |
|                                 unreachable!()
 | |
|                             },
 | |
|                             s.span(),
 | |
|                         )?;
 | |
|                         // This attribute is only allowed to be applied once, and the attribute
 | |
|                         // will be set in the initialisation code.
 | |
|                         quote! {}
 | |
|                     }
 | |
|                     other => throw_span_err!(
 | |
|                         attr.span().unwrap(),
 | |
|                         &format!(
 | |
|                             "`#[{} = ...]` is not a valid SessionDiagnostic struct attribute",
 | |
|                             other
 | |
|                         )
 | |
|                     ),
 | |
|                 }
 | |
|             }
 | |
|             _ => todo!("unhandled meta kind"),
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     #[must_use]
 | |
|     fn set_kind_once(
 | |
|         &mut self,
 | |
|         kind: DiagnosticId,
 | |
|         span: proc_macro2::Span,
 | |
|     ) -> Result<(), SessionDiagnosticDeriveError> {
 | |
|         if self.kind.is_none() {
 | |
|             self.kind = Some((kind, span));
 | |
|             Ok(())
 | |
|         } else {
 | |
|             let kind_str = |kind: &DiagnosticId| match kind {
 | |
|                 DiagnosticId::Lint(..) => "lint",
 | |
|                 DiagnosticId::Error(..) => "error",
 | |
|             };
 | |
| 
 | |
|             let existing_kind = kind_str(&self.kind.as_ref().unwrap().0);
 | |
|             let this_kind = kind_str(&kind);
 | |
| 
 | |
|             let msg = if this_kind == existing_kind {
 | |
|                 format!("`{}` specified multiple times", existing_kind)
 | |
|             } else {
 | |
|                 format!("`{}` specified when `{}` was already specified", this_kind, existing_kind)
 | |
|             };
 | |
|             throw_span_err!(span.unwrap(), &msg);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn generate_field_code(
 | |
|         &mut self,
 | |
|         attr: &syn::Attribute,
 | |
|         info: FieldInfo<'_>,
 | |
|     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
 | |
|         let field_binding = &info.binding.binding;
 | |
| 
 | |
|         let option_ty = option_inner_ty(&info.ty);
 | |
| 
 | |
|         let generated_code = self.generate_non_option_field_code(
 | |
|             attr,
 | |
|             FieldInfo {
 | |
|                 vis: info.vis,
 | |
|                 binding: info.binding,
 | |
|                 ty: option_ty.unwrap_or(&info.ty),
 | |
|                 span: info.span,
 | |
|             },
 | |
|         )?;
 | |
|         Ok(if option_ty.is_none() {
 | |
|             quote! { #generated_code }
 | |
|         } else {
 | |
|             quote! {
 | |
|                 if let Some(#field_binding) = #field_binding {
 | |
|                     #generated_code
 | |
|                 }
 | |
|             }
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     fn generate_non_option_field_code(
 | |
|         &mut self,
 | |
|         attr: &syn::Attribute,
 | |
|         info: FieldInfo<'_>,
 | |
|     ) -> Result<proc_macro2::TokenStream, SessionDiagnosticDeriveError> {
 | |
|         let diag = &self.diag;
 | |
|         let field_binding = &info.binding.binding;
 | |
|         let name = attr.path.segments.last().unwrap().ident.to_string();
 | |
|         let name = name.as_str();
 | |
|         // At this point, we need to dispatch based on the attribute key + the
 | |
|         // type.
 | |
|         let meta = attr.parse_meta()?;
 | |
|         Ok(match meta {
 | |
|             syn::Meta::NameValue(syn::MetaNameValue { lit: syn::Lit::Str(s), .. }) => {
 | |
|                 let formatted_str = self.build_format(&s.value(), attr.span());
 | |
|                 match name {
 | |
|                     "message" => {
 | |
|                         if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
 | |
|                             quote! {
 | |
|                                 #diag.set_span(*#field_binding);
 | |
|                                 #diag.set_primary_message(#formatted_str);
 | |
|                             }
 | |
|                         } else {
 | |
|                             throw_span_err!(
 | |
|                                 attr.span().unwrap(),
 | |
|                                 "the `#[message = \"...\"]` attribute can only be applied to fields of type Span"
 | |
|                             );
 | |
|                         }
 | |
|                     }
 | |
|                     "label" => {
 | |
|                         if type_matches_path(&info.ty, &["rustc_span", "Span"]) {
 | |
|                             quote! {
 | |
|                                 #diag.span_label(*#field_binding, #formatted_str);
 | |
|                             }
 | |
|                         } else {
 | |
|                             throw_span_err!(
 | |
|                                 attr.span().unwrap(),
 | |
|                                 "The `#[label = ...]` attribute can only be applied to fields of type Span"
 | |
|                             );
 | |
|                         }
 | |
|                     }
 | |
|                     other => throw_span_err!(
 | |
|                         attr.span().unwrap(),
 | |
|                         &format!(
 | |
|                             "`#[{} = ...]` is not a valid SessionDiagnostic field attribute",
 | |
|                             other
 | |
|                         )
 | |
|                     ),
 | |
|                 }
 | |
|             }
 | |
|             syn::Meta::List(list) => {
 | |
|                 match list.path.segments.iter().last().unwrap().ident.to_string().as_str() {
 | |
|                     suggestion_kind @ "suggestion"
 | |
|                     | suggestion_kind @ "suggestion_short"
 | |
|                     | suggestion_kind @ "suggestion_hidden"
 | |
|                     | suggestion_kind @ "suggestion_verbose" => {
 | |
|                         // For suggest, we need to ensure we are running on a (Span,
 | |
|                         // Applicability) pair.
 | |
|                         let (span, applicability) = (|| match &info.ty {
 | |
|                             ty @ syn::Type::Path(..)
 | |
|                                 if type_matches_path(ty, &["rustc_span", "Span"]) =>
 | |
|                             {
 | |
|                                 let binding = &info.binding.binding;
 | |
|                                 Ok((
 | |
|                                     quote!(*#binding),
 | |
|                                     quote!(rustc_errors::Applicability::Unspecified),
 | |
|                                 ))
 | |
|                             }
 | |
|                             syn::Type::Tuple(tup) => {
 | |
|                                 let mut span_idx = None;
 | |
|                                 let mut applicability_idx = None;
 | |
|                                 for (idx, elem) in tup.elems.iter().enumerate() {
 | |
|                                     if type_matches_path(elem, &["rustc_span", "Span"]) {
 | |
|                                         if span_idx.is_none() {
 | |
|                                             span_idx = Some(syn::Index::from(idx));
 | |
|                                         } else {
 | |
|                                             throw_span_err!(
 | |
|                                                 info.span.unwrap(),
 | |
|                                                 "type of field annotated with `#[suggestion(...)]` contains more than one Span"
 | |
|                                             );
 | |
|                                         }
 | |
|                                     } else if type_matches_path(
 | |
|                                         elem,
 | |
|                                         &["rustc_errors", "Applicability"],
 | |
|                                     ) {
 | |
|                                         if applicability_idx.is_none() {
 | |
|                                             applicability_idx = Some(syn::Index::from(idx));
 | |
|                                         } else {
 | |
|                                             throw_span_err!(
 | |
|                                                 info.span.unwrap(),
 | |
|                                                 "type of field annotated with `#[suggestion(...)]` contains more than one Applicability"
 | |
|                                             );
 | |
|                                         }
 | |
|                                     }
 | |
|                                 }
 | |
|                                 if let Some(span_idx) = span_idx {
 | |
|                                     let binding = &info.binding.binding;
 | |
|                                     let span = quote!(#binding.#span_idx);
 | |
|                                     let applicability = applicability_idx
 | |
|                                         .map(
 | |
|                                             |applicability_idx| quote!(#binding.#applicability_idx),
 | |
|                                         )
 | |
|                                         .unwrap_or_else(|| {
 | |
|                                             quote!(rustc_errors::Applicability::Unspecified)
 | |
|                                         });
 | |
|                                     return Ok((span, applicability));
 | |
|                                 }
 | |
|                                 throw_span_err!(
 | |
|                                     info.span.unwrap(),
 | |
|                                     "wrong types for suggestion",
 | |
|                                     |diag| {
 | |
|                                         diag.help("#[suggestion(...)] on a tuple field must be applied to fields of type (Span, Applicability)")
 | |
|                                     }
 | |
|                                 );
 | |
|                             }
 | |
|                             _ => throw_span_err!(
 | |
|                                 info.span.unwrap(),
 | |
|                                 "wrong field type for suggestion",
 | |
|                                 |diag| {
 | |
|                                     diag.help("#[suggestion(...)] should be applied to fields of type Span or (Span, Applicability)")
 | |
|                                 }
 | |
|                             ),
 | |
|                         })()?;
 | |
|                         // Now read the key-value pairs.
 | |
|                         let mut msg = None;
 | |
|                         let mut code = None;
 | |
| 
 | |
|                         for arg in list.nested.iter() {
 | |
|                             if let syn::NestedMeta::Meta(syn::Meta::NameValue(arg_name_value)) = arg
 | |
|                             {
 | |
|                                 if let syn::MetaNameValue { lit: syn::Lit::Str(s), .. } =
 | |
|                                     arg_name_value
 | |
|                                 {
 | |
|                                     let name = arg_name_value
 | |
|                                         .path
 | |
|                                         .segments
 | |
|                                         .last()
 | |
|                                         .unwrap()
 | |
|                                         .ident
 | |
|                                         .to_string();
 | |
|                                     let name = name.as_str();
 | |
|                                     let formatted_str = self.build_format(&s.value(), arg.span());
 | |
|                                     match name {
 | |
|                                         "message" => {
 | |
|                                             msg = Some(formatted_str);
 | |
|                                         }
 | |
|                                         "code" => {
 | |
|                                             code = Some(formatted_str);
 | |
|                                         }
 | |
|                                         other => throw_span_err!(
 | |
|                                             arg.span().unwrap(),
 | |
|                                             &format!(
 | |
|                                                 "`{}` is not a valid key for `#[suggestion(...)]`",
 | |
|                                                 other
 | |
|                                             )
 | |
|                                         ),
 | |
|                                     }
 | |
|                                 }
 | |
|                             }
 | |
|                         }
 | |
|                         let msg = if let Some(msg) = msg {
 | |
|                             quote!(#msg.as_str())
 | |
|                         } else {
 | |
|                             throw_span_err!(
 | |
|                                 list.span().unwrap(),
 | |
|                                 "missing suggestion message",
 | |
|                                 |diag| {
 | |
|                                     diag.help("provide a suggestion message using #[suggestion(message = \"...\")]")
 | |
|                                 }
 | |
|                             );
 | |
|                         };
 | |
|                         let code = code.unwrap_or_else(|| quote! { String::new() });
 | |
|                         // Now build it out:
 | |
|                         let suggestion_method = format_ident!("span_{}", suggestion_kind);
 | |
|                         quote! {
 | |
|                             #diag.#suggestion_method(#span, #msg, #code, #applicability);
 | |
|                         }
 | |
|                     }
 | |
|                     other => throw_span_err!(
 | |
|                         list.span().unwrap(),
 | |
|                         &format!("invalid annotation list `#[{}(...)]`", other)
 | |
|                     ),
 | |
|                 }
 | |
|             }
 | |
|             _ => panic!("unhandled meta kind"),
 | |
|         })
 | |
|     }
 | |
| 
 | |
|     /// In the strings in the attributes supplied to this macro, we want callers to be able to
 | |
|     /// reference fields in the format string. Take this, for example:
 | |
|     /// ```ignore (not-usage-example)
 | |
|     /// struct Point {
 | |
|     ///     #[error = "Expected a point greater than ({x}, {y})"]
 | |
|     ///     x: i32,
 | |
|     ///     y: i32,
 | |
|     /// }
 | |
|     /// ```
 | |
|     /// We want to automatically pick up that {x} refers `self.x` and {y} refers to `self.y`, then
 | |
|     /// generate this call to format!:
 | |
|     /// ```ignore (not-usage-example)
 | |
|     /// format!("Expected a point greater than ({x}, {y})", x = self.x, y = self.y)
 | |
|     /// ```
 | |
|     /// This function builds the entire call to format!.
 | |
|     fn build_format(&self, input: &str, span: proc_macro2::Span) -> proc_macro2::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 == '{' && *it.peek().unwrap_or(&'\0') != '{' {
 | |
|                 #[must_use]
 | |
|                 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 = if self.fields.contains_key(&field) {
 | |
|                 quote! {
 | |
|                     &self.#field_ident
 | |
|                 }
 | |
|             } else {
 | |
|                 // This field doesn't exist. Emit a diagnostic.
 | |
|                 Diagnostic::spanned(
 | |
|                     span.unwrap(),
 | |
|                     proc_macro::Level::Error,
 | |
|                     format!("`{}` doesn't refer to a field on this type", field),
 | |
|                 )
 | |
|                 .emit();
 | |
|                 quote! {
 | |
|                     "{#field}"
 | |
|                 }
 | |
|             };
 | |
|             quote! {
 | |
|                 #field_ident = #value
 | |
|             }
 | |
|         });
 | |
|         quote! {
 | |
|             format!(#input #(,#args)*)
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// If `ty` is an Option, returns Some(inner type). Else, returns None.
 | |
| fn option_inner_ty(ty: &syn::Type) -> Option<&syn::Type> {
 | |
|     if type_matches_path(ty, &["std", "option", "Option"]) {
 | |
|         if let syn::Type::Path(ty_path) = ty {
 | |
|             let path = &ty_path.path;
 | |
|             let ty = path.segments.iter().last().unwrap();
 | |
|             if let syn::PathArguments::AngleBracketed(bracketed) = &ty.arguments {
 | |
|                 if bracketed.args.len() == 1 {
 | |
|                     if let syn::GenericArgument::Type(ty) = &bracketed.args[0] {
 | |
|                         return Some(ty);
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     None
 | |
| }
 | 
