miette_derive/
help.rs

1use proc_macro2::TokenStream;
2use quote::{format_ident, quote};
3use syn::{
4    parenthesized,
5    parse::{Parse, ParseStream},
6    spanned::Spanned,
7    Fields, Token,
8};
9
10use crate::{
11    diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
12    utils::{display_pat_members, gen_all_variants_with},
13};
14use crate::{
15    fmt::{self, Display},
16    forward::WhichFn,
17};
18
19pub enum Help {
20    Display(Display),
21    Field(syn::Member, Box<syn::Type>),
22}
23
24impl Parse for Help {
25    fn parse(input: ParseStream) -> syn::Result<Self> {
26        let ident = input.parse::<syn::Ident>()?;
27        if ident == "help" {
28            let la = input.lookahead1();
29            if la.peek(syn::token::Paren) {
30                let content;
31                parenthesized!(content in input);
32                let fmt = content.parse()?;
33                let args = if content.is_empty() {
34                    TokenStream::new()
35                } else {
36                    fmt::parse_token_expr(&content, false)?
37                };
38                let display = Display {
39                    fmt,
40                    args,
41                    has_bonus_display: false,
42                };
43                Ok(Help::Display(display))
44            } else {
45                input.parse::<Token![=]>()?;
46                Ok(Help::Display(Display {
47                    fmt: input.parse()?,
48                    args: TokenStream::new(),
49                    has_bonus_display: false,
50                }))
51            }
52        } else {
53            Err(syn::Error::new(ident.span(), "not a help"))
54        }
55    }
56}
57
58impl Help {
59    pub(crate) fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
60        match fields {
61            syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
62            syn::Fields::Unnamed(unnamed) => {
63                Self::from_fields_vec(unnamed.unnamed.iter().collect())
64            }
65            syn::Fields::Unit => Ok(None),
66        }
67    }
68
69    fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
70        for (i, field) in fields.iter().enumerate() {
71            for attr in &field.attrs {
72                if attr.path().is_ident("help") {
73                    let help = if let Some(ident) = field.ident.clone() {
74                        syn::Member::Named(ident)
75                    } else {
76                        syn::Member::Unnamed(syn::Index {
77                            index: i as u32,
78                            span: field.span(),
79                        })
80                    };
81                    return Ok(Some(Help::Field(help, Box::new(field.ty.clone()))));
82                }
83            }
84        }
85        Ok(None)
86    }
87    pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
88        gen_all_variants_with(
89            variants,
90            WhichFn::Help,
91            |ident, fields, DiagnosticConcreteArgs { help, .. }| {
92                let (display_pat, display_members) = display_pat_members(fields);
93                match &help.as_ref()? {
94                    Help::Display(display) => {
95                        let (fmt, args) = display.expand_shorthand_cloned(&display_members);
96                        Some(quote! {
97                            Self::#ident #display_pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
98                        })
99                    }
100                    Help::Field(member, ty) => {
101                        let help = match &member {
102                            syn::Member::Named(ident) => ident.clone(),
103                            syn::Member::Unnamed(syn::Index { index, .. }) => {
104                                format_ident!("_{}", index)
105                            }
106                        };
107                        let var = quote! { __miette_internal_var };
108                        Some(quote! {
109                            Self::#ident #display_pat => {
110                                use miette::macro_helpers::ToOption;
111                                miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&#help).as_ref().map(|#var| -> std::boxed::Box<dyn std::fmt::Display + '_> { std::boxed::Box::new(format!("{}", #var)) })
112                            },
113                        })
114                    }
115                }
116            },
117        )
118    }
119
120    pub(crate) fn gen_struct(&self, fields: &Fields) -> Option<TokenStream> {
121        let (display_pat, display_members) = display_pat_members(fields);
122        match self {
123            Help::Display(display) => {
124                let (fmt, args) = display.expand_shorthand_cloned(&display_members);
125                Some(quote! {
126                    fn help(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> {
127                        #[allow(unused_variables, deprecated)]
128                        let Self #display_pat = self;
129                        std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
130                    }
131                })
132            }
133            Help::Field(member, ty) => {
134                let var = quote! { __miette_internal_var };
135                Some(quote! {
136                    fn help(&self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>> {
137                        #[allow(unused_variables, deprecated)]
138                        let Self #display_pat = self;
139                        use miette::macro_helpers::ToOption;
140                        miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#member).as_ref().map(|#var| -> std::boxed::Box<dyn std::fmt::Display + '_> { std::boxed::Box::new(format!("{}", #var)) })
141                    }
142                })
143            }
144        }
145    }
146}