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