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