tamanegi_error_impl/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::TokenStream as TokenStream2;
3use quote::quote;
4use std::ops::{Deref, DerefMut};
5use syn::{
6    Data, DataEnum, DataStruct, DeriveInput, Error, Fields, FieldsNamed, Result, parse_macro_input,
7    punctuated::Punctuated, spanned::Spanned,
8};
9
10const CRATE_NAME: &'static str = "tamanegi-error";
11const CRATE_USE_NAME: &'static str = "tamanegi_error";
12
13#[proc_macro_derive(TamanegiError)]
14pub fn derive(input: TokenStream) -> TokenStream {
15    let input: DeriveInput = parse_macro_input!(input as DeriveInput);
16
17    match derive_builder(input) {
18        Ok(token) => TokenStream::from(token),
19        Err(err) => TokenStream::from(err.to_compile_error()),
20    }
21}
22
23fn derive_builder(input: DeriveInput) -> Result<TokenStream2> {
24    // - fmt::Debugを作る
25    // - location, sourceメンバが存在することを確認
26    // - traitの実装確認(専用traitが必要?)
27    //      - AsErrorSourceが実装されているか確認
28    //      - Displayが実装されているか確認
29
30    let ident = input.ident.clone();
31
32    match input.data {
33        Data::Struct(DataStruct {
34            fields: Fields::Named(FieldsNamed { named, .. }),
35            ..
36        }) => struct_derive_builder(&ident, &named),
37        Data::Enum(DataEnum { variants, .. }) => enum_derive_builder(&ident, &variants),
38        _ => Err(Error::new(input.span(), "not supported")),
39    }
40}
41
42fn has_ident(fields: &Punctuated<syn::Field, syn::token::Comma>, ident: &str) -> bool {
43    fields
44        .iter()
45        .find(|f| f.ident.as_ref().is_some_and(|x| x == ident))
46        .is_some()
47}
48
49fn crate_name() -> proc_macro2::Ident {
50    let crate_name = proc_macro_crate::crate_name(CRATE_NAME).unwrap();
51    let crate_name = match crate_name {
52        proc_macro_crate::FoundCrate::Itself => {
53            proc_macro2::Ident::new(CRATE_USE_NAME, proc_macro2::Span::call_site())
54        }
55        proc_macro_crate::FoundCrate::Name(name) => {
56            proc_macro2::Ident::new(&name, proc_macro2::Span::call_site())
57        }
58    };
59    crate_name
60}
61
62fn struct_derive_builder(
63    ident: &syn::Ident,
64    input: &Punctuated<syn::Field, syn::token::Comma>,
65) -> Result<TokenStream2> {
66    if !has_ident(input, "location") {
67        return Err(syn::Error::new(
68            input.span(),
69            "location field must be exist",
70        ));
71    }
72
73    let crate_name = crate_name();
74
75    let debug_impl = quote! {
76        const _: () = {
77            extern crate #crate_name as _tamanegi_error;
78            impl _tamanegi_error::TamanegiTrait for #ident {}
79
80            impl ::core::fmt::Debug for #ident {
81                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result
82                    where Self: _tamanegi_error::TamanegiTrait
83                {
84                    use ::snafu::ErrorCompat;
85                    // show source error if exists
86                    write!(f, "{}: {}, at {:?}\n", self.iter_chain().count() - 1, self, self.location)?;
87
88                    if let Some(e) = self.iter_chain().nth(1) {
89                        ::core::fmt::Debug::fmt(e, f)?;
90                    }
91
92                    Ok(())
93                }
94            }
95        };
96    };
97
98    Ok(TokenStream2::from(debug_impl))
99}
100
101#[derive(Debug)]
102struct EnumVariant {
103    ident: syn::Ident,
104    has_location: bool,
105}
106
107#[derive(Debug)]
108struct EnumVariants(Vec<EnumVariant>);
109
110impl EnumVariants {
111    fn new() -> Self {
112        Self(Vec::new())
113    }
114}
115
116impl Deref for EnumVariants {
117    type Target = Vec<EnumVariant>;
118
119    fn deref(&self) -> &Self::Target {
120        &self.0
121    }
122}
123
124impl DerefMut for EnumVariants {
125    fn deref_mut(&mut self) -> &mut Self::Target {
126        &mut self.0
127    }
128}
129
130fn enum_derive_builder(
131    ident: &syn::Ident,
132    input: &Punctuated<syn::Variant, syn::token::Comma>,
133) -> Result<TokenStream2> {
134    // - fmt::Debugを作る
135    // - locationメンバが存在することを確認
136    // - traitの実装確認(専用traitが必要?)
137    //      - AsErrorSourceが実装されているか確認
138    //      - Displayが実装されているか確認
139
140    let mut variants = EnumVariants::new();
141
142    // enumのすべてのVariant内にlocationフィールドが存在することを確認
143    // sourceはleafの場合はないこともある
144    // TODO: syn::Parseにまとめる
145    for v in input.iter() {
146        if match &v.fields {
147            Fields::Named(FieldsNamed { named, .. }) => has_ident(named, "location"),
148            _ => {
149                // error: non member variant
150                false
151            }
152        } {
153            Ok(())
154        } else {
155            Err(syn::Error::new(
156                v.ident.span(),
157                "location field must be exist",
158            ))
159        }?;
160
161        variants.push(EnumVariant {
162            ident: v.ident.clone(),
163            has_location: true,
164        });
165    }
166
167    let debug_impl = variants.iter().map(|v| {
168        let variant_ident = v.ident.clone();
169
170        if v.has_location {
171            quote! {
172                #ident::#variant_ident { location, .. } => {
173                    write!(f, "{:?}", location)?;
174                }
175            }
176        } else {
177            quote! {
178                #ident::#variant_ident { .. } => {
179                    write!(f, "Unknown")?;
180                }
181            }
182        }
183    });
184
185    let crate_name = crate_name();
186
187    // impl Drbug
188    // TODO: support generics
189    let debug_impl = quote! {
190        const _: () = {
191            extern crate #crate_name as _tamanegi_error;
192
193            impl _tamanegi_error::TamanegiTrait for #ident {}
194
195            impl ::core::fmt::Debug for #ident {
196                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result
197                    where Self: _tamanegi_error::TamanegiTrait
198                {
199                    use ::snafu::ErrorCompat;
200                    // show source error if exists
201                    write!(f, "{}: {}, at ", self.iter_chain().count() - 1, self)?;
202
203                    match self {
204                        #(#debug_impl),*
205                    }
206
207                    write!(f, "\n")?;
208
209                    if let Some(e) = self.iter_chain().nth(1) {
210                        ::core::fmt::Debug::fmt(e, f)?;
211                    }
212
213                    Ok(())
214                }
215            }
216        };
217    };
218
219    Ok(TokenStream2::from(debug_impl))
220}