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