miette_derive/
source_code.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use syn::spanned::Spanned;
4
5use crate::{
6    diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
7    forward::WhichFn,
8    utils::{display_pat_members, gen_all_variants_with},
9};
10
11pub struct SourceCode {
12    source_code: syn::Member,
13    is_option: bool,
14}
15
16impl SourceCode {
17    pub fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
18        match fields {
19            syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
20            syn::Fields::Unnamed(unnamed) => {
21                Self::from_fields_vec(unnamed.unnamed.iter().collect())
22            }
23            syn::Fields::Unit => Ok(None),
24        }
25    }
26
27    fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
28        for (i, field) in fields.iter().enumerate() {
29            for attr in &field.attrs {
30                if attr.path().is_ident("source_code") {
31                    let is_option = if let syn::Type::Path(syn::TypePath {
32                        path: syn::Path { segments, .. },
33                        ..
34                    }) = &field.ty
35                    {
36                        segments
37                            .last()
38                            .map(|seg| seg.ident == "Option")
39                            .unwrap_or(false)
40                    } else {
41                        false
42                    };
43
44                    let source_code = if let Some(ident) = field.ident.clone() {
45                        syn::Member::Named(ident)
46                    } else {
47                        syn::Member::Unnamed(syn::Index {
48                            index: i as u32,
49                            span: field.span(),
50                        })
51                    };
52                    return Ok(Some(SourceCode {
53                        source_code,
54                        is_option,
55                    }));
56                }
57            }
58        }
59        Ok(None)
60    }
61
62    pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
63        let (display_pat, _display_members) = display_pat_members(fields);
64        let src = &self.source_code;
65        let ret = if self.is_option {
66            quote! {
67                self.#src.as_ref().map(|s| s as _)
68            }
69        } else {
70            quote! {
71                Some(&self.#src)
72            }
73        };
74
75        Some(quote! {
76            #[allow(unused_variables)]
77            fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> {
78                let Self #display_pat = self;
79                #ret
80            }
81        })
82    }
83
84    pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
85        gen_all_variants_with(
86            variants,
87            WhichFn::SourceCode,
88            |ident, fields, DiagnosticConcreteArgs { source_code, .. }| {
89                let (display_pat, _display_members) = display_pat_members(fields);
90                source_code.as_ref().and_then(|source_code| {
91                    let field = match &source_code.source_code {
92                        syn::Member::Named(ident) => ident.clone(),
93                        syn::Member::Unnamed(syn::Index { index, .. }) => {
94                            format_ident!("_{}", index)
95                        }
96                    };
97                    let variant_name = ident.clone();
98                    let ret = if source_code.is_option {
99                        quote! {
100                            #field.as_ref().map(|s| s as _)
101                        }
102                    } else {
103                        quote! {
104                            std::option::Option::Some(#field)
105                        }
106                    };
107                    match &fields {
108                        syn::Fields::Unit => None,
109                        _ => Some(quote! {
110                            Self::#variant_name #display_pat => #ret,
111                        }),
112                    }
113                })
114            },
115        )
116    }
117}