| use proc_macro2::TokenStream; |
| use quote::quote; |
| use syn::{punctuated::Punctuated, DeriveInput, Token}; |
| |
| use crate::code::Code; |
| use crate::diagnostic_arg::DiagnosticArg; |
| use crate::diagnostic_source::DiagnosticSource; |
| use crate::forward::{Forward, WhichFn}; |
| use crate::help::Help; |
| use crate::label::Labels; |
| use crate::related::Related; |
| use crate::severity::Severity; |
| use crate::source_code::SourceCode; |
| use crate::url::Url; |
| |
| pub enum Diagnostic { |
| Struct { |
| generics: syn::Generics, |
| ident: syn::Ident, |
| fields: syn::Fields, |
| args: DiagnosticDefArgs, |
| }, |
| Enum { |
| ident: syn::Ident, |
| generics: syn::Generics, |
| variants: Vec<DiagnosticDef>, |
| }, |
| } |
| |
| pub struct DiagnosticDef { |
| pub ident: syn::Ident, |
| pub fields: syn::Fields, |
| pub args: DiagnosticDefArgs, |
| } |
| |
| pub enum DiagnosticDefArgs { |
| Transparent(Forward), |
| Concrete(Box<DiagnosticConcreteArgs>), |
| } |
| |
| impl DiagnosticDefArgs { |
| pub(crate) fn forward_or_override_enum( |
| &self, |
| variant: &syn::Ident, |
| which_fn: WhichFn, |
| mut f: impl FnMut(&DiagnosticConcreteArgs) -> Option<TokenStream>, |
| ) -> Option<TokenStream> { |
| match self { |
| Self::Transparent(forward) => Some(forward.gen_enum_match_arm(variant, which_fn)), |
| Self::Concrete(concrete) => f(concrete).or_else(|| { |
| concrete |
| .forward |
| .as_ref() |
| .map(|forward| forward.gen_enum_match_arm(variant, which_fn)) |
| }), |
| } |
| } |
| } |
| |
| #[derive(Default)] |
| pub struct DiagnosticConcreteArgs { |
| pub code: Option<Code>, |
| pub severity: Option<Severity>, |
| pub help: Option<Help>, |
| pub labels: Option<Labels>, |
| pub source_code: Option<SourceCode>, |
| pub url: Option<Url>, |
| pub forward: Option<Forward>, |
| pub related: Option<Related>, |
| pub diagnostic_source: Option<DiagnosticSource>, |
| } |
| |
| impl DiagnosticConcreteArgs { |
| fn for_fields(fields: &syn::Fields) -> Result<Self, syn::Error> { |
| let labels = Labels::from_fields(fields)?; |
| let source_code = SourceCode::from_fields(fields)?; |
| let related = Related::from_fields(fields)?; |
| let help = Help::from_fields(fields)?; |
| let diagnostic_source = DiagnosticSource::from_fields(fields)?; |
| Ok(DiagnosticConcreteArgs { |
| code: None, |
| help, |
| related, |
| severity: None, |
| labels, |
| url: None, |
| forward: None, |
| source_code, |
| diagnostic_source, |
| }) |
| } |
| |
| fn add_args( |
| &mut self, |
| attr: &syn::Attribute, |
| args: impl Iterator<Item = DiagnosticArg>, |
| errors: &mut Vec<syn::Error>, |
| ) { |
| for arg in args { |
| match arg { |
| DiagnosticArg::Transparent => { |
| errors.push(syn::Error::new_spanned(attr, "transparent not allowed")); |
| } |
| DiagnosticArg::Forward(to_field) => { |
| if self.forward.is_some() { |
| errors.push(syn::Error::new_spanned( |
| attr, |
| "forward has already been specified", |
| )); |
| } |
| self.forward = Some(to_field); |
| } |
| DiagnosticArg::Code(new_code) => { |
| if self.code.is_some() { |
| errors.push(syn::Error::new_spanned( |
| attr, |
| "code has already been specified", |
| )); |
| } |
| self.code = Some(new_code); |
| } |
| DiagnosticArg::Severity(sev) => { |
| if self.severity.is_some() { |
| errors.push(syn::Error::new_spanned( |
| attr, |
| "severity has already been specified", |
| )); |
| } |
| self.severity = Some(sev); |
| } |
| DiagnosticArg::Help(hl) => { |
| if self.help.is_some() { |
| errors.push(syn::Error::new_spanned( |
| attr, |
| "help has already been specified", |
| )); |
| } |
| self.help = Some(hl); |
| } |
| DiagnosticArg::Url(u) => { |
| if self.url.is_some() { |
| errors.push(syn::Error::new_spanned( |
| attr, |
| "url has already been specified", |
| )); |
| } |
| self.url = Some(u); |
| } |
| } |
| } |
| } |
| } |
| |
| impl DiagnosticDefArgs { |
| fn parse( |
| _ident: &syn::Ident, |
| fields: &syn::Fields, |
| attrs: &[&syn::Attribute], |
| allow_transparent: bool, |
| ) -> syn::Result<Self> { |
| let mut errors = Vec::new(); |
| |
| // Handle the only condition where Transparent is allowed |
| if allow_transparent && attrs.len() == 1 { |
| if let Ok(args) = |
| attrs[0].parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated) |
| { |
| if matches!(args.first(), Some(DiagnosticArg::Transparent)) { |
| let forward = Forward::for_transparent_field(fields)?; |
| return Ok(Self::Transparent(forward)); |
| } |
| } |
| } |
| |
| // Create errors for any appearances of Transparent |
| let error_message = if allow_transparent { |
| "diagnostic(transparent) not allowed in combination with other args" |
| } else { |
| "diagnostic(transparent) not allowed here" |
| }; |
| fn is_transparent(d: &DiagnosticArg) -> bool { |
| matches!(d, DiagnosticArg::Transparent) |
| } |
| |
| let mut concrete = DiagnosticConcreteArgs::for_fields(fields)?; |
| for attr in attrs { |
| let args = |
| attr.parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated); |
| let args = match args { |
| Ok(args) => args, |
| Err(error) => { |
| errors.push(error); |
| continue; |
| } |
| }; |
| |
| if args.iter().any(is_transparent) { |
| errors.push(syn::Error::new_spanned(attr, error_message)); |
| } |
| |
| let args = args |
| .into_iter() |
| .filter(|x| !matches!(x, DiagnosticArg::Transparent)); |
| |
| concrete.add_args(attr, args, &mut errors); |
| } |
| |
| let combined_error = errors.into_iter().reduce(|mut lhs, rhs| { |
| lhs.combine(rhs); |
| lhs |
| }); |
| if let Some(error) = combined_error { |
| Err(error) |
| } else { |
| Ok(DiagnosticDefArgs::Concrete(Box::new(concrete))) |
| } |
| } |
| } |
| |
| impl Diagnostic { |
| pub fn from_derive_input(input: DeriveInput) -> Result<Self, syn::Error> { |
| let input_attrs = input |
| .attrs |
| .iter() |
| .filter(|x| x.path().is_ident("diagnostic")) |
| .collect::<Vec<&syn::Attribute>>(); |
| Ok(match input.data { |
| syn::Data::Struct(data_struct) => { |
| let args = DiagnosticDefArgs::parse( |
| &input.ident, |
| &data_struct.fields, |
| &input_attrs, |
| true, |
| )?; |
| |
| Diagnostic::Struct { |
| fields: data_struct.fields, |
| ident: input.ident, |
| generics: input.generics, |
| args, |
| } |
| } |
| syn::Data::Enum(syn::DataEnum { variants, .. }) => { |
| let mut vars = Vec::new(); |
| for var in variants { |
| let mut variant_attrs = input_attrs.clone(); |
| variant_attrs |
| .extend(var.attrs.iter().filter(|x| x.path().is_ident("diagnostic"))); |
| let args = |
| DiagnosticDefArgs::parse(&var.ident, &var.fields, &variant_attrs, true)?; |
| vars.push(DiagnosticDef { |
| ident: var.ident, |
| fields: var.fields, |
| args, |
| }); |
| } |
| Diagnostic::Enum { |
| ident: input.ident, |
| generics: input.generics, |
| variants: vars, |
| } |
| } |
| syn::Data::Union(_) => { |
| return Err(syn::Error::new( |
| input.ident.span(), |
| "Can't derive Diagnostic for Unions", |
| )) |
| } |
| }) |
| } |
| |
| pub fn gen(&self) -> TokenStream { |
| match self { |
| Self::Struct { |
| ident, |
| fields, |
| generics, |
| args, |
| } => { |
| let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl(); |
| match args { |
| DiagnosticDefArgs::Transparent(forward) => { |
| let code_method = forward.gen_struct_method(WhichFn::Code); |
| let help_method = forward.gen_struct_method(WhichFn::Help); |
| let url_method = forward.gen_struct_method(WhichFn::Url); |
| let labels_method = forward.gen_struct_method(WhichFn::Labels); |
| let source_code_method = forward.gen_struct_method(WhichFn::SourceCode); |
| let severity_method = forward.gen_struct_method(WhichFn::Severity); |
| let related_method = forward.gen_struct_method(WhichFn::Related); |
| let diagnostic_source_method = |
| forward.gen_struct_method(WhichFn::DiagnosticSource); |
| |
| quote! { |
| impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause { |
| #code_method |
| #help_method |
| #url_method |
| #labels_method |
| #severity_method |
| #source_code_method |
| #related_method |
| #diagnostic_source_method |
| } |
| } |
| } |
| DiagnosticDefArgs::Concrete(concrete) => { |
| let forward = |which| { |
| concrete |
| .forward |
| .as_ref() |
| .map(|fwd| fwd.gen_struct_method(which)) |
| }; |
| let code_body = concrete |
| .code |
| .as_ref() |
| .and_then(|x| x.gen_struct()) |
| .or_else(|| forward(WhichFn::Code)); |
| let help_body = concrete |
| .help |
| .as_ref() |
| .and_then(|x| x.gen_struct(fields)) |
| .or_else(|| forward(WhichFn::Help)); |
| let sev_body = concrete |
| .severity |
| .as_ref() |
| .and_then(|x| x.gen_struct()) |
| .or_else(|| forward(WhichFn::Severity)); |
| let rel_body = concrete |
| .related |
| .as_ref() |
| .and_then(|x| x.gen_struct()) |
| .or_else(|| forward(WhichFn::Related)); |
| let url_body = concrete |
| .url |
| .as_ref() |
| .and_then(|x| x.gen_struct(ident, fields)) |
| .or_else(|| forward(WhichFn::Url)); |
| let labels_body = concrete |
| .labels |
| .as_ref() |
| .and_then(|x| x.gen_struct(fields)) |
| .or_else(|| forward(WhichFn::Labels)); |
| let src_body = concrete |
| .source_code |
| .as_ref() |
| .and_then(|x| x.gen_struct(fields)) |
| .or_else(|| forward(WhichFn::SourceCode)); |
| let diagnostic_source = concrete |
| .diagnostic_source |
| .as_ref() |
| .and_then(|x| x.gen_struct()) |
| .or_else(|| forward(WhichFn::DiagnosticSource)); |
| quote! { |
| impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause { |
| #code_body |
| #help_body |
| #sev_body |
| #rel_body |
| #url_body |
| #labels_body |
| #src_body |
| #diagnostic_source |
| } |
| } |
| } |
| } |
| } |
| Self::Enum { |
| ident, |
| generics, |
| variants, |
| } => { |
| let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl(); |
| let code_body = Code::gen_enum(variants); |
| let help_body = Help::gen_enum(variants); |
| let sev_body = Severity::gen_enum(variants); |
| let labels_body = Labels::gen_enum(variants); |
| let src_body = SourceCode::gen_enum(variants); |
| let rel_body = Related::gen_enum(variants); |
| let url_body = Url::gen_enum(ident, variants); |
| let diagnostic_source_body = DiagnosticSource::gen_enum(variants); |
| quote! { |
| impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause { |
| #code_body |
| #help_body |
| #sev_body |
| #labels_body |
| #src_body |
| #rel_body |
| #url_body |
| #diagnostic_source_body |
| } |
| } |
| } |
| } |
| } |
| } |