miette_derive/
forward.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4    parenthesized,
5    parse::{Parse, ParseStream},
6    spanned::Spanned,
7};
8
9pub enum Forward {
10    Unnamed(usize),
11    Named(syn::Ident),
12}
13
14impl Parse for Forward {
15    fn parse(input: ParseStream) -> syn::Result<Self> {
16        let forward = input.parse::<syn::Ident>()?;
17        if forward != "forward" {
18            return Err(syn::Error::new(forward.span(), "msg"));
19        }
20        let content;
21        parenthesized!(content in input);
22        let looky = content.lookahead1();
23        if looky.peek(syn::LitInt) {
24            let int: syn::LitInt = content.parse()?;
25            let index = int.base10_parse()?;
26            return Ok(Forward::Unnamed(index));
27        }
28        Ok(Forward::Named(content.parse()?))
29    }
30}
31
32#[derive(Copy, Clone)]
33pub enum WhichFn {
34    Code,
35    Help,
36    Url,
37    Severity,
38    Labels,
39    SourceCode,
40    Related,
41    DiagnosticSource,
42}
43
44impl WhichFn {
45    pub fn method_call(&self) -> TokenStream {
46        match self {
47            Self::Code => quote! { code() },
48            Self::Help => quote! { help() },
49            Self::Url => quote! { url() },
50            Self::Severity => quote! { severity() },
51            Self::Labels => quote! { labels() },
52            Self::SourceCode => quote! { source_code() },
53            Self::Related => quote! { related() },
54            Self::DiagnosticSource => quote! { diagnostic_source() },
55        }
56    }
57
58    pub fn signature(&self) -> TokenStream {
59        match self {
60            Self::Code => quote! {
61                fn code(& self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>>
62            },
63            Self::Help => quote! {
64                fn help(& self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>>
65            },
66            Self::Url => quote! {
67                fn url(& self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>>
68            },
69            Self::Severity => quote! {
70                fn severity(&self) -> std::option::Option<miette::Severity>
71            },
72            Self::Related => quote! {
73                fn related(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = &dyn miette::Diagnostic> + '_>>
74            },
75            Self::Labels => quote! {
76                fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>>
77            },
78            Self::SourceCode => quote! {
79                fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode>
80            },
81            Self::DiagnosticSource => quote! {
82                fn diagnostic_source(&self) -> std::option::Option<&dyn miette::Diagnostic>
83            },
84        }
85    }
86
87    pub fn catchall_arm(&self) -> TokenStream {
88        quote! { _ => std::option::Option::None }
89    }
90}
91
92impl Forward {
93    pub fn for_transparent_field(fields: &syn::Fields) -> syn::Result<Self> {
94        let make_err = || {
95            syn::Error::new(
96                fields.span(),
97                "you can only use #[diagnostic(transparent)] with exactly one field",
98            )
99        };
100        match fields {
101            syn::Fields::Named(named) => {
102                let mut iter = named.named.iter();
103                let field = iter.next().ok_or_else(make_err)?;
104                if iter.next().is_some() {
105                    return Err(make_err());
106                }
107                let field_name = field
108                    .ident
109                    .clone()
110                    .unwrap_or_else(|| format_ident!("unnamed"));
111                Ok(Self::Named(field_name))
112            }
113            syn::Fields::Unnamed(unnamed) => {
114                if unnamed.unnamed.iter().len() != 1 {
115                    return Err(make_err());
116                }
117                Ok(Self::Unnamed(0))
118            }
119            _ => Err(syn::Error::new(
120                fields.span(),
121                "you cannot use #[diagnostic(transparent)] with a unit struct or a unit variant",
122            )),
123        }
124    }
125
126    pub fn gen_struct_method(&self, which_fn: WhichFn) -> TokenStream {
127        let signature = which_fn.signature();
128        let method_call = which_fn.method_call();
129
130        let field_name = match self {
131            Forward::Named(field_name) => quote!(#field_name),
132            Forward::Unnamed(index) => {
133                let index = syn::Index::from(*index);
134                quote!(#index)
135            }
136        };
137
138        quote! {
139            #[inline]
140            #signature {
141                self.#field_name.#method_call
142            }
143        }
144    }
145
146    pub fn gen_enum_match_arm(&self, variant: &syn::Ident, which_fn: WhichFn) -> TokenStream {
147        let method_call = which_fn.method_call();
148        match self {
149            Forward::Named(field_name) => quote! {
150                Self::#variant { #field_name, .. } => #field_name.#method_call,
151            },
152            Forward::Unnamed(index) => {
153                let underscores: Vec<_> = core::iter::repeat(quote! { _, }).take(*index).collect();
154                let unnamed = format_ident!("unnamed");
155                quote! {
156                    Self::#variant ( #(#underscores)* #unnamed, .. ) => #unnamed.#method_call,
157                }
158            }
159        }
160    }
161}