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}