1 use proc_macro2::TokenStream;
2 use quote::{format_ident, quote};
3 use syn::{
4     parenthesized,
5     parse::{Parse, ParseStream},
6     spanned::Spanned,
7     Token,
8 };
9 
10 use crate::{
11     diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
12     fmt::{self, Display},
13     forward::WhichFn,
14     utils::{display_pat_members, gen_all_variants_with},
15 };
16 
17 pub struct Labels(Vec<Label>);
18 
19 struct Label {
20     label: Option<Display>,
21     ty: syn::Type,
22     span: syn::Member,
23 }
24 
25 struct LabelAttr {
26     label: Option<Display>,
27 }
28 
29 impl Parse for LabelAttr {
parse(input: ParseStream) -> syn::Result<Self>30     fn parse(input: ParseStream) -> syn::Result<Self> {
31         // Skip a token.
32         // This should receive one of:
33         // - label = "..."
34         // - label("...")
35         let _ = input.step(|cursor| {
36             if let Some((_, next)) = cursor.token_tree() {
37                 Ok(((), next))
38             } else {
39                 Err(cursor.error("unexpected empty attribute"))
40             }
41         });
42         let la = input.lookahead1();
43         let label = if la.peek(syn::token::Paren) {
44             // #[label("{}", x)]
45             let content;
46             parenthesized!(content in input);
47             if content.peek(syn::LitStr) {
48                 let fmt = content.parse()?;
49                 let args = if content.is_empty() {
50                     TokenStream::new()
51                 } else {
52                     fmt::parse_token_expr(&content, false)?
53                 };
54                 let display = Display {
55                     fmt,
56                     args,
57                     has_bonus_display: false,
58                 };
59                 Some(display)
60             } else {
61                 return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The first argument must be a literal string."));
62             }
63         } else if la.peek(Token![=]) {
64             // #[label = "blabla"]
65             input.parse::<Token![=]>()?;
66             Some(Display {
67                 fmt: input.parse()?,
68                 args: TokenStream::new(),
69                 has_bonus_display: false,
70             })
71         } else {
72             None
73         };
74         Ok(LabelAttr { label })
75     }
76 }
77 
78 impl Labels {
from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>>79     pub fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
80         match fields {
81             syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
82             syn::Fields::Unnamed(unnamed) => {
83                 Self::from_fields_vec(unnamed.unnamed.iter().collect())
84             }
85             syn::Fields::Unit => Ok(None),
86         }
87     }
88 
from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>>89     fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
90         let mut labels = Vec::new();
91         for (i, field) in fields.iter().enumerate() {
92             for attr in &field.attrs {
93                 if attr.path().is_ident("label") {
94                     let span = if let Some(ident) = field.ident.clone() {
95                         syn::Member::Named(ident)
96                     } else {
97                         syn::Member::Unnamed(syn::Index {
98                             index: i as u32,
99                             span: field.span(),
100                         })
101                     };
102                     use quote::ToTokens;
103                     let LabelAttr { label } =
104                         syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
105                     labels.push(Label {
106                         label,
107                         span,
108                         ty: field.ty.clone(),
109                     });
110                 }
111             }
112         }
113         if labels.is_empty() {
114             Ok(None)
115         } else {
116             Ok(Some(Labels(labels)))
117         }
118     }
119 
gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream>120     pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
121         let (display_pat, display_members) = display_pat_members(fields);
122         let labels = self.0.iter().map(|highlight| {
123             let Label { span, label, ty } = highlight;
124             let var = quote! { __miette_internal_var };
125             if let Some(display) = label {
126                 let (fmt, args) = display.expand_shorthand_cloned(&display_members);
127                 quote! {
128                     miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
129                     .map(|#var| miette::LabeledSpan::new_with_span(
130                         std::option::Option::Some(format!(#fmt #args)),
131                         #var.clone(),
132                     ))
133                 }
134             } else {
135                 quote! {
136                     miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
137                     .map(|#var| miette::LabeledSpan::new_with_span(
138                         std::option::Option::None,
139                         #var.clone(),
140                     ))
141                 }
142             }
143         });
144         Some(quote! {
145             #[allow(unused_variables)]
146             fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
147                 use miette::macro_helpers::ToOption;
148                 let Self #display_pat = self;
149                 std::option::Option::Some(Box::new(vec![
150                     #(#labels),*
151                 ].into_iter().filter(Option::is_some).map(Option::unwrap)))
152             }
153         })
154     }
155 
gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream>156     pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
157         gen_all_variants_with(
158             variants,
159             WhichFn::Labels,
160             |ident, fields, DiagnosticConcreteArgs { labels, .. }| {
161                 let (display_pat, display_members) = display_pat_members(fields);
162                 labels.as_ref().and_then(|labels| {
163                     let variant_labels = labels.0.iter().map(|label| {
164                         let Label { span, label, ty } = label;
165                         let field = match &span {
166                             syn::Member::Named(ident) => ident.clone(),
167                             syn::Member::Unnamed(syn::Index { index, .. }) => {
168                                 format_ident!("_{}", index)
169                             }
170                         };
171                         let var = quote! { __miette_internal_var };
172                         if let Some(display) = label {
173                             let (fmt, args) = display.expand_shorthand_cloned(&display_members);
174                             quote! {
175                                 miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
176                                 .map(|#var| miette::LabeledSpan::new_with_span(
177                                     std::option::Option::Some(format!(#fmt #args)),
178                                     #var.clone(),
179                                 ))
180                             }
181                         } else {
182                             quote! {
183                                 miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
184                                 .map(|#var| miette::LabeledSpan::new_with_span(
185                                     std::option::Option::None,
186                                     #var.clone(),
187                                 ))
188                             }
189                         }
190                     });
191                     let variant_name = ident.clone();
192                     match &fields {
193                         syn::Fields::Unit => None,
194                         _ => Some(quote! {
195                             Self::#variant_name #display_pat => {
196                                 use miette::macro_helpers::ToOption;
197                                 std::option::Option::Some(std::boxed::Box::new(vec![
198                                     #(#variant_labels),*
199                                 ].into_iter().filter(Option::is_some).map(Option::unwrap)))
200                             }
201                         }),
202                     }
203                 })
204             },
205         )
206     }
207 }
208