mirror of
https://github.com/rust-lang/rust.git
synced 2025-11-01 21:45:15 +00:00
The PR had some unforseen perf regressions that are not as easy to find. Revert the PR for now. This reverts commit 6ae8912a3e7d2c4c775024f58a7ba4b1aedc4073, reversing changes made to 86d6d2b7389fe1b339402c1798edae8b695fc9ef.
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
|
|
}
|