• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     primary: bool,
24 }
25 
26 struct LabelAttr {
27     label: Option<Display>,
28     primary: bool,
29 }
30 
31 impl Parse for LabelAttr {
parse(input: ParseStream) -> syn::Result<Self>32     fn parse(input: ParseStream) -> syn::Result<Self> {
33         // Skip a token.
34         // This should receive one of:
35         // - label = "..."
36         // - label("...")
37         let _ = input.step(|cursor| {
38             if let Some((_, next)) = cursor.token_tree() {
39                 Ok(((), next))
40             } else {
41                 Err(cursor.error("unexpected empty attribute"))
42             }
43         });
44         let la = input.lookahead1();
45         let (primary, label) = if la.peek(syn::token::Paren) {
46             // #[label(primary?, "{}", x)]
47             let content;
48             parenthesized!(content in input);
49 
50             let primary = if content.peek(syn::Ident) {
51                 let ident: syn::Ident = content.parse()?;
52                 if ident != "primary" {
53                     return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or the keyword `primary`."));
54                 }
55                 let _ = content.parse::<Token![,]>();
56                 true
57             } else {
58                 false
59             };
60 
61             if content.peek(syn::LitStr) {
62                 let fmt = content.parse()?;
63                 let args = if content.is_empty() {
64                     TokenStream::new()
65                 } else {
66                     fmt::parse_token_expr(&content, false)?
67                 };
68                 let display = Display {
69                     fmt,
70                     args,
71                     has_bonus_display: false,
72                 };
73                 (primary, Some(display))
74             } else if !primary {
75                 return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or the keyword `primary`."));
76             } else {
77                 (primary, None)
78             }
79         } else if la.peek(Token![=]) {
80             // #[label = "blabla"]
81             input.parse::<Token![=]>()?;
82             (
83                 false,
84                 Some(Display {
85                     fmt: input.parse()?,
86                     args: TokenStream::new(),
87                     has_bonus_display: false,
88                 }),
89             )
90         } else {
91             (false, None)
92         };
93         Ok(LabelAttr { label, primary })
94     }
95 }
96 
97 impl Labels {
from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>>98     pub fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
99         match fields {
100             syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
101             syn::Fields::Unnamed(unnamed) => {
102                 Self::from_fields_vec(unnamed.unnamed.iter().collect())
103             }
104             syn::Fields::Unit => Ok(None),
105         }
106     }
107 
from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>>108     fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
109         let mut labels = Vec::new();
110         for (i, field) in fields.iter().enumerate() {
111             for attr in &field.attrs {
112                 if attr.path().is_ident("label") {
113                     let span = if let Some(ident) = field.ident.clone() {
114                         syn::Member::Named(ident)
115                     } else {
116                         syn::Member::Unnamed(syn::Index {
117                             index: i as u32,
118                             span: field.span(),
119                         })
120                     };
121                     use quote::ToTokens;
122                     let LabelAttr { label, primary } =
123                         syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
124 
125                     if primary && labels.iter().any(|l: &Label| l.primary) {
126                         return Err(syn::Error::new(
127                             field.span(),
128                             "Cannot have more than one primary label.",
129                         ));
130                     }
131 
132                     labels.push(Label {
133                         label,
134                         span,
135                         ty: field.ty.clone(),
136                         primary,
137                     });
138                 }
139             }
140         }
141         if labels.is_empty() {
142             Ok(None)
143         } else {
144             Ok(Some(Labels(labels)))
145         }
146     }
147 
gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream>148     pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
149         let (display_pat, display_members) = display_pat_members(fields);
150         let labels = self.0.iter().map(|highlight| {
151             let Label {
152                 span,
153                 label,
154                 ty,
155                 primary,
156             } = highlight;
157             let var = quote! { __miette_internal_var };
158             let ctor = if *primary {
159                 quote! { miette::LabeledSpan::new_primary_with_span }
160             } else {
161                 quote! { miette::LabeledSpan::new_with_span }
162             };
163             if let Some(display) = label {
164                 let (fmt, args) = display.expand_shorthand_cloned(&display_members);
165                 quote! {
166                     miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
167                     .map(|#var| #ctor(
168                         std::option::Option::Some(format!(#fmt #args)),
169                         #var.clone(),
170                     ))
171                 }
172             } else {
173                 quote! {
174                     miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
175                     .map(|#var| #ctor(
176                         std::option::Option::None,
177                         #var.clone(),
178                     ))
179                 }
180             }
181         });
182         Some(quote! {
183             #[allow(unused_variables)]
184             fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
185                 use miette::macro_helpers::ToOption;
186                 let Self #display_pat = self;
187                 std::option::Option::Some(Box::new(vec![
188                     #(#labels),*
189                 ].into_iter().filter(Option::is_some).map(Option::unwrap)))
190             }
191         })
192     }
193 
gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream>194     pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
195         gen_all_variants_with(
196             variants,
197             WhichFn::Labels,
198             |ident, fields, DiagnosticConcreteArgs { labels, .. }| {
199                 let (display_pat, display_members) = display_pat_members(fields);
200                 labels.as_ref().and_then(|labels| {
201                     let variant_labels = labels.0.iter().map(|label| {
202                         let Label { span, label, ty, primary } = label;
203                         let field = match &span {
204                             syn::Member::Named(ident) => ident.clone(),
205                             syn::Member::Unnamed(syn::Index { index, .. }) => {
206                                 format_ident!("_{}", index)
207                             }
208                         };
209                         let var = quote! { __miette_internal_var };
210                         let ctor = if *primary {
211                             quote! { miette::LabeledSpan::new_primary_with_span }
212                         } else {
213                             quote! { miette::LabeledSpan::new_with_span }
214                         };
215                         if let Some(display) = label {
216                             let (fmt, args) = display.expand_shorthand_cloned(&display_members);
217                             quote! {
218                                 miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
219                                 .map(|#var| #ctor(
220                                     std::option::Option::Some(format!(#fmt #args)),
221                                     #var.clone(),
222                                 ))
223                             }
224                         } else {
225                             quote! {
226                                 miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
227                                 .map(|#var| #ctor(
228                                     std::option::Option::None,
229                                     #var.clone(),
230                                 ))
231                             }
232                         }
233                     });
234                     let variant_name = ident.clone();
235                     match &fields {
236                         syn::Fields::Unit => None,
237                         _ => Some(quote! {
238                             Self::#variant_name #display_pat => {
239                                 use miette::macro_helpers::ToOption;
240                                 std::option::Option::Some(std::boxed::Box::new(vec![
241                                     #(#variant_labels),*
242                                 ].into_iter().filter(Option::is_some).map(Option::unwrap)))
243                             }
244                         }),
245                     }
246                 })
247             },
248         )
249     }
250 }
251