1 use proc_macro2::TokenStream;
2 use quote::{format_ident, quote};
3 use syn::{
4     parenthesized,
5     parse::{Parse, ParseStream},
6     spanned::Spanned,
7     Fields, Token,
8 };
9 
10 use crate::{
11     diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
12     utils::{display_pat_members, gen_all_variants_with},
13 };
14 use crate::{
15     fmt::{self, Display},
16     forward::WhichFn,
17 };
18 
19 pub enum Help {
20     Display(Display),
21     Field(syn::Member, Box<syn::Type>),
22 }
23 
24 impl Parse for Help {
parse(input: ParseStream) -> syn::Result<Self>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 
58 impl Help {
from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>>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 
from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>>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     }
gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream>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 
gen_struct(&self, fields: &Fields) -> Option<TokenStream>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 }
147