Skip to main content

macron_impl_error/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use proc_macro::TokenStream;
4use quote::quote;
5
6/// The implementation of trait [Error](std::error::Error)
7#[proc_macro_derive(Error, attributes(source))]
8pub fn impl_error(input: TokenStream) -> TokenStream {
9    let syn::DeriveInput { ident, data, .. } = syn::parse_macro_input!(input as syn::DeriveInput);
10    
11    match data {
12        syn::Data::Struct(st) => {
13            let source = match st.fields {
14                syn::Fields::Named(fields) => {
15                    let src_field = fields.named
16                        .iter()
17                        .find(|f| f.ident
18                            .as_ref()
19                            .map(|i| i == "source")
20                            .unwrap_or(false)
21                        );
22            
23                    match src_field {
24                        Some(field) if is_option_type(&field.ty) => quote! { self.source.as_deref().map(|e| e as &(dyn ::std::error::Error + 'static)) },
25                        Some(_) => quote! { Some(&*self.source) },
26                        None => quote! { None }
27                    }
28                },
29
30                _ => quote! { None }
31            };
32            
33            quote! {
34                impl ::std::error::Error for #ident {
35                    fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> {
36                        #source
37                    }
38                }
39            }.into()
40        },
41
42        syn::Data::Enum(en) => {
43            let vars = en.variants
44                .into_iter()
45                .map(|syn::Variant { ident: var_ident, fields, .. }| {
46                    match fields {
47                        syn::Fields::Named(fields) => {
48                            let src_field = fields.named
49                                .iter()
50                                .find(|f| f.ident
51                                    .as_ref()
52                                    .map(|i| i == "source")
53                                    .unwrap_or(false)
54                                );
55                    
56                            match src_field {
57                                Some(field) if is_option_type(&field.ty) => quote! { Self::#var_ident { source, .. } => source.as_deref().map(|e| e as &(dyn ::std::error::Error + 'static)) },
58                                Some(_) => quote! { Self::#var_ident { source, .. } => Some(&**source) }, // double deref для ссылки на Box в паттерн-матчинге
59                                None => quote! { Self::#var_ident { .. } => None }
60                            }
61                        },
62
63                        syn::Fields::Unnamed(fields) => {
64                             let src_field = fields.unnamed
65                                .iter()
66                                .enumerate()
67                                .find(|(_, field)| field.attrs
68                                    .iter()
69                                    .any(|attr| attr.path().is_ident("source"))
70                                );
71
72                            let src_idx = src_field.map(|(idx, _)| idx).unwrap_or(0);
73                            let stubs = (0..src_idx).into_iter().map(|_| quote! { _ });
74
75                            match src_field {
76                                Some((_, field)) if is_option_type(&field.ty) => quote! { Self::#var_ident(#(#stubs,)* source, ..) => source.as_deref().map(|e| e as &(dyn ::std::error::Error + 'static)) },
77                                Some(_) => quote! { Self::#var_ident(#(#stubs,)* source, ..) => Some(&**source) }, // double deref здесь, т.к. source в match — это &Box или &Type
78                                None => quote! { Self::#var_ident(..) => None }
79                            }
80                        },
81
82                        syn::Fields::Unit => quote! { Self::#var_ident => None }
83                    }
84                });
85            
86            quote! {
87                impl ::std::error::Error for #ident {
88                    fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> {
89                        match self {
90                            #(
91                                #vars,
92                            )*
93                        }
94                    }
95                }
96            }.into()
97        },
98
99        _ => panic!("the expected a 'struct' or 'enum'")
100    }
101}
102
103fn is_option_type(ty: &syn::Type) -> bool {
104    if let syn::Type::Path(type_path) = ty {
105        type_path.path.segments.last().map_or(false, |seg| seg.ident == "Option")
106    } else {
107        false
108    }
109}