1use proc_macro2::{Literal, Span, TokenStream as TokenStream2, TokenTree};
18use quote::{quote, quote_spanned};
19use syn::spanned::Spanned;
20use syn::{parenthesized, Attribute, Ident, ItemEnum, Variant};
21
22pub fn stack_trace_style_impl(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
23 let input_cloned: TokenStream2 = input.clone();
24
25 let error_enum_definition: ItemEnum = syn::parse2(input_cloned).unwrap();
26 let enum_name = error_enum_definition.ident;
27
28 let mut variants = vec![];
29
30 for error_variant in error_enum_definition.variants {
31 let variant = ErrorVariant::from_enum_variant(error_variant);
32 variants.push(variant);
33 }
34
35 let transparent_fn = build_transparent_fn(enum_name.clone(), &variants);
36 let debug_fmt_fn = build_debug_fmt_impl(enum_name.clone(), variants.clone());
37 let next_fn = build_next_impl(enum_name.clone(), variants);
38 let debug_impl = build_debug_impl(enum_name.clone());
39
40 quote! {
41 #args
42 #input
43
44 impl ::common_error::ext::StackError for #enum_name {
45 #debug_fmt_fn
46 #next_fn
47 #transparent_fn
48 }
49
50 #debug_impl
51 }
52}
53
54fn build_debug_fmt_impl(enum_name: Ident, variants: Vec<ErrorVariant>) -> TokenStream2 {
61 let match_arms = variants
62 .iter()
63 .map(|v| v.to_debug_match_arm())
64 .collect::<Vec<_>>();
65
66 quote! {
67 fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>) {
68 use #enum_name::*;
69 match self {
70 #(#match_arms)*
71 }
72 }
73 }
74}
75
76fn build_next_impl(enum_name: Ident, variants: Vec<ErrorVariant>) -> TokenStream2 {
83 let match_arms = variants
84 .iter()
85 .map(|v| v.to_next_match_arm())
86 .collect::<Vec<_>>();
87
88 quote! {
89 fn next(&self) -> Option<&dyn ::common_error::ext::StackError> {
90 use #enum_name::*;
91 match self {
92 #(#match_arms)*
93 }
94 }
95 }
96}
97
98fn build_debug_impl(enum_name: Ident) -> TokenStream2 {
100 quote! {
101 impl std::fmt::Debug for #enum_name {
102 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103 use ::common_error::ext::StackError;
104 let mut buf = vec![];
105 self.debug_fmt(0, &mut buf);
106 write!(f, "{}", buf.join("\n"))
107 }
108 }
109 }
110}
111
112#[derive(Clone, Debug)]
113struct ErrorVariant {
114 name: Ident,
115 fields: Vec<Ident>,
116 has_location: bool,
117 has_source: bool,
118 has_external_cause: bool,
119 display: TokenStream2,
120 transparent: bool,
121 span: Span,
122 cfg_attr: Option<Attribute>,
123}
124
125impl ErrorVariant {
126 fn from_enum_variant(variant: Variant) -> Self {
128 let span = variant.span();
129 let mut has_location = false;
130 let mut has_source = false;
131 let mut has_external_cause = false;
132
133 for field in &variant.fields {
134 if let Some(ident) = &field.ident {
135 if ident == "location" {
136 has_location = true;
137 } else if ident == "source" {
138 has_source = true;
139 } else if ident == "error" {
140 has_external_cause = true;
141 }
142 }
143 }
144
145 let mut display = None;
146 let mut transparent = false;
147 let mut cfg_attr = None;
148 for attr in variant.attrs {
149 if attr.path().is_ident("snafu") {
150 attr.parse_nested_meta(|meta| {
151 if meta.path.is_ident("display") {
152 let content;
153 parenthesized!(content in meta.input);
154 let display_ts: TokenStream2 = content.parse()?;
155 display = Some(display_ts);
156 Ok(())
157 } else if meta.path.is_ident("transparent") {
158 display = Some(TokenStream2::from(TokenTree::Literal(Literal::string(
159 "<transparent>",
160 ))));
161 transparent = true;
162 Ok(())
163 } else {
164 Err(meta.error("unrecognized repr"))
165 }
166 })
167 .unwrap_or_else(|e| panic!("{e}"));
168 }
169
170 if attr.path().is_ident("cfg") {
171 cfg_attr = Some(attr);
172 }
173 }
174 let display = display.unwrap_or_else(|| {
175 panic!(
176 r#"Error "{}" must be annotated with attribute "display" or "transparent"."#,
177 variant.ident,
178 )
179 });
180
181 let field_ident = variant
182 .fields
183 .iter()
184 .map(|f| f.ident.clone().unwrap_or_else(|| Ident::new("_", f.span())))
185 .collect();
186
187 Self {
188 name: variant.ident,
189 fields: field_ident,
190 has_location,
191 has_source,
192 has_external_cause,
193 display,
194 transparent,
195 span,
196 cfg_attr,
197 }
198 }
199
200 fn to_debug_match_arm(&self) -> TokenStream2 {
214 let name = &self.name;
215 let fields = &self.fields;
216 let display = &self.display;
217 let cfg = if let Some(cfg) = &self.cfg_attr {
218 quote_spanned!(cfg.span() => #cfg)
219 } else {
220 quote! {}
221 };
222
223 match (self.has_location, self.has_source, self.has_external_cause) {
224 (true, true, _) => quote_spanned! {
225 self.span => #cfg #[allow(unused_variables)] #name { #(#fields),*, } => {
226 buf.push(format!("{layer}: {}, at {}", format!(#display), location));
227 source.debug_fmt(layer + 1, buf);
228 },
229 },
230 (true, false, true) => quote_spanned! {
231 self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } => {
232 buf.push(format!("{layer}: {}, at {}", format!(#display), location));
233 buf.push(format!("{}: {:?}", layer + 1, error));
234 },
235 },
236 (true, false, false) => quote_spanned! {
237 self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } => {
238 buf.push(format!("{layer}: {}, at {}", format!(#display), location));
239 },
240 },
241 (false, true, _) => quote_spanned! {
242 self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } => {
243 buf.push(format!("{layer}: {}", format!(#display)));
244 source.debug_fmt(layer + 1, buf);
245 },
246 },
247 (false, false, true) => quote_spanned! {
248 self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } => {
249 buf.push(format!("{layer}: {}", format!(#display)));
250 buf.push(format!("{}: {:?}", layer + 1, error));
251 },
252 },
253 (false, false, false) => quote_spanned! {
254 self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } => {
255 buf.push(format!("{layer}: {}", format!(#display)));
256 },
257 },
258 }
259 }
260
261 fn to_next_match_arm(&self) -> TokenStream2 {
273 let name = &self.name;
274 let fields = &self.fields;
275 let cfg = if let Some(cfg) = &self.cfg_attr {
276 quote_spanned!(cfg.span() => #cfg)
277 } else {
278 quote! {}
279 };
280
281 if self.has_source {
282 quote_spanned! {
283 self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } => {
284 Some(source)
285 },
286 }
287 } else {
288 quote_spanned! {
289 self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } =>{
290 None
291 }
292 }
293 }
294 }
295
296 fn build_transparent_match_arm(&self) -> TokenStream2 {
297 let cfg = if let Some(cfg) = &self.cfg_attr {
298 quote_spanned!(cfg.span() => #cfg)
299 } else {
300 quote! {}
301 };
302 let name = &self.name;
303 let fields = &self.fields;
304
305 if self.transparent {
306 quote_spanned! {
307 self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } => {
308 true
309 },
310 }
311 } else {
312 quote_spanned! {
313 self.span => #cfg #[allow(unused_variables)] #name { #(#fields),* } =>{
314 false
315 }
316 }
317 }
318 }
319}
320
321fn build_transparent_fn(enum_name: Ident, variants: &[ErrorVariant]) -> TokenStream2 {
322 let match_arms = variants
323 .iter()
324 .map(|v| v.build_transparent_match_arm())
325 .collect::<Vec<_>>();
326
327 quote! {
328 fn transparent(&self) -> bool {
329 use #enum_name::*;
330 match self {
331 #(#match_arms)*
332 }
333 }
334 }
335}