1 use proc_macro2::TokenStream; 2 use quote::quote; 3 use syn::{punctuated::Punctuated, DeriveInput, Token}; 4 5 use crate::code::Code; 6 use crate::diagnostic_arg::DiagnosticArg; 7 use crate::diagnostic_source::DiagnosticSource; 8 use crate::forward::{Forward, WhichFn}; 9 use crate::help::Help; 10 use crate::label::Labels; 11 use crate::related::Related; 12 use crate::severity::Severity; 13 use crate::source_code::SourceCode; 14 use crate::url::Url; 15 16 pub enum Diagnostic { 17 Struct { 18 generics: syn::Generics, 19 ident: syn::Ident, 20 fields: syn::Fields, 21 args: DiagnosticDefArgs, 22 }, 23 Enum { 24 ident: syn::Ident, 25 generics: syn::Generics, 26 variants: Vec<DiagnosticDef>, 27 }, 28 } 29 30 pub struct DiagnosticDef { 31 pub ident: syn::Ident, 32 pub fields: syn::Fields, 33 pub args: DiagnosticDefArgs, 34 } 35 36 pub enum DiagnosticDefArgs { 37 Transparent(Forward), 38 Concrete(Box<DiagnosticConcreteArgs>), 39 } 40 41 impl DiagnosticDefArgs { forward_or_override_enum( &self, variant: &syn::Ident, which_fn: WhichFn, mut f: impl FnMut(&DiagnosticConcreteArgs) -> Option<TokenStream>, ) -> Option<TokenStream>42 pub(crate) fn forward_or_override_enum( 43 &self, 44 variant: &syn::Ident, 45 which_fn: WhichFn, 46 mut f: impl FnMut(&DiagnosticConcreteArgs) -> Option<TokenStream>, 47 ) -> Option<TokenStream> { 48 match self { 49 Self::Transparent(forward) => Some(forward.gen_enum_match_arm(variant, which_fn)), 50 Self::Concrete(concrete) => f(concrete).or_else(|| { 51 concrete 52 .forward 53 .as_ref() 54 .map(|forward| forward.gen_enum_match_arm(variant, which_fn)) 55 }), 56 } 57 } 58 } 59 60 #[derive(Default)] 61 pub struct DiagnosticConcreteArgs { 62 pub code: Option<Code>, 63 pub severity: Option<Severity>, 64 pub help: Option<Help>, 65 pub labels: Option<Labels>, 66 pub source_code: Option<SourceCode>, 67 pub url: Option<Url>, 68 pub forward: Option<Forward>, 69 pub related: Option<Related>, 70 pub diagnostic_source: Option<DiagnosticSource>, 71 } 72 73 impl DiagnosticConcreteArgs { for_fields(fields: &syn::Fields) -> Result<Self, syn::Error>74 fn for_fields(fields: &syn::Fields) -> Result<Self, syn::Error> { 75 let labels = Labels::from_fields(fields)?; 76 let source_code = SourceCode::from_fields(fields)?; 77 let related = Related::from_fields(fields)?; 78 let help = Help::from_fields(fields)?; 79 let diagnostic_source = DiagnosticSource::from_fields(fields)?; 80 Ok(DiagnosticConcreteArgs { 81 code: None, 82 help, 83 related, 84 severity: None, 85 labels, 86 url: None, 87 forward: None, 88 source_code, 89 diagnostic_source, 90 }) 91 } 92 add_args( &mut self, attr: &syn::Attribute, args: impl Iterator<Item = DiagnosticArg>, errors: &mut Vec<syn::Error>, )93 fn add_args( 94 &mut self, 95 attr: &syn::Attribute, 96 args: impl Iterator<Item = DiagnosticArg>, 97 errors: &mut Vec<syn::Error>, 98 ) { 99 for arg in args { 100 match arg { 101 DiagnosticArg::Transparent => { 102 errors.push(syn::Error::new_spanned(attr, "transparent not allowed")); 103 } 104 DiagnosticArg::Forward(to_field) => { 105 if self.forward.is_some() { 106 errors.push(syn::Error::new_spanned( 107 attr, 108 "forward has already been specified", 109 )); 110 } 111 self.forward = Some(to_field); 112 } 113 DiagnosticArg::Code(new_code) => { 114 if self.code.is_some() { 115 errors.push(syn::Error::new_spanned( 116 attr, 117 "code has already been specified", 118 )); 119 } 120 self.code = Some(new_code); 121 } 122 DiagnosticArg::Severity(sev) => { 123 if self.severity.is_some() { 124 errors.push(syn::Error::new_spanned( 125 attr, 126 "severity has already been specified", 127 )); 128 } 129 self.severity = Some(sev); 130 } 131 DiagnosticArg::Help(hl) => { 132 if self.help.is_some() { 133 errors.push(syn::Error::new_spanned( 134 attr, 135 "help has already been specified", 136 )); 137 } 138 self.help = Some(hl); 139 } 140 DiagnosticArg::Url(u) => { 141 if self.url.is_some() { 142 errors.push(syn::Error::new_spanned( 143 attr, 144 "url has already been specified", 145 )); 146 } 147 self.url = Some(u); 148 } 149 } 150 } 151 } 152 } 153 154 impl DiagnosticDefArgs { parse( _ident: &syn::Ident, fields: &syn::Fields, attrs: &[&syn::Attribute], allow_transparent: bool, ) -> syn::Result<Self>155 fn parse( 156 _ident: &syn::Ident, 157 fields: &syn::Fields, 158 attrs: &[&syn::Attribute], 159 allow_transparent: bool, 160 ) -> syn::Result<Self> { 161 let mut errors = Vec::new(); 162 163 // Handle the only condition where Transparent is allowed 164 if allow_transparent && attrs.len() == 1 { 165 if let Ok(args) = 166 attrs[0].parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated) 167 { 168 if matches!(args.first(), Some(DiagnosticArg::Transparent)) { 169 let forward = Forward::for_transparent_field(fields)?; 170 return Ok(Self::Transparent(forward)); 171 } 172 } 173 } 174 175 // Create errors for any appearances of Transparent 176 let error_message = if allow_transparent { 177 "diagnostic(transparent) not allowed in combination with other args" 178 } else { 179 "diagnostic(transparent) not allowed here" 180 }; 181 fn is_transparent(d: &DiagnosticArg) -> bool { 182 matches!(d, DiagnosticArg::Transparent) 183 } 184 185 let mut concrete = DiagnosticConcreteArgs::for_fields(fields)?; 186 for attr in attrs { 187 let args = 188 attr.parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated); 189 let args = match args { 190 Ok(args) => args, 191 Err(error) => { 192 errors.push(error); 193 continue; 194 } 195 }; 196 197 if args.iter().any(is_transparent) { 198 errors.push(syn::Error::new_spanned(attr, error_message)); 199 } 200 201 let args = args 202 .into_iter() 203 .filter(|x| !matches!(x, DiagnosticArg::Transparent)); 204 205 concrete.add_args(attr, args, &mut errors); 206 } 207 208 let combined_error = errors.into_iter().reduce(|mut lhs, rhs| { 209 lhs.combine(rhs); 210 lhs 211 }); 212 if let Some(error) = combined_error { 213 Err(error) 214 } else { 215 Ok(DiagnosticDefArgs::Concrete(Box::new(concrete))) 216 } 217 } 218 } 219 220 impl Diagnostic { from_derive_input(input: DeriveInput) -> Result<Self, syn::Error>221 pub fn from_derive_input(input: DeriveInput) -> Result<Self, syn::Error> { 222 let input_attrs = input 223 .attrs 224 .iter() 225 .filter(|x| x.path().is_ident("diagnostic")) 226 .collect::<Vec<&syn::Attribute>>(); 227 Ok(match input.data { 228 syn::Data::Struct(data_struct) => { 229 let args = DiagnosticDefArgs::parse( 230 &input.ident, 231 &data_struct.fields, 232 &input_attrs, 233 true, 234 )?; 235 236 Diagnostic::Struct { 237 fields: data_struct.fields, 238 ident: input.ident, 239 generics: input.generics, 240 args, 241 } 242 } 243 syn::Data::Enum(syn::DataEnum { variants, .. }) => { 244 let mut vars = Vec::new(); 245 for var in variants { 246 let mut variant_attrs = input_attrs.clone(); 247 variant_attrs 248 .extend(var.attrs.iter().filter(|x| x.path().is_ident("diagnostic"))); 249 let args = 250 DiagnosticDefArgs::parse(&var.ident, &var.fields, &variant_attrs, true)?; 251 vars.push(DiagnosticDef { 252 ident: var.ident, 253 fields: var.fields, 254 args, 255 }); 256 } 257 Diagnostic::Enum { 258 ident: input.ident, 259 generics: input.generics, 260 variants: vars, 261 } 262 } 263 syn::Data::Union(_) => { 264 return Err(syn::Error::new( 265 input.ident.span(), 266 "Can't derive Diagnostic for Unions", 267 )) 268 } 269 }) 270 } 271 gen(&self) -> TokenStream272 pub fn gen(&self) -> TokenStream { 273 match self { 274 Self::Struct { 275 ident, 276 fields, 277 generics, 278 args, 279 } => { 280 let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl(); 281 match args { 282 DiagnosticDefArgs::Transparent(forward) => { 283 let code_method = forward.gen_struct_method(WhichFn::Code); 284 let help_method = forward.gen_struct_method(WhichFn::Help); 285 let url_method = forward.gen_struct_method(WhichFn::Url); 286 let labels_method = forward.gen_struct_method(WhichFn::Labels); 287 let source_code_method = forward.gen_struct_method(WhichFn::SourceCode); 288 let severity_method = forward.gen_struct_method(WhichFn::Severity); 289 let related_method = forward.gen_struct_method(WhichFn::Related); 290 let diagnostic_source_method = 291 forward.gen_struct_method(WhichFn::DiagnosticSource); 292 293 quote! { 294 impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause { 295 #code_method 296 #help_method 297 #url_method 298 #labels_method 299 #severity_method 300 #source_code_method 301 #related_method 302 #diagnostic_source_method 303 } 304 } 305 } 306 DiagnosticDefArgs::Concrete(concrete) => { 307 let forward = |which| { 308 concrete 309 .forward 310 .as_ref() 311 .map(|fwd| fwd.gen_struct_method(which)) 312 }; 313 let code_body = concrete 314 .code 315 .as_ref() 316 .and_then(|x| x.gen_struct()) 317 .or_else(|| forward(WhichFn::Code)); 318 let help_body = concrete 319 .help 320 .as_ref() 321 .and_then(|x| x.gen_struct(fields)) 322 .or_else(|| forward(WhichFn::Help)); 323 let sev_body = concrete 324 .severity 325 .as_ref() 326 .and_then(|x| x.gen_struct()) 327 .or_else(|| forward(WhichFn::Severity)); 328 let rel_body = concrete 329 .related 330 .as_ref() 331 .and_then(|x| x.gen_struct()) 332 .or_else(|| forward(WhichFn::Related)); 333 let url_body = concrete 334 .url 335 .as_ref() 336 .and_then(|x| x.gen_struct(ident, fields)) 337 .or_else(|| forward(WhichFn::Url)); 338 let labels_body = concrete 339 .labels 340 .as_ref() 341 .and_then(|x| x.gen_struct(fields)) 342 .or_else(|| forward(WhichFn::Labels)); 343 let src_body = concrete 344 .source_code 345 .as_ref() 346 .and_then(|x| x.gen_struct(fields)) 347 .or_else(|| forward(WhichFn::SourceCode)); 348 let diagnostic_source = concrete 349 .diagnostic_source 350 .as_ref() 351 .and_then(|x| x.gen_struct()) 352 .or_else(|| forward(WhichFn::DiagnosticSource)); 353 quote! { 354 impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause { 355 #code_body 356 #help_body 357 #sev_body 358 #rel_body 359 #url_body 360 #labels_body 361 #src_body 362 #diagnostic_source 363 } 364 } 365 } 366 } 367 } 368 Self::Enum { 369 ident, 370 generics, 371 variants, 372 } => { 373 let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl(); 374 let code_body = Code::gen_enum(variants); 375 let help_body = Help::gen_enum(variants); 376 let sev_body = Severity::gen_enum(variants); 377 let labels_body = Labels::gen_enum(variants); 378 let src_body = SourceCode::gen_enum(variants); 379 let rel_body = Related::gen_enum(variants); 380 let url_body = Url::gen_enum(ident, variants); 381 let diagnostic_source_body = DiagnosticSource::gen_enum(variants); 382 quote! { 383 impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause { 384 #code_body 385 #help_body 386 #sev_body 387 #labels_body 388 #src_body 389 #rel_body 390 #url_body 391 #diagnostic_source_body 392 } 393 } 394 } 395 } 396 } 397 } 398