tamanegi_error_impl/
lib.rs1use 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 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 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 let mut variants = EnumVariants::new();
141
142 for v in input.iter() {
146 if match &v.fields {
147 Fields::Named(FieldsNamed { named, .. }) => has_ident(named, "location"),
148 _ => {
149 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 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 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}