#![recursion_limit = "300"]
#![cfg_attr(feature = "cargo-clippy", deny(clippy, clippy_pedantic))]
#![cfg_attr(feature = "cargo-clippy", allow(
large_enum_variant,
too_many_arguments,
use_self,
))]
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;
extern crate syntex_fmt_macros;
#[proc_macro_derive(ErrorChain, attributes(error_chain))]
pub fn derive_error_chain(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap();
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let mut generics_lifetime = ast.generics.clone();
generics_lifetime.params = std::iter::once(parse_quote!('__a)).chain(generics_lifetime.params).collect();
let (impl_generics_lifetime, _, _) = generics_lifetime.split_for_impl();
let mut result_generics = ast.generics.clone();
result_generics.params.push(parse_quote!(__T));
let (_, result_ty_generics, _) = result_generics.split_for_impl();
let mut result_ext_generics_t = ast.generics.clone();
result_ext_generics_t.params.push(parse_quote!(__T));
let (result_ext_impl_generics_t, result_ext_ty_generics_t, _) = result_ext_generics_t.split_for_impl();
let mut result_ext_generics_t_e = result_ext_generics_t.clone();
result_ext_generics_t_e.params.push(parse_quote!(__E: ::std::error::Error + ::std::marker::Send + 'static));
let (result_ext_impl_generics_t_e, _, _) = result_ext_generics_t_e.split_for_impl();
let generics: std::collections::HashSet<_> =
ast.generics.params.iter()
.filter_map(|param|
if let syn::GenericParam::Type(syn::TypeParam { ref ident, .. }) = *param {
Some(ident)
}
else {
None
})
.collect();
let TopLevelProperties {
error_kind_name,
error_kind_vis,
error_name,
result_ext_name,
result_name,
support_backtrace,
error_chain_name,
} = (&ast).into();
let result = match ast.data {
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
let links: Vec<Link> = variants.into_iter().map(Into::into).collect();
let error_kind_description_cases = links.iter().map(|link| link.error_kind_description(&error_kind_name));
let error_kind_display_cases = links.iter().map(|link| link.error_kind_display_case(&error_kind_name));
let error_kind_from_impls =
links.iter().filter_map(|link|
link.error_kind_from_impl(
&error_kind_name,
&impl_generics, &impl_generics_lifetime, &ty_generics, where_clause,
));
let error_cause_cases = links.iter().filter_map(|link| link.error_cause_case(&error_kind_name));
let error_doc_comment = format!(r"The Error type.
This struct is made of three things:
- `{0}` which is used to determine the type of the error.
- a backtrace, generated when the error is created.
- an error chain, used for the implementation of `Error::cause()`.", error_kind_name);
let error_from_impls =
links.iter().filter_map(|link|
link.error_from_impl(
&error_kind_name, &error_name,
&generics,
&impl_generics, &impl_generics_lifetime, &ty_generics, where_clause,
));
let extract_backtrace_fn = if support_backtrace {
let chained_error_extract_backtrace_cases = links.iter().filter_map(Link::chained_error_extract_backtrace_case);
Some(quote! {
fn extract_backtrace(err: &(::std::error::Error + Send + 'static)) -> Option<::std::sync::Arc<#error_chain_name::Backtrace>> {
if let Some(err) = err.downcast_ref::<Self>() {
return err.1.backtrace.clone();
}
#(#chained_error_extract_backtrace_cases)*
None
}
})
}
else {
None
};
let result_ext_chain_err_doc_comment = format!("\
If the `Result` is an `Err` then `chain_err` evaluates the closure, \
which returns *some type that can be converted to `{}`*, \
boxes the original error to store as the cause, then returns a new error \
containing the original error.\
", error_kind_name);
let result_wrapper = result_name.map(|result_name| quote! {
#error_kind_vis type #result_name #result_ty_generics = ::std::result::Result<__T, #error_name #ty_generics>;
});
quote! {
extern crate error_chain as #error_chain_name;
impl #impl_generics #error_kind_name #ty_generics #where_clause {
pub fn description(&self) -> &str {
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
match *self {
#(#error_kind_description_cases)*
}
}
}
impl #impl_generics ::std::fmt::Display for #error_kind_name #ty_generics #where_clause {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
match *self {
#(#error_kind_display_cases)*
}
}
}
#(#error_kind_from_impls)*
impl #impl_generics From<#error_name #ty_generics> for #error_kind_name #ty_generics #where_clause {
fn from(err: #error_name #ty_generics) -> Self { err.0 }
}
#[doc = #error_doc_comment]
#[derive(Debug)]
#error_kind_vis struct #error_name #impl_generics (
pub #error_kind_name #ty_generics,
pub #error_chain_name::State,
) #where_clause ;
#[allow(unused)]
impl #impl_generics #error_name #ty_generics #where_clause {
pub fn from_kind(kind: #error_kind_name #ty_generics) -> Self {
#error_name(kind, #error_chain_name::State::default())
}
pub fn with_chain<__E, __K>(error: __E, kind: __K) -> Self
where __E: ::std::error::Error + Send + 'static, __K: Into<#error_kind_name #ty_generics>
{
#error_name::with_boxed_chain(Box::new(error), kind)
}
pub fn with_boxed_chain<__K>(error: Box<::std::error::Error + Send>, kind: __K) -> #error_name #ty_generics
where __K: Into<#error_kind_name #ty_generics>
{
#error_name(kind.into(), #error_chain_name::State::new::<Self>(error))
}
pub fn kind(&self) -> &#error_kind_name #ty_generics { &self.0 }
pub fn iter(&self) -> #error_chain_name::Iter {
#error_chain_name::ChainedError::iter(self)
}
pub fn backtrace(&self) -> Option<&#error_chain_name::Backtrace> {
self.1.backtrace()
}
pub fn chain_err<__F, __EK>(self, error: __F) -> Self where __F: FnOnce() -> __EK, __EK: Into<#error_kind_name #ty_generics> {
#error_name::with_chain(self, Self::from_kind(error().into()))
}
}
impl #impl_generics ::std::error::Error for #error_name #ty_generics #where_clause {
fn description(&self) -> &str { self.0.description() }
fn cause(&self) -> Option<&::std::error::Error> {
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
match self.1.next_error {
Some(ref c) => Some(&**c),
None => match self.0 {
#(#error_cause_cases)*
_ => None,
},
}
}
}
impl #impl_generics ::std::fmt::Display for #error_name #ty_generics #where_clause {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::std::fmt::Display::fmt(&self.0, f)
}
}
#(#error_from_impls)*
impl #impl_generics From<#error_kind_name #ty_generics> for #error_name #ty_generics #where_clause {
fn from(kind: #error_kind_name #ty_generics) -> Self { Self::from_kind(kind) }
}
impl #impl_generics ::std::ops::Deref for #error_name #ty_generics #where_clause {
type Target = #error_kind_name #ty_generics;
fn deref(&self) -> &Self::Target { &self.0 }
}
impl #impl_generics #error_chain_name::ChainedError for #error_name #ty_generics #where_clause {
type ErrorKind = #error_kind_name #ty_generics;
fn new(kind: Self::ErrorKind, state: #error_chain_name::State) -> Self {
#error_name(kind, state)
}
fn from_kind(kind: Self::ErrorKind) -> Self {
Self::from_kind(kind)
}
fn with_chain<__E, __K>(error: __E, kind: __K) -> Self
where __E: ::std::error::Error + Send + 'static, __K: Into<Self::ErrorKind> {
Self::with_chain(error, kind)
}
fn kind(&self) -> &Self::ErrorKind {
self.kind()
}
fn iter(&self) -> #error_chain_name::Iter {
#error_chain_name::Iter::new(Some(self))
}
fn backtrace(&self) -> Option<&#error_chain_name::Backtrace> {
self.backtrace()
}
fn chain_err<__F, __EK>(self, error: __F) -> Self where __F: FnOnce() -> __EK, __EK: Into<Self::ErrorKind> {
self.chain_err(error)
}
#extract_backtrace_fn
}
#error_kind_vis trait #result_ext_name #result_ext_impl_generics_t #where_clause {
#[doc = #result_ext_chain_err_doc_comment]
fn chain_err<__F, __EK>(self, callback: __F) -> ::std::result::Result<__T, #error_name #ty_generics>
where __F: FnOnce() -> __EK, __EK: Into<#error_kind_name #ty_generics>;
}
impl #result_ext_impl_generics_t_e #result_ext_name #result_ext_ty_generics_t for ::std::result::Result<__T, __E> #where_clause {
fn chain_err<__F, __EK>(self, callback: __F) -> ::std::result::Result<__T, #error_name #ty_generics>
where __F: FnOnce() -> __EK, __EK: Into<#error_kind_name #ty_generics> {
self.map_err(move |e| {
let state = #error_chain_name::State::new::<#error_name #ty_generics>(Box::new(e));
#error_chain_name::ChainedError::new(callback().into(), state)
})
}
}
impl #result_ext_impl_generics_t #result_ext_name #result_ext_ty_generics_t for ::std::option::Option<__T> #where_clause {
fn chain_err<__F, __EK>(self, callback: __F) -> ::std::result::Result<__T, #error_name #ty_generics>
where __F: FnOnce() -> __EK, __EK: Into<#error_kind_name #ty_generics> {
self.ok_or_else(move || {
#error_chain_name::ChainedError::from_kind(callback().into())
})
}
}
#result_wrapper
}
},
_ => panic!("#[derive(ErrorChain] can only be used with enums."),
};
result.into()
}
struct TopLevelProperties {
error_kind_name: proc_macro2::Ident,
error_kind_vis: syn::Visibility,
error_name: proc_macro2::Ident,
result_ext_name: proc_macro2::Ident,
result_name: Option<proc_macro2::Ident>,
error_chain_name: proc_macro2::Ident,
support_backtrace: bool,
}
impl<'a> From<&'a syn::DeriveInput> for TopLevelProperties {
fn from(ast: &'a syn::DeriveInput) -> Self {
let mut error_name = proc_macro2::Ident::new("Error", proc_macro2::Span::call_site());
let mut result_ext_name = proc_macro2::Ident::new("ResultExt", proc_macro2::Span::call_site());
let mut result_name = Some(proc_macro2::Ident::new("Result", proc_macro2::Span::call_site()));
let mut support_backtrace = true;
for attr in &ast.attrs {
if !is_error_chain_attribute(attr) {
continue;
}
match attr.interpret_meta() {
Some(syn::Meta::List(syn::MetaList { nested, .. })) => {
for nested_meta in nested {
match nested_meta {
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { ident, lit: syn::Lit::Str(value), .. })) => {
let value = &value.value();
match &*ident.to_string() {
"error" => error_name = syn::parse_str(value).unwrap_or_else(|err|
panic!("Could not parse `error` value as an identifier - {}", err)),
"result_ext" => result_ext_name = syn::parse_str(value).unwrap_or_else(|err|
panic!("Could not parse `result_ext` value as an identifier - {}", err)),
"result" => result_name =
if value == "" {
None
}
else {
Some(syn::parse_str(value).unwrap_or_else(|err|
panic!("Could not parse `result` value as an identifier - {}", err)))
},
"backtrace" => support_backtrace = value.parse().unwrap_or_else(|err|
panic!("Could not parse `backtrace` value - {}", err)),
_ =>
panic!("Could not parse `error_chain` attribute - expected one of `error`, `result_ext`, `result`, `backtrace` but got {}", ident),
}
},
syn::NestedMeta::Meta(syn::Meta::NameValue(
syn::MetaNameValue { ref ident, lit: syn::Lit::Bool(syn::LitBool { value, .. }), .. }))
if ident == "backtrace" => support_backtrace = value,
_ => panic!("Could not parse `error_chain` attribute - expected one of `error`, `result_ext`, `result`, `backtrace`"),
}
}
},
_ => panic!("Could not parse `error_chain` attribute - expected one of `error`, `result_ext`, `result`, `backtrace`"),
}
}
let error_chain_name = syn::parse_str(&format!("{}_error_chain", error_name)).unwrap_or_else(|err|
panic!("Could not generate error_chain crate name as a valid ident - {}", err));
TopLevelProperties {
error_kind_name: ast.ident.clone(),
error_kind_vis: ast.vis.clone(),
error_name,
result_ext_name,
result_name,
error_chain_name,
support_backtrace,
}
}
}
struct Link {
variant_ident: proc_macro2::Ident,
variant_fields: syn::Fields,
link_type: LinkType,
custom_description: Option<CustomFormatter>,
custom_display: Option<CustomFormatter>,
custom_cause: Option<syn::Expr>,
}
enum LinkType {
Msg,
Chainable(syn::Type, syn::Type),
Foreign(syn::Type),
Custom,
}
impl From<syn::Variant> for Link {
fn from(syn::Variant { ident: variant_ident, attrs, fields: variant_fields, .. }: syn::Variant) -> Self {
let is_msg = loop {
if variant_ident != "Msg" {
break false;
}
if let syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) = variant_fields {
if unnamed.len() == 1 {
if let syn::Type::Path(syn::TypePath { ref path, .. }) = unnamed[0].ty {
if !path.global() && path.segments.len() == 1 && path.segments[0].ident == "String" {
break true;
}
}
}
}
panic!("Expected Msg member to be a tuple of String");
};
if is_msg {
return Link {
variant_ident,
variant_fields,
link_type: LinkType::Msg,
custom_description: None,
custom_display: None,
custom_cause: None,
};
}
let mut link_type = None;
let mut custom_description = None;
let mut custom_display = None;
let mut custom_cause: Option<syn::Expr> = None;
for attr in attrs {
if !is_error_chain_attribute(&attr) {
continue;
}
if let Some(syn::Meta::List(syn::MetaList { nested, .. })) = attr.interpret_meta() {
for nested_meta in nested {
match nested_meta {
syn::NestedMeta::Meta(syn::Meta::Word(ident)) => match &*ident.to_string() {
"foreign" => match variant_fields {
syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 =>
link_type = Some(LinkType::Foreign(unnamed[0].ty.clone())),
_ => panic!("Foreign link {} must be a tuple of one element (the foreign error type).", variant_ident),
},
"custom" => link_type = Some(LinkType::Custom),
_ => panic!(
"Could not parse `error_chain` attribute of member {} - expected one of `foreign`, `custom` but got {}",
variant_ident, ident),
},
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { ident, lit: syn::Lit::Str(value), .. })) => {
let value = &value.value();
match &*ident.to_string() {
"link" => match variant_fields {
syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 =>
link_type = Some(LinkType::Chainable(
syn::parse_str(value).unwrap_or_else(|err|
panic!("Could not parse `link` attribute of member {} as a type - {}", variant_ident, err)),
unnamed[0].ty.clone())),
_ => panic!("Chainable link {} must be a tuple of one element (the chainable error kind).", variant_ident),
},
"description" => custom_description = Some(CustomFormatter::Expr(syn::parse_str(value).unwrap_or_else(|err|
panic!("Could not parse `description` attribute of member {} as an expression - {}", variant_ident, err)))),
"display" => custom_display = Some(CustomFormatter::Expr(syn::parse_str(value).unwrap_or_else(|err|
panic!("Could not parse `display` attribute of member {} as an expression - {}", variant_ident, err)))),
"cause" => custom_cause = Some(syn::parse_str(value).unwrap_or_else(|err|
panic!("Could not parse `cause` attribute of member {} as an expression - {}", variant_ident, err))),
_ => panic!(
"Could not parse `error_chain` attribute of member {} - expected one of `link`, `description`, `display`, `cause` but got {}",
variant_ident, ident),
}
},
_ => panic!("Could not parse `error_chain` attribute of member {} - expected term or name-value meta item", variant_ident),
}
}
}
else {
let mut tts = {
let mut tts = attr.tts.into_iter();
let tt = match tts.next() {
Some(proc_macro2::TokenTree::Group(ref group)) if group.delimiter() == proc_macro2::Delimiter::Parenthesis => group.stream(),
Some(tt) => panic!("Could not parse `error_chain` attribute of member {} - expected `(tokens)` but found {}", variant_ident, tt),
None => panic!("Could not parse `error_chain` attribute of member {} - expected `(tokens)`", variant_ident),
};
if let Some(tt) = tts.next() {
panic!("Could not parse `error_chain` attribute of member {} - unexpected token {} after `(tokens)`", variant_ident, tt);
}
tt.into_iter()
};
let ident = match tts.next() {
Some(proc_macro2::TokenTree::Ident(ident)) => ident,
Some(tt) => panic!("Could not parse `error_chain` attribute of member {} - expected a term but got {}", variant_ident, tt),
None => panic!("Could not parse `error_chain` attribute of member {} - expected a term", variant_ident),
};
let ident = ident.to_string();
match tts.next() {
Some(proc_macro2::TokenTree::Punct(ref punct)) if punct.as_char() == '=' => (),
Some(tt) => panic!("Could not parse `error_chain` attribute of member {} - expected `=` but got {}", variant_ident, tt),
None => panic!("Could not parse `error_chain` attribute of member {} - expected `=`", variant_ident),
}
let value: proc_macro2::TokenStream = tts.collect();
if value.is_empty() {
panic!("Could not parse `error_chain` attribute of member {} - expected tokens after `=`", variant_ident);
}
match &*ident {
"link" => match variant_fields {
syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) if unnamed.len() == 1 =>
link_type = Some(LinkType::Chainable(
syn::parse2(value).unwrap_or_else(|err|
panic!("Could not parse `link` attribute of member {} as a type - {}", variant_ident, err)),
unnamed[0].ty.clone())),
_ => panic!("Chainable link {} must be a tuple of one element (the chainable error kind).", variant_ident),
},
"description" => custom_description = Some(CustomFormatter::parse(value, "description", &variant_ident, &variant_fields)),
"display" => custom_display = Some(CustomFormatter::parse(value, "display", &variant_ident, &variant_fields)),
"cause" => custom_cause = Some(syn::parse2(value).unwrap_or_else(|err|
panic!("Could not parse `cause` attribute of member {} as an expression - {}", variant_ident, err))),
_ => panic!(
"Could not parse `error_chain` attribute of member {} - expected one of `link`, `description`, `display`, `cause` but got {}",
variant_ident, ident),
}
}
}
let link_type = link_type.unwrap_or_else(||
panic!(r#"Member {} does not have any of #[error_chain(link = "...")] or #[error_chain(foreign)] or #[error_chain(custom)]."#, variant_ident));
Link {
variant_ident,
variant_fields,
link_type,
custom_description,
custom_display,
custom_cause,
}
}
}
impl Link {
fn error_kind_description(&self, error_kind_name: &proc_macro2::Ident) -> proc_macro2::TokenStream {
let variant_ident = &self.variant_ident;
match (self.custom_description.as_ref(), &self.link_type) {
(_, &LinkType::Msg) => quote! {
#error_kind_name::#variant_ident(ref s) => s,
},
(Some(&CustomFormatter::FormatString { ref format_string, .. }), &LinkType::Chainable(_, _)) |
(Some(&CustomFormatter::FormatString { ref format_string, .. }), &LinkType::Foreign(_)) => quote! {
#error_kind_name::#variant_ident(_) => #format_string,
},
(Some(&CustomFormatter::Expr(ref custom_description)), &LinkType::Chainable(_, _)) |
(Some(&CustomFormatter::Expr(ref custom_description)), &LinkType::Foreign(_)) if is_closure(custom_description) => quote! {
#error_kind_name::#variant_ident(ref err) => {
#[cfg_attr(feature = "cargo-clippy", allow(redundant_closure_call))]
{ (#custom_description)(err) }
},
},
(Some(&CustomFormatter::Expr(ref custom_description)), &LinkType::Chainable(_, _)) |
(Some(&CustomFormatter::Expr(ref custom_description)), &LinkType::Foreign(_)) => quote! {
#error_kind_name::#variant_ident(ref err) => #custom_description(err),
},
(Some(&CustomFormatter::FormatString { ref format_string, .. }), &LinkType::Custom) => {
let pattern = fields_pattern_ignore(&self.variant_fields);
quote! {
#error_kind_name::#variant_ident #pattern => #format_string,
}
},
(Some(&CustomFormatter::Expr(ref custom_description)), &LinkType::Custom) => {
let pattern = fields_pattern(&self.variant_fields);
let args = args(&self.variant_fields);
if is_closure(custom_description) {
quote! {
#error_kind_name::#variant_ident #pattern => {
#[cfg_attr(feature = "cargo-clippy", allow(redundant_closure_call))]
{ (#custom_description)(#args) }
},
}
}
else {
quote! {
#error_kind_name::#variant_ident #pattern => #custom_description(#args),
}
}
},
(None, &LinkType::Chainable(_, _)) => quote! {
#error_kind_name::#variant_ident(ref kind) => kind.description(),
},
(None, &LinkType::Foreign(_)) => quote! {
#error_kind_name::#variant_ident(ref err) => ::std::error::Error::description(err),
},
(None, &LinkType::Custom) => {
let pattern = fields_pattern_ignore(&self.variant_fields);
quote! {
#error_kind_name::#variant_ident #pattern => stringify!(#variant_ident),
}
},
}
}
fn error_kind_display_case(
&self,
error_kind_name: &proc_macro2::Ident,
) -> proc_macro2::TokenStream {
let variant_ident = &self.variant_ident;
match (self.custom_display.as_ref(), &self.link_type) {
(_, &LinkType::Msg) => quote! {
#error_kind_name::#variant_ident(ref s) => ::std::fmt::Display::fmt(s, f),
},
(Some(&CustomFormatter::FormatString { ref format_string, ref pattern, ref args }), &LinkType::Chainable(_, _)) => quote! {
#error_kind_name::#variant_ident #pattern => write!(f, #format_string, #args),
},
(Some(&CustomFormatter::Expr(ref custom_display)), &LinkType::Chainable(_, _)) if is_closure(custom_display) => quote! {
#error_kind_name::#variant_ident(ref kind) => {
#[cfg_attr(feature = "cargo-clippy", allow(redundant_closure_call))]
{ (#custom_display)(kind) }
},
},
(Some(&CustomFormatter::Expr(ref custom_display)), &LinkType::Chainable(_, _)) => quote! {
#error_kind_name::#variant_ident(ref kind) => #custom_display(f, kind),
},
(Some(&CustomFormatter::FormatString { ref format_string, ref pattern, ref args }), &LinkType::Foreign(_)) => quote! {
#error_kind_name::#variant_ident #pattern => write!(f, #format_string, #args),
},
(Some(&CustomFormatter::Expr(ref custom_display)), &LinkType::Foreign(_)) if is_closure(custom_display) => quote! {
#error_kind_name::#variant_ident(ref err) => {
#[cfg_attr(feature = "cargo-clippy", allow(redundant_closure_call))]
{ (#custom_display)(err) }
},
},
(Some(&CustomFormatter::Expr(ref custom_display)), &LinkType::Foreign(_)) => quote! {
#error_kind_name::#variant_ident(ref err) => #custom_display(f, err),
},
(Some(&CustomFormatter::FormatString { ref format_string, ref pattern, ref args }), &LinkType::Custom) => quote! {
#error_kind_name::#variant_ident #pattern => write!(f, #format_string, #args),
},
(Some(&CustomFormatter::Expr(ref custom_display)), &LinkType::Custom) => {
let pattern = fields_pattern(&self.variant_fields);
let args = args(&self.variant_fields);
if is_closure(custom_display) {
quote! {
#error_kind_name::#variant_ident #pattern => {
#[cfg_attr(feature = "cargo-clippy", allow(redundant_closure_call))]
{ (#custom_display)(#args) }
},
}
}
else {
quote! {
#error_kind_name::#variant_ident #pattern => #custom_display(f, #args),
}
}
},
(None, &LinkType::Chainable(_, _)) => quote! {
#error_kind_name::#variant_ident(ref kind) => ::std::fmt::Display::fmt(kind, f),
},
(None, &LinkType::Foreign(_)) => quote! {
#error_kind_name::#variant_ident(ref err) => ::std::fmt::Display::fmt(err, f),
},
(None, &LinkType::Custom) => {
let pattern = fields_pattern_ignore(&self.variant_fields);
quote! {
#error_kind_name::#variant_ident #pattern => ::std::fmt::Display::fmt(self.description(), f),
}
},
}
}
fn error_kind_from_impl(
&self,
error_kind_name: &proc_macro2::Ident,
impl_generics: &syn::ImplGenerics, impl_generics_lifetime: &syn::ImplGenerics, ty_generics: &syn::TypeGenerics, where_clause: Option<&syn::WhereClause>,
) -> Option<proc_macro2::TokenStream> {
let variant_ident = &self.variant_ident;
match self.link_type {
LinkType::Msg => Some(quote! {
impl #impl_generics_lifetime From<&'__a str> for #error_kind_name #ty_generics #where_clause {
fn from(s: &'__a str) -> Self { #error_kind_name::#variant_ident(s.to_string()) }
}
impl #impl_generics From<String> for #error_kind_name #ty_generics #where_clause {
fn from(s: String) -> Self { #error_kind_name::#variant_ident(s) }
}
}),
LinkType::Chainable(_, ref error_kind_ty) => Some(quote! {
impl #impl_generics From<#error_kind_ty> for #error_kind_name #ty_generics #where_clause {
fn from(kind: #error_kind_ty) -> Self {
#error_kind_name::#variant_ident(kind)
}
}
}),
LinkType::Foreign(_) |
LinkType::Custom => None,
}
}
fn error_cause_case(
&self,
error_kind_name: &proc_macro2::Ident,
) -> Option<proc_macro2::TokenStream> {
let variant_ident = &self.variant_ident;
#[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))]
match (self.custom_cause.as_ref(), &self.link_type) {
(_, &LinkType::Msg) => None,
(Some(custom_cause), _) => Some({
let pattern = fields_pattern(&self.variant_fields);
let args = args(&self.variant_fields);
if is_closure(custom_cause) {
quote! {
#error_kind_name::#variant_ident #pattern => {
#[cfg_attr(feature = "cargo-clippy", allow(redundant_closure_call))]
let result = (#custom_cause)(#args);
Some(result)
},
}
}
else {
quote! {
#error_kind_name::#variant_ident #pattern => Some(#custom_cause(#args)),
}
}
}),
(None, &LinkType::Foreign(_)) => Some(quote! {
#error_kind_name::#variant_ident(ref err) => ::std::error::Error::cause(err),
}),
(None, &LinkType::Chainable(_, _)) |
(None, &LinkType::Custom) => None,
}
}
fn error_from_impl(
&self,
error_kind_name: &proc_macro2::Ident, error_name: &proc_macro2::Ident,
generics: &std::collections::HashSet<&proc_macro2::Ident>,
impl_generics: &syn::ImplGenerics, impl_generics_lifetime: &syn::ImplGenerics, ty_generics: &syn::TypeGenerics, where_clause: Option<&syn::WhereClause>,
) -> Option<proc_macro2::TokenStream> {
let variant_ident = &self.variant_ident;
match self.link_type {
LinkType::Msg => Some(quote! {
impl #impl_generics_lifetime From<&'__a str> for #error_name #ty_generics #where_clause {
fn from(s: &'__a str) -> Self { Self::from_kind(s.into()) }
}
impl #impl_generics From<String> for #error_name #ty_generics #where_clause {
fn from(s: String) -> Self { Self::from_kind(s.into()) }
}
}),
LinkType::Chainable(ref error_ty, _) => Some(quote! {
impl #impl_generics From<#error_ty> for #error_name #ty_generics #where_clause {
fn from(err: #error_ty) -> Self {
#error_name(#error_kind_name::#variant_ident(err.0), err.1)
}
}
}),
LinkType::Foreign(syn::Type::Path(syn::TypePath { ref path, .. }))
if !path.global() && path.segments.len() == 1 && generics.contains(&path.segments[0].ident) => None,
LinkType::Foreign(ref ty) => Some(quote! {
impl #impl_generics From<#ty> for #error_name #ty_generics #where_clause {
fn from(err: #ty) -> Self {
Self::from_kind(#error_kind_name::#variant_ident(err))
}
}
}),
LinkType::Custom => None,
}
}
fn chained_error_extract_backtrace_case(&self) -> Option<proc_macro2::TokenStream> {
match self.link_type {
LinkType::Chainable(ref error_ty, _) => Some(quote! {
if let Some(err) = err.downcast_ref::<#error_ty>() {
return err.1.backtrace.clone();
}
}),
LinkType::Msg |
LinkType::Foreign(_) |
LinkType::Custom => None,
}
}
}
enum CustomFormatter {
FormatString { format_string: String, pattern: proc_macro2::TokenStream, args: proc_macro2::TokenStream },
Expr(syn::Expr),
}
impl CustomFormatter {
fn parse(tokens: proc_macro2::TokenStream, attr_name: &str, variant_ident: &proc_macro2::Ident, variant_fields: &syn::Fields) -> Self {
let err = match syn::parse(tokens.clone().into()) {
Ok(expr) => return CustomFormatter::Expr(expr),
Err(err) => err,
};
let mut tts = tokens.into_iter();
match tts.next() {
Some(proc_macro2::TokenTree::Ident(ref ident)) if ident == "const" => (),
Some(tt) => panic!(
"Could not parse `{}` attribute of member {}. Expression - {}. Format string - expected `const` but got {}",
attr_name, variant_ident, err, tt),
_ => panic!(
"Could not parse `{}` attribute of member {}. Expression - {}. Format string - expected `const`",
attr_name, variant_ident, err),
}
let value = match tts.next() {
Some(proc_macro2::TokenTree::Group(ref group)) if group.delimiter() == proc_macro2::Delimiter::Parenthesis => group.stream(),
Some(tt) => panic!(
"Could not parse `{}` attribute of member {} - expected `(string literal)` but got {}",
attr_name, variant_ident, tt),
_ => panic!(
"Could not parse `{}` attribute of member {} - expected `(string literal)`",
attr_name, variant_ident),
};
let format_string = match syn::parse2(value) {
Ok(syn::Lit::Str(value)) => value.value(),
Ok(lit) => panic!(
"Could not parse `{}` attribute of member {} - expected string literal but got {}",
attr_name, variant_ident, quote!(#lit).to_string()),
Err(err) => panic!(
"Could not parse `{}` attribute of member {} - {}",
attr_name, variant_ident, err),
};
if let Some(tt) = tts.next() {
panic!(
"Could not parse `{}` attribute of member {} - unexpected token {} after string literal",
attr_name, variant_ident, tt);
}
match *variant_fields {
syn::Fields::Named(syn::FieldsNamed { ref named, .. }) => {
let referenced_names = get_parameter_names(&format_string).unwrap_or_else(|err| panic!(
"Could not parse `{}` attribute of member {} - {}",
attr_name, variant_ident, err));
let (patterns, args): (Vec<_>, Vec<_>) = named.into_iter().map(|f| {
let field_name = f.ident.as_ref().unwrap();
if referenced_names.contains(field_name) {
(quote!(ref #field_name), quote!(#field_name = #field_name,))
}
else {
let ignored_field_name = proc_macro2::Ident::new(&format!("_{}", field_name), proc_macro2::Span::call_site());
(quote!(#field_name: ref #ignored_field_name), quote!())
}
}).unzip();
CustomFormatter::FormatString {
format_string,
pattern: quote!({ #(#patterns,)* }),
args: quote!(#(#args)*),
}
},
syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) => {
let referenced_positions = get_parameter_positions(&format_string).unwrap_or_else(|err| panic!(
"Could not parse `{}` attribute of member {} - {}",
attr_name, variant_ident, err));
let (patterns, args): (Vec<_>, Vec<_>) = unnamed.into_iter().enumerate().map(|(i, _)| {
if referenced_positions.contains(&i) {
let field_name = proc_macro2::Ident::new(&format!("value{}", i), proc_macro2::Span::call_site());
(quote!(ref #field_name), quote!(#field_name,))
}
else {
(quote!(_), quote!())
}
}).unzip();
CustomFormatter::FormatString {
format_string,
pattern: quote!((#(#patterns,)*)),
args: quote!(#(#args)*),
}
},
syn::Fields::Unit => {
ensure_no_parameters(&format_string).unwrap_or_else(|err| panic!(
"Could not parse `{}` attribute of member {} - {}",
attr_name, variant_ident, err));
CustomFormatter::FormatString {
format_string,
pattern: quote!(),
args: quote!(),
}
},
}
}
}
fn is_error_chain_attribute(attr: &syn::Attribute) -> bool {
if !attr.path.global() && attr.path.segments.len() == 1 {
let segment = &attr.path.segments[0];
return segment.ident == "error_chain" && segment.arguments.is_empty();
}
false
}
fn is_closure(expr: &syn::Expr) -> bool {
if let syn::Expr::Closure(..) = *expr {
true
}
else {
false
}
}
fn fields_pattern(variant_fields: &syn::Fields) -> proc_macro2::TokenStream {
match *variant_fields {
syn::Fields::Named(syn::FieldsNamed { ref named, .. }) => {
let fields = named.into_iter().map(|f| {
let field_name = f.ident.as_ref().unwrap();
quote!(ref #field_name)
});
quote!({ #(#fields,)* })
},
syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) => {
let fields = unnamed.into_iter().enumerate().map(|(i, _)| {
let field_name = proc_macro2::Ident::new(&format!("value{}", i), proc_macro2::Span::call_site());
quote!(ref #field_name)
});
quote!((#(#fields,)*))
},
syn::Fields::Unit => quote!(),
}
}
fn fields_pattern_ignore(variant_fields: &syn::Fields) -> proc_macro2::TokenStream {
match *variant_fields {
syn::Fields::Named(syn::FieldsNamed { .. }) => quote!({ .. }),
syn::Fields::Unnamed(_) => quote!((..)),
syn::Fields::Unit => quote!(),
}
}
fn args(variant_fields: &syn::Fields) -> proc_macro2::TokenStream {
match *variant_fields {
syn::Fields::Named(syn::FieldsNamed { ref named, .. }) => {
let fields = named.into_iter().map(|f| {
let field_name = f.ident.as_ref().unwrap();
quote!(#field_name)
});
quote!(#(#fields,)*)
},
syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) => {
let fields = unnamed.into_iter().enumerate().map(|(i, _)| {
let field_name = proc_macro2::Ident::new(&format!("value{}", i), proc_macro2::Span::call_site());
quote!(#field_name)
});
quote!(#(#fields,)*)
},
syn::Fields::Unit => quote!(),
}
}
fn get_parameter_names(format_string: &str) -> Result<std::collections::HashSet<proc_macro2::Ident>, String> {
let parser = syntex_fmt_macros::Parser::new(format_string);
parser
.filter_map(|piece| match piece {
syntex_fmt_macros::Piece::String(_) => None,
syntex_fmt_macros::Piece::NextArgument(syntex_fmt_macros::Argument { position, .. }) => match position {
syntex_fmt_macros::Position::ArgumentNext => Some(Err("expected named parameter but found `{}`".to_string())),
syntex_fmt_macros::Position::ArgumentIs(index) => Some(Err(format!("expected named parameter but found `{{{}}}`", index))),
syntex_fmt_macros::Position::ArgumentNamed(name) => Some(syn::parse_str(name).map_err(|err| format!("could not parse named parameter `{{{}}}` - {}", name, err))),
},
})
.collect()
}
fn get_parameter_positions(format_string: &str) -> Result<std::collections::HashSet<usize>, String> {
let parser = syntex_fmt_macros::Parser::new(format_string);
parser
.filter_map(|piece| match piece {
syntex_fmt_macros::Piece::String(_) => None,
syntex_fmt_macros::Piece::NextArgument(syntex_fmt_macros::Argument { position, .. }) => match position {
syntex_fmt_macros::Position::ArgumentNext => Some(Err("expected positional parameter but found `{}`".to_string())),
syntex_fmt_macros::Position::ArgumentIs(index) => Some(Ok(index)),
syntex_fmt_macros::Position::ArgumentNamed(name) => Some(Err(format!("expected positional parameter but found `{{{}}}`", name))),
},
})
.collect()
}
fn ensure_no_parameters(format_string: &str) -> Result<(), String> {
let parser = syntex_fmt_macros::Parser::new(format_string);
for piece in parser {
match piece {
syntex_fmt_macros::Piece::String(_) => (),
syntex_fmt_macros::Piece::NextArgument(syntex_fmt_macros::Argument { position, .. }) => match position {
syntex_fmt_macros::Position::ArgumentNext => return Err("expected no parameters but found `{}`".to_string()),
syntex_fmt_macros::Position::ArgumentIs(index) => return Err(format!("expected no parameters but found `{{{}}}`", index)),
syntex_fmt_macros::Position::ArgumentNamed(name) => return Err(format!("expected no parameters but found `{{{}}}`", name)),
},
}
}
Ok(())
}