common_macro/
stack_trace_debug.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! implement `::common_error::ext::StackError`
16
17use 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
54/// Generate `debug_fmt` fn.
55///
56/// The generated fn will be like:
57/// ```rust, ignore
58/// fn debug_fmt(&self, layer: usize, buf: &mut Vec<String>);
59/// ```
60fn 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
76/// Generate `next` fn.
77///
78/// The generated fn will be like:
79/// ```rust, ignore
80/// fn next(&self) -> Option<&dyn ::common_error::ext::StackError>;
81/// ```
82fn 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
98/// Implement [std::fmt::Debug] via `debug_fmt`
99fn 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    /// Construct self from [Variant]
127    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    /// Convert self into an match arm that will be used in [build_debug_impl].
201    ///
202    /// The generated match arm will be like:
203    /// ```rust, ignore
204    ///     ErrorKindWithSource { source, .. } => {
205    ///         debug_fmt(source, layer + 1, buf);
206    ///     },
207    ///     ErrorKindWithoutSource { .. } => {
208    ///        buf.push(format!("{layer}: {}, at {}", format!(#display), location)));
209    ///     }
210    /// ```
211    ///
212    /// The generated code assumes fn `debug_fmt`, var `layer`, var `buf` are in scope.
213    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    /// Convert self into an match arm that will be used in [build_next_impl].
262    ///
263    /// The generated match arm will be like:
264    /// ```rust, ignore
265    ///     ErrorKindWithSource { source, .. } => {
266    ///         Some(source)
267    ///     },
268    ///     ErrorKindWithoutSource { .. } => {
269    ///        None
270    ///     }
271    /// ```
272    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}