diff options
Diffstat (limited to 'vendor/miette-derive/src/diagnostic.rs')
-rw-r--r-- | vendor/miette-derive/src/diagnostic.rs | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/vendor/miette-derive/src/diagnostic.rs b/vendor/miette-derive/src/diagnostic.rs new file mode 100644 index 0000000..0173d2a --- /dev/null +++ b/vendor/miette-derive/src/diagnostic.rs @@ -0,0 +1,397 @@ +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 + } + } + } + } + } +} |