1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//! This crate provides `TrackableError` derive macro.
//!
//! This crate should not be used directly.
//! See [trackable] documentation for the usage of `#[derive(TrackableError)]`.
//!
//! [trackable]: https://docs.rs/trackable
#![recursion_limit = "128"]
extern crate proc_macro;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;

use proc_macro::TokenStream;
use syn::DeriveInput;

#[doc(hidden)]
#[proc_macro_derive(TrackableError, attributes(trackable))]
pub fn derive_trackable_error(input: TokenStream) -> TokenStream {
    let ast = parse_macro_input!(input as DeriveInput);
    let expanded = impl_trackable_error(&ast);
    expanded.into()
}

fn impl_trackable_error(ast: &syn::DeriveInput) -> impl Into<TokenStream> {
    let error = &ast.ident;
    let error_kind = get_error_kind(&ast.attrs);
    quote! {
        impl ::std::ops::Deref for #error {
            type Target = ::trackable::error::TrackableError<#error_kind>;

            #[inline]
            fn deref(&self) -> &Self::Target {
                &self.0
            }
        }
        impl ::std::fmt::Display for #error {
            fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
                self.0.fmt(f)
            }
        }
        impl ::std::error::Error for #error {
            fn source(&self) -> Option<&(::std::error::Error + 'static)> {
                self.0.source()
            }
        }
        impl ::trackable::Trackable for #error {
            type Event = ::trackable::Location;

            #[inline]
            fn history(&self) -> Option<&::trackable::History<Self::Event>> {
                self.0.history()
            }

            #[inline]
            fn history_mut(&mut self) -> Option<&mut ::trackable::History<Self::Event>> {
                self.0.history_mut()
            }
        }
        impl From<::trackable::error::TrackableError<#error_kind>> for #error {
            #[inline]
            fn from(f: ::trackable::error::TrackableError<#error_kind>) -> Self {
                #error(f)
            }
        }
        impl From<#error> for ::trackable::error::TrackableError<#error_kind> {
            #[inline]
            fn from(f: #error) -> Self {
                f.0
            }
        }
        impl From<#error_kind> for #error {
            #[inline]
            fn from(f: #error_kind) -> Self {
                use ::trackable::error::ErrorKindExt;
                f.error().into()
            }
        }
    }
}

fn get_error_kind(attrs: &[syn::Attribute]) -> syn::Path {
    use syn::Lit::*;
    use syn::Meta::*;
    use syn::MetaNameValue;
    use syn::NestedMeta::*;

    let mut error_kind = "ErrorKind".to_owned();

    let attrs = attrs
        .iter()
        .filter_map(|attr| {
            let path = &attr.path;
            if quote!(#path).to_string() == "trackable" {
                Some(attr.parse_meta().unwrap_or_else(|e| {
                    panic!("invalid trackable syntax: {} (error={})", quote!(attr), e)
                }))
            } else {
                None
            }
        })
        .flat_map(|m| match m {
            List(l) => l.nested,
            tokens => panic!("unsupported syntax: {}", quote!(#tokens).to_string()),
        })
        .map(|m| match m {
            Meta(m) => m,
            tokens => panic!("unsupported syntax: {}", quote!(#tokens).to_string()),
        });
    for attr in attrs {
        match &attr {
            NameValue(MetaNameValue {
                path,
                lit: Str(value),
                ..
            }) if path
                .get_ident()
                .map_or(false, |ident| ident == "error_kind") =>
            {
                error_kind = value.value().to_string();
            }
            i @ List(..) | i @ Path(..) | i @ NameValue(..) => {
                panic!("unsupported option: {}", quote!(#i))
            }
        }
    }

    match syn::parse_str(&error_kind) {
        Err(e) => panic!("{:?} is not a valid type (parse error: {})", error_kind, e),
        Ok(path) => path,
    }
}