mirror of https://github.com/zkat/miette.git
426 lines
16 KiB
Rust
426 lines
16 KiB
Rust
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::trait_bounds::TypeParamBoundStore;
|
|
use crate::url::Url;
|
|
|
|
pub enum Diagnostic {
|
|
Struct {
|
|
generics: syn::Generics,
|
|
ident: syn::Ident,
|
|
fields: syn::Fields,
|
|
args: DiagnosticDefArgs,
|
|
bound_store: TypeParamBoundStore,
|
|
},
|
|
Enum {
|
|
ident: syn::Ident,
|
|
generics: syn::Generics,
|
|
variants: Vec<DiagnosticDef>,
|
|
bound_store: TypeParamBoundStore,
|
|
},
|
|
}
|
|
|
|
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,
|
|
bounds_store: &mut TypeParamBoundStore,
|
|
) -> Result<Self, syn::Error> {
|
|
let labels = Labels::from_fields(fields, bounds_store)?;
|
|
let source_code = SourceCode::from_fields(fields, bounds_store)?;
|
|
let related = Related::from_fields(fields, bounds_store)?;
|
|
let help = Help::from_fields(fields)?;
|
|
let diagnostic_source = DiagnosticSource::from_fields(fields, bounds_store)?;
|
|
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],
|
|
bounds_store: &mut TypeParamBoundStore,
|
|
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, bounds_store)?;
|
|
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, bounds_store)?;
|
|
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 mut bounds_store = TypeParamBoundStore::new(&input.generics);
|
|
|
|
let args = DiagnosticDefArgs::parse(
|
|
&input.ident,
|
|
&data_struct.fields,
|
|
&input_attrs,
|
|
&mut bounds_store,
|
|
true,
|
|
)?;
|
|
|
|
Diagnostic::Struct {
|
|
fields: data_struct.fields,
|
|
ident: input.ident,
|
|
generics: input.generics,
|
|
args,
|
|
bound_store: bounds_store,
|
|
}
|
|
}
|
|
syn::Data::Enum(syn::DataEnum { variants, .. }) => {
|
|
let mut vars = Vec::new();
|
|
let mut bound_store = TypeParamBoundStore::new(&input.generics);
|
|
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,
|
|
&mut bound_store,
|
|
true,
|
|
)?;
|
|
vars.push(DiagnosticDef {
|
|
ident: var.ident,
|
|
fields: var.fields,
|
|
args,
|
|
});
|
|
}
|
|
Diagnostic::Enum {
|
|
ident: input.ident,
|
|
generics: input.generics,
|
|
variants: vars,
|
|
bound_store,
|
|
}
|
|
}
|
|
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,
|
|
bound_store,
|
|
} => {
|
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
let where_clause = bound_store.add_to_where_clause(where_clause);
|
|
|
|
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,
|
|
bound_store,
|
|
} => {
|
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
let where_clause = bound_store.add_to_where_clause(where_clause);
|
|
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|