use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
parenthesized,
parse::{Parse, ParseStream},
spanned::Spanned,
};
pub enum Forward {
Unnamed(usize),
Named(syn::Ident),
}
impl Parse for Forward {
fn parse(input: ParseStream) -> syn::Result<Self> {
let forward = input.parse::<syn::Ident>()?;
if forward != "forward" {
return Err(syn::Error::new(forward.span(), "msg"));
}
let content;
parenthesized!(content in input);
let looky = content.lookahead1();
if looky.peek(syn::LitInt) {
let int: syn::LitInt = content.parse()?;
let index = int.base10_parse()?;
return Ok(Forward::Unnamed(index));
}
Ok(Forward::Named(content.parse()?))
}
}
#[derive(Copy, Clone)]
pub enum WhichFn {
Code,
Help,
Url,
Severity,
Labels,
SourceCode,
Related,
DiagnosticSource,
}
impl WhichFn {
pub fn method_call(&self) -> TokenStream {
match self {
Self::Code => quote! { code() },
Self::Help => quote! { help() },
Self::Url => quote! { url() },
Self::Severity => quote! { severity() },
Self::Labels => quote! { labels() },
Self::SourceCode => quote! { source_code() },
Self::Related => quote! { related() },
Self::DiagnosticSource => quote! { diagnostic_source() },
}
}
pub fn signature(&self) -> TokenStream {
match self {
Self::Code => quote! {
fn code(& self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>>
},
Self::Help => quote! {
fn help(& self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>>
},
Self::Url => quote! {
fn url(& self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + '_>>
},
Self::Severity => quote! {
fn severity(&self) -> std::option::Option<miette::Severity>
},
Self::Related => quote! {
fn related(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = &dyn miette::Diagnostic> + '_>>
},
Self::Labels => quote! {
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>>
},
Self::SourceCode => quote! {
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode>
},
Self::DiagnosticSource => quote! {
fn diagnostic_source(&self) -> std::option::Option<&dyn miette::Diagnostic>
},
}
}
pub fn catchall_arm(&self) -> TokenStream {
quote! { _ => std::option::Option::None }
}
}
impl Forward {
pub fn for_transparent_field(fields: &syn::Fields) -> syn::Result<Self> {
let make_err = || {
syn::Error::new(
fields.span(),
"you can only use #[diagnostic(transparent)] with exactly one field",
)
};
match fields {
syn::Fields::Named(named) => {
let mut iter = named.named.iter();
let field = iter.next().ok_or_else(make_err)?;
if iter.next().is_some() {
return Err(make_err());
}
let field_name = field
.ident
.clone()
.unwrap_or_else(|| format_ident!("unnamed"));
Ok(Self::Named(field_name))
}
syn::Fields::Unnamed(unnamed) => {
if unnamed.unnamed.iter().len() != 1 {
return Err(make_err());
}
Ok(Self::Unnamed(0))
}
_ => Err(syn::Error::new(
fields.span(),
"you cannot use #[diagnostic(transparent)] with a unit struct or a unit variant",
)),
}
}
pub fn gen_struct_method(&self, which_fn: WhichFn) -> TokenStream {
let signature = which_fn.signature();
let method_call = which_fn.method_call();
let field_name = match self {
Forward::Named(field_name) => quote!(#field_name),
Forward::Unnamed(index) => {
let index = syn::Index::from(*index);
quote!(#index)
}
};
quote! {
#[inline]
#signature {
self.#field_name.#method_call
}
}
}
pub fn gen_enum_match_arm(&self, variant: &syn::Ident, which_fn: WhichFn) -> TokenStream {
let method_call = which_fn.method_call();
match self {
Forward::Named(field_name) => quote! {
Self::#variant { #field_name, .. } => #field_name.#method_call,
},
Forward::Unnamed(index) => {
let underscores: Vec<_> = core::iter::repeat(quote! { _, }).take(*index).collect();
let unnamed = format_ident!("unnamed");
quote! {
Self::#variant ( #(#underscores)* #unnamed, .. ) => #unnamed.#method_call,
}
}
}
}
}