mirror of https://github.com/zkat/miette.git
feat(derive): Add `#[diagnostic(forward(field_name), code(...))]` (#41)
This commit is contained in:
parent
d994add912
commit
2fa5551c81
|
|
@ -7,8 +7,9 @@ use syn::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
||||
utils::forward_to_single_field_variant,
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
forward::WhichFn,
|
||||
utils::gen_all_variants_with,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
@ -48,41 +49,24 @@ impl Parse for Code {
|
|||
|
||||
impl Code {
|
||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||
let code_pairs = variants.iter().map(
|
||||
|DiagnosticDef {
|
||||
ident,
|
||||
fields,
|
||||
args,
|
||||
}| {
|
||||
match args {
|
||||
DiagnosticDefArgs::Transparent => {
|
||||
Some(forward_to_single_field_variant(ident, fields, quote! { code() }))
|
||||
gen_all_variants_with(
|
||||
variants,
|
||||
WhichFn::Code,
|
||||
|ident, fields, DiagnosticConcreteArgs { code, .. }| {
|
||||
let code = &code.as_ref()?.0;
|
||||
Some(match fields {
|
||||
syn::Fields::Named(_) => {
|
||||
quote! { Self::#ident { .. } => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
}
|
||||
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { code, .. }) => {
|
||||
let code = &code.as_ref()?.0;
|
||||
Some(match fields {
|
||||
syn::Fields::Named(_) => {
|
||||
quote! { Self::#ident { .. } => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
}
|
||||
syn::Fields::Unnamed(_) => {
|
||||
quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
}
|
||||
syn::Fields::Unit => {
|
||||
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
}
|
||||
})
|
||||
syn::Fields::Unnamed(_) => {
|
||||
quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
}
|
||||
}
|
||||
syn::Fields::Unit => {
|
||||
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||
}
|
||||
})
|
||||
},
|
||||
);
|
||||
Some(quote! {
|
||||
fn code<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
match self {
|
||||
#(#code_pairs)*
|
||||
_ => std::option::Option::None,
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use quote::quote;
|
||||
use syn::{punctuated::Punctuated, DeriveInput, Token};
|
||||
|
||||
use crate::code::Code;
|
||||
use crate::diagnostic_arg::DiagnosticArg;
|
||||
use crate::forward::{Forward, WhichFn};
|
||||
use crate::help::Help;
|
||||
use crate::severity::Severity;
|
||||
use crate::snippets::Snippets;
|
||||
|
|
@ -30,10 +31,29 @@ pub struct DiagnosticDef {
|
|||
}
|
||||
|
||||
pub enum DiagnosticDefArgs {
|
||||
Transparent,
|
||||
Transparent(Forward),
|
||||
Concrete(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>,
|
||||
|
|
@ -41,6 +61,7 @@ pub struct DiagnosticConcreteArgs {
|
|||
pub help: Option<Help>,
|
||||
pub snippets: Option<Snippets>,
|
||||
pub url: Option<Url>,
|
||||
pub forward: Option<Forward>,
|
||||
}
|
||||
|
||||
impl DiagnosticConcreteArgs {
|
||||
|
|
@ -54,11 +75,15 @@ impl DiagnosticConcreteArgs {
|
|||
let mut severity = None;
|
||||
let mut help = None;
|
||||
let mut url = None;
|
||||
let mut forward = None;
|
||||
for arg in args {
|
||||
match arg {
|
||||
DiagnosticArg::Transparent => {
|
||||
return Err(syn::Error::new_spanned(attr, "transparent not allowed"));
|
||||
}
|
||||
DiagnosticArg::Forward(to_field) => {
|
||||
forward = Some(to_field);
|
||||
}
|
||||
DiagnosticArg::Code(new_code) => {
|
||||
// TODO: error on multiple?
|
||||
code = Some(new_code);
|
||||
|
|
@ -81,6 +106,7 @@ impl DiagnosticConcreteArgs {
|
|||
severity,
|
||||
snippets,
|
||||
url,
|
||||
forward,
|
||||
};
|
||||
Ok(concrete)
|
||||
}
|
||||
|
|
@ -92,14 +118,15 @@ impl DiagnosticDefArgs {
|
|||
fields: &syn::Fields,
|
||||
attr: &syn::Attribute,
|
||||
allow_transparent: bool,
|
||||
) -> Result<Self, syn::Error> {
|
||||
) -> syn::Result<Self> {
|
||||
let args =
|
||||
attr.parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated)?;
|
||||
if allow_transparent
|
||||
&& args.len() == 1
|
||||
&& matches!(args.first(), Some(DiagnosticArg::Transparent))
|
||||
{
|
||||
return Ok(Self::Transparent);
|
||||
let forward = Forward::for_transparent_field(fields)?;
|
||||
return Ok(Self::Transparent(forward));
|
||||
} else if args.iter().any(|x| matches!(x, DiagnosticArg::Transparent)) {
|
||||
return Err(syn::Error::new_spanned(
|
||||
attr,
|
||||
|
|
@ -177,65 +204,55 @@ impl Diagnostic {
|
|||
} => {
|
||||
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
||||
match args {
|
||||
DiagnosticDefArgs::Transparent => {
|
||||
if fields.iter().len() != 1 {
|
||||
return quote! {
|
||||
compile_error!("you can only use #[diagnostic(transparent)] on a struct with exactly one field");
|
||||
};
|
||||
}
|
||||
let field = fields
|
||||
.iter()
|
||||
.next()
|
||||
.expect("MIETTE BUG: thought we knew we had exactly one field");
|
||||
let field_name = field
|
||||
.ident
|
||||
.clone()
|
||||
.unwrap_or_else(|| format_ident!("unnamed"));
|
||||
let matcher = match fields {
|
||||
syn::Fields::Named(_) => quote! { let Self { #field_name } = self; },
|
||||
syn::Fields::Unnamed(_) => quote! { let Self(#field_name) = self; },
|
||||
syn::Fields::Unit => {
|
||||
unreachable!("MIETTE BUG: thought we knew we had exactly one field")
|
||||
}
|
||||
};
|
||||
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 severity_method = forward.gen_struct_method(WhichFn::Severity);
|
||||
let snippets_method = forward.gen_struct_method(WhichFn::Snippets);
|
||||
|
||||
quote! {
|
||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||
fn code<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#matcher
|
||||
#field_name.code()
|
||||
}
|
||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#matcher
|
||||
#field_name.help()
|
||||
}
|
||||
fn url<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#matcher
|
||||
#field_name.url()
|
||||
}
|
||||
fn severity(&self) -> std::option::Option<miette::Severity> {
|
||||
#matcher
|
||||
#field_name.severity()
|
||||
}
|
||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>> {
|
||||
#matcher
|
||||
#field_name.snippets()
|
||||
}
|
||||
#code_method
|
||||
#help_method
|
||||
#url_method
|
||||
#severity_method
|
||||
#snippets_method
|
||||
}
|
||||
}
|
||||
}
|
||||
DiagnosticDefArgs::Concrete(concrete) => {
|
||||
let code_body = concrete.code.as_ref().and_then(|x| x.gen_struct());
|
||||
let help_body = concrete.help.as_ref().and_then(|x| x.gen_struct(fields));
|
||||
let sev_body = concrete.severity.as_ref().and_then(|x| x.gen_struct());
|
||||
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 snip_body = concrete
|
||||
.snippets
|
||||
.as_ref()
|
||||
.and_then(|x| x.gen_struct(fields));
|
||||
.and_then(|x| x.gen_struct(fields))
|
||||
.or_else(|| forward(WhichFn::Snippets));
|
||||
let url_body = concrete
|
||||
.url
|
||||
.as_ref()
|
||||
.and_then(|x| x.gen_struct(ident, fields));
|
||||
.and_then(|x| x.gen_struct(ident, fields))
|
||||
.or_else(|| forward(WhichFn::Url));
|
||||
|
||||
quote! {
|
||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use syn::parse::{Parse, ParseStream};
|
||||
|
||||
use crate::code::Code;
|
||||
use crate::forward::Forward;
|
||||
use crate::help::Help;
|
||||
use crate::severity::Severity;
|
||||
use crate::url::Url;
|
||||
|
|
@ -11,6 +12,7 @@ pub enum DiagnosticArg {
|
|||
Severity(Severity),
|
||||
Help(Help),
|
||||
Url(Url),
|
||||
Forward(Forward),
|
||||
}
|
||||
|
||||
impl Parse for DiagnosticArg {
|
||||
|
|
@ -20,6 +22,8 @@ impl Parse for DiagnosticArg {
|
|||
// consume the token
|
||||
let _: syn::Ident = input.parse()?;
|
||||
Ok(DiagnosticArg::Transparent)
|
||||
} else if ident == "forward" {
|
||||
Ok(DiagnosticArg::Forward(input.parse()?))
|
||||
} else if ident == "code" {
|
||||
Ok(DiagnosticArg::Code(input.parse()?))
|
||||
} else if ident == "severity" {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,150 @@
|
|||
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,
|
||||
Snippets,
|
||||
}
|
||||
|
||||
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::Snippets => quote! { snippets() },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> TokenStream {
|
||||
match self {
|
||||
Self::Code => quote! {
|
||||
fn code<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>>
|
||||
},
|
||||
Self::Help => quote! {
|
||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>>
|
||||
},
|
||||
Self::Url => quote! {
|
||||
fn url<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>>
|
||||
},
|
||||
Self::Severity => quote! {
|
||||
fn severity(&self) -> std::option::Option<miette::Severity>
|
||||
},
|
||||
Self::Snippets => quote! {
|
||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>>
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn catchall_arm(&self) -> TokenStream {
|
||||
match self {
|
||||
// required, hence method can't return None
|
||||
Self::Code => quote! {},
|
||||
_ => quote! { _ => 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parenthesized,
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
Fields, Token,
|
||||
};
|
||||
|
||||
use crate::fmt::{self, Display};
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
||||
utils::forward_to_single_field_variant,
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
utils::{display_pat_members, gen_all_variants_with},
|
||||
};
|
||||
use crate::{
|
||||
fmt::{self, Display},
|
||||
forward::WhichFn,
|
||||
};
|
||||
|
||||
pub struct Help {
|
||||
|
|
@ -31,7 +31,6 @@ impl Parse for Help {
|
|||
let args = if content.is_empty() {
|
||||
TokenStream::new()
|
||||
} else {
|
||||
content.parse::<Token![,]>()?;
|
||||
fmt::parse_token_expr(&content, false)?
|
||||
};
|
||||
let display = Display {
|
||||
|
|
@ -58,110 +57,28 @@ impl Parse for Help {
|
|||
|
||||
impl Help {
|
||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||
let help_pairs = variants
|
||||
.iter()
|
||||
.map(
|
||||
|DiagnosticDef {
|
||||
ident,
|
||||
fields,
|
||||
args,
|
||||
..
|
||||
}| {
|
||||
match args {
|
||||
DiagnosticDefArgs::Transparent => {
|
||||
Some(forward_to_single_field_variant(ident, fields, quote!{ help() } ))
|
||||
}
|
||||
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { help, .. }) => {
|
||||
let mut display = help.as_ref()?.display.clone();
|
||||
let member_idents = fields.iter().enumerate().map(|(i, field)| {
|
||||
field
|
||||
.ident
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format_ident!("_{}", i))
|
||||
});
|
||||
let members: HashSet<syn::Member> = fields.iter().enumerate().map(|(i, field)| {
|
||||
if let Some(ident) = field.ident.as_ref().cloned() {
|
||||
syn::Member::Named(ident)
|
||||
} else {
|
||||
syn::Member::Unnamed(syn::Index { index: i as u32, span: field.span() })
|
||||
}
|
||||
}).collect();
|
||||
display.expand_shorthand(&members);
|
||||
let Display { fmt, args, .. } = display;
|
||||
Some(match fields {
|
||||
syn::Fields::Named(_) => {
|
||||
quote! { Self::#ident{ #(#member_idents),* } => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), }
|
||||
}
|
||||
syn::Fields::Unnamed(_) => {
|
||||
quote! { Self::#ident( #(#member_idents),* ) => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), }
|
||||
}
|
||||
syn::Fields::Unit =>
|
||||
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), },
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
if help_pairs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#[allow(unused_variables, deprecated)]
|
||||
match self {
|
||||
#(#help_pairs)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
gen_all_variants_with(
|
||||
variants,
|
||||
WhichFn::Help,
|
||||
|ident, fields, DiagnosticConcreteArgs { help, .. }| {
|
||||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
let display = &help.as_ref()?.display;
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
Some(quote! {
|
||||
Self::#ident #display_pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn gen_struct(&self, fields: &Fields) -> Option<TokenStream> {
|
||||
let mut display = self.display.clone();
|
||||
let members: HashSet<syn::Member> = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
if let Some(ident) = field.ident.as_ref().cloned() {
|
||||
syn::Member::Named(ident)
|
||||
} else {
|
||||
syn::Member::Unnamed(syn::Index {
|
||||
index: i as u32,
|
||||
span: field.span(),
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
display.expand_shorthand(&members);
|
||||
let members = members.iter();
|
||||
let Display { fmt, args, .. } = display;
|
||||
let fields_pat = match fields {
|
||||
Fields::Named(_) => quote! {
|
||||
let Self { #(#members),* } = self;
|
||||
},
|
||||
Fields::Unnamed(_) => {
|
||||
let vars = members.map(|member| {
|
||||
if let syn::Member::Unnamed(member) = member {
|
||||
format_ident!("_{}", member)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
let Self(#(#vars),*) = self;
|
||||
}
|
||||
}
|
||||
Fields::Unit => quote! {},
|
||||
};
|
||||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
let (fmt, args) = self.display.expand_shorthand_cloned(&display_members);
|
||||
Some(quote! {
|
||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#[allow(unused_variables, deprecated)]
|
||||
#fields_pat
|
||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args)))
|
||||
let Self #display_pat = self;
|
||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ mod code;
|
|||
mod diagnostic;
|
||||
mod diagnostic_arg;
|
||||
mod fmt;
|
||||
mod forward;
|
||||
mod help;
|
||||
mod severity;
|
||||
mod snippets;
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ use syn::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
||||
utils::forward_to_single_field_variant,
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
forward::WhichFn,
|
||||
utils::gen_all_variants_with,
|
||||
};
|
||||
|
||||
pub struct Severity(pub syn::Ident);
|
||||
|
|
@ -60,42 +61,21 @@ fn get_severity(input: &str, span: Span) -> syn::Result<String> {
|
|||
|
||||
impl Severity {
|
||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||
let sev_pairs = variants
|
||||
.iter()
|
||||
.map(
|
||||
|DiagnosticDef {
|
||||
ident, fields, args
|
||||
}| {
|
||||
match args {
|
||||
DiagnosticDefArgs::Transparent => {
|
||||
Some(forward_to_single_field_variant(ident, fields, quote!{ severity() }))
|
||||
}
|
||||
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { severity, .. }) => {
|
||||
let severity = &severity.as_ref()?.0;
|
||||
let fields = match fields {
|
||||
syn::Fields::Named(_) => quote! { { .. } },
|
||||
syn::Fields::Unnamed(_) => quote! { (..) },
|
||||
syn::Fields::Unit => quote!{},
|
||||
};
|
||||
Some(quote! { Self::#ident #fields => std::option::Option::Some(miette::Severity::#severity), })
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
if sev_pairs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
fn severity(&self) -> std::option::Option<miette::Severity> {
|
||||
match self {
|
||||
#(#sev_pairs)*
|
||||
_ => std::option::Option::None,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
gen_all_variants_with(
|
||||
variants,
|
||||
WhichFn::Severity,
|
||||
|ident, fields, DiagnosticConcreteArgs { severity, .. }| {
|
||||
let severity = &severity.as_ref()?.0;
|
||||
let fields = match fields {
|
||||
syn::Fields::Named(_) => quote! { { .. } },
|
||||
syn::Fields::Unnamed(_) => quote! { (..) },
|
||||
syn::Fields::Unit => quote! {},
|
||||
};
|
||||
Some(
|
||||
quote! { Self::#ident #fields => std::option::Option::Some(miette::Severity::#severity), },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
|
@ -10,11 +10,10 @@ use syn::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
diagnostic::DiagnosticConcreteArgs, fmt::Display, utils::forward_to_single_field_variant,
|
||||
};
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticDef, DiagnosticDefArgs},
|
||||
fmt,
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
fmt::{self, Display},
|
||||
forward::WhichFn,
|
||||
utils::{display_pat_members, gen_all_variants_with},
|
||||
};
|
||||
|
||||
pub struct Snippets(Vec<Snippet>);
|
||||
|
|
@ -57,7 +56,6 @@ impl Parse for SnippetAttr {
|
|||
let args = if content.is_empty() {
|
||||
TokenStream::new()
|
||||
} else {
|
||||
content.parse::<Token![,]>()?;
|
||||
fmt::parse_token_expr(&content, false)?
|
||||
};
|
||||
let display = Display {
|
||||
|
|
@ -106,7 +104,6 @@ impl Parse for HighlightAttr {
|
|||
let args = if content.is_empty() {
|
||||
TokenStream::new()
|
||||
} else {
|
||||
content.parse::<Token![,]>()?;
|
||||
fmt::parse_token_expr(&content, false)?
|
||||
};
|
||||
let display = Display {
|
||||
|
|
@ -210,28 +207,15 @@ impl Snippets {
|
|||
}
|
||||
|
||||
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
||||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
let snippets = self.0.iter().map(|snippet| {
|
||||
// snippet message
|
||||
let msg = if let Some(display) = &snippet.message {
|
||||
let members: HashSet<syn::Member> = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
if let Some(ident) = field.ident.as_ref().cloned() {
|
||||
syn::Member::Named(ident)
|
||||
} else {
|
||||
syn::Member::Unnamed(syn::Index {
|
||||
index: i as u32,
|
||||
span: field.span(),
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut display = display.clone();
|
||||
display.expand_shorthand(&members);
|
||||
let Display { fmt, args, .. } = display;
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
quote! {
|
||||
message: std::option::Option::Some(format!(#fmt, #args)),
|
||||
message: {
|
||||
std::option::Option::Some(format!(#fmt #args))
|
||||
},
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
|
|
@ -254,12 +238,11 @@ impl Snippets {
|
|||
// Highlights
|
||||
let highlights = snippet.highlights.iter().map(|highlight| {
|
||||
let Highlight { highlight, label } = highlight;
|
||||
if let Some(Display { fmt, args, .. }) = label {
|
||||
if let Some(display) = label {
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
quote! {
|
||||
(
|
||||
std::option::Option::Some(
|
||||
format!(#fmt, #args)
|
||||
),
|
||||
std::option::Option::Some(format!(#fmt #args)),
|
||||
self.#highlight.clone().into()
|
||||
)
|
||||
}
|
||||
|
|
@ -288,6 +271,7 @@ impl Snippets {
|
|||
Some(quote! {
|
||||
#[allow(unused_variables)]
|
||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>> {
|
||||
let Self #display_pat = self;
|
||||
Some(Box::new(vec![
|
||||
#(#snippets),*
|
||||
].into_iter()))
|
||||
|
|
@ -296,31 +280,16 @@ impl Snippets {
|
|||
}
|
||||
|
||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||
let variant_arms = variants.iter().map(|variant| {
|
||||
let DiagnosticDef { ident, fields, args: def_args } = variant;
|
||||
match def_args {
|
||||
DiagnosticDefArgs::Transparent => {
|
||||
Some(forward_to_single_field_variant(
|
||||
ident,
|
||||
fields,
|
||||
quote! { snippets() },
|
||||
))
|
||||
}
|
||||
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { snippets, .. }) => {
|
||||
snippets.as_ref().and_then(|snippets| {
|
||||
gen_all_variants_with(
|
||||
variants,
|
||||
WhichFn::Snippets,
|
||||
|ident, fields, DiagnosticConcreteArgs { snippets, .. }| {
|
||||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
snippets.as_ref().and_then(|snippets| {
|
||||
let variant_snippets = snippets.0.iter().map(|snippet| {
|
||||
// snippet message
|
||||
let msg = if let Some(display) = &snippet.message {
|
||||
let members: HashSet<syn::Member> = variant.fields.iter().enumerate().map(|(i, field)| {
|
||||
if let Some(ident) = field.ident.as_ref().cloned() {
|
||||
syn::Member::Named(ident)
|
||||
} else {
|
||||
syn::Member::Unnamed(syn::Index { index: i as u32, span: field.span() })
|
||||
}
|
||||
}).collect();
|
||||
let mut display = display.clone();
|
||||
display.expand_shorthand(&members);
|
||||
let Display { fmt, args, .. } = display;
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
quote! {
|
||||
message: std::option::Option::Some(format!(#fmt, #args)),
|
||||
}
|
||||
|
|
@ -390,40 +359,17 @@ impl Snippets {
|
|||
}
|
||||
}
|
||||
});
|
||||
let variant_name = variant.ident.clone();
|
||||
let members = variant.fields.iter().enumerate().map(|(i, field)| {
|
||||
field
|
||||
.ident
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format_ident!("_{}", i))
|
||||
});
|
||||
match &variant.fields {
|
||||
let variant_name = ident.clone();
|
||||
match &fields {
|
||||
syn::Fields::Unit => None,
|
||||
syn::Fields::Named(_) => Some(quote! {
|
||||
Self::#variant_name { #(#members),* } => std::option::Option::Some(std::boxed::Box::new(vec![
|
||||
#(#variant_snippets),*
|
||||
].into_iter())),
|
||||
}),
|
||||
syn::Fields::Unnamed(_) => Some(quote! {
|
||||
Self::#variant_name(#(#members),*) => std::option::Option::Some(Box::new(vec![
|
||||
_ => Some(quote! {
|
||||
Self::#variant_name #display_pat => std::option::Option::Some(std::boxed::Box::new(vec![
|
||||
#(#variant_snippets),*
|
||||
].into_iter())),
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten();
|
||||
Some(quote! {
|
||||
#[allow(unused_variables)]
|
||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>> {
|
||||
match self {
|
||||
#(#variant_arms)*
|
||||
_ => std::option::Option::None,
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parenthesized,
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
Fields, Token,
|
||||
};
|
||||
|
||||
use crate::fmt::{self, Display};
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
||||
utils::forward_to_single_field_variant,
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
utils::{display_pat_members, gen_all_variants_with, gen_unused_pat},
|
||||
};
|
||||
use crate::{
|
||||
fmt::{self, Display},
|
||||
forward::WhichFn,
|
||||
};
|
||||
|
||||
pub enum Url {
|
||||
|
|
@ -33,7 +33,6 @@ impl Parse for Url {
|
|||
let args = if content.is_empty() {
|
||||
TokenStream::new()
|
||||
} else {
|
||||
content.parse::<Token![,]>()?;
|
||||
fmt::parse_token_expr(&content, false)?
|
||||
};
|
||||
let display = Display {
|
||||
|
|
@ -69,78 +68,37 @@ impl Url {
|
|||
enum_name: &syn::Ident,
|
||||
variants: &[DiagnosticDef],
|
||||
) -> Option<TokenStream> {
|
||||
let url_pairs = variants.iter().map(|variant| {
|
||||
let DiagnosticDef { ident, fields, args: def_args } = variant;
|
||||
match def_args {
|
||||
DiagnosticDefArgs::Transparent => {
|
||||
Some(forward_to_single_field_variant(
|
||||
ident,
|
||||
fields,
|
||||
quote! { url() },
|
||||
))
|
||||
}
|
||||
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { ref url, .. }) => {
|
||||
let member_idents = fields.iter().enumerate().map(|(i, field)| {
|
||||
field
|
||||
.ident
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format_ident!("_{}", i))
|
||||
});
|
||||
let members: HashSet<syn::Member> = fields.iter().enumerate().map(|(i, field)| {
|
||||
if let Some(ident) = field.ident.as_ref().cloned() {
|
||||
syn::Member::Named(ident)
|
||||
} else {
|
||||
syn::Member::Unnamed(syn::Index { index: i as u32, span: field.span() })
|
||||
}
|
||||
}).collect();
|
||||
let (fmt, args) = match url.as_ref()? {
|
||||
// fall through to `_ => None` below
|
||||
Url::Display(display) => {
|
||||
let mut display = display.clone();
|
||||
display.expand_shorthand(&members);
|
||||
let Display { fmt, args, .. } = display;
|
||||
(fmt.value(), args)
|
||||
}
|
||||
Url::DocsRs => {
|
||||
let fmt = "https://docs.rs/{crate_name}/{crate_version}/{crate_name}/{item_path}".into();
|
||||
let item_path = format!("enum.{}.html#variant.{}", enum_name, ident);
|
||||
let args = quote! {
|
||||
crate_name=env!("CARGO_PKG_NAME"),
|
||||
crate_version=env!("CARGO_PKG_VERSION"),
|
||||
item_path=#item_path
|
||||
};
|
||||
(fmt, args)
|
||||
}
|
||||
};
|
||||
Some(match fields {
|
||||
syn::Fields::Named(_) => {
|
||||
quote! { Self::#ident{ #(#member_idents),* } => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), }
|
||||
}
|
||||
syn::Fields::Unnamed(_) => {
|
||||
quote! { Self::#ident( #(#member_idents),* ) => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), }
|
||||
}
|
||||
syn::Fields::Unit =>
|
||||
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), },
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
if url_pairs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(quote! {
|
||||
fn url<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#[allow(unused_variables, deprecated)]
|
||||
match self {
|
||||
#(#url_pairs)*
|
||||
_ => None,
|
||||
gen_all_variants_with(
|
||||
variants,
|
||||
WhichFn::Url,
|
||||
|ident, fields, DiagnosticConcreteArgs { url, .. }| {
|
||||
let (pat, fmt, args) = match url.as_ref()? {
|
||||
// fall through to `_ => None` below
|
||||
Url::Display(display) => {
|
||||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
(display_pat, fmt.value(), args)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Url::DocsRs => {
|
||||
let pat = gen_unused_pat(fields);
|
||||
let fmt =
|
||||
"https://docs.rs/{crate_name}/{crate_version}/{crate_name}/{item_path}"
|
||||
.into();
|
||||
let item_path = format!("enum.{}.html#variant.{}", enum_name, ident);
|
||||
let args = quote! {
|
||||
,
|
||||
crate_name=env!("CARGO_PKG_NAME"),
|
||||
crate_version=env!("CARGO_PKG_VERSION"),
|
||||
item_path=#item_path
|
||||
};
|
||||
(pat, fmt, args)
|
||||
}
|
||||
};
|
||||
Some(quote! {
|
||||
Self::#ident #pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))),
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn gen_struct(
|
||||
|
|
@ -148,63 +106,31 @@ impl Url {
|
|||
struct_name: &syn::Ident,
|
||||
fields: &Fields,
|
||||
) -> Option<TokenStream> {
|
||||
let members: HashSet<syn::Member> = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
if let Some(ident) = field.ident.as_ref().cloned() {
|
||||
syn::Member::Named(ident)
|
||||
} else {
|
||||
syn::Member::Unnamed(syn::Index {
|
||||
index: i as u32,
|
||||
span: field.span(),
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let (fmt, args) = match self {
|
||||
let (pat, fmt, args) = match self {
|
||||
Url::Display(display) => {
|
||||
let mut display = display.clone();
|
||||
display.expand_shorthand(&members);
|
||||
let Display { fmt, args, .. } = display;
|
||||
(fmt.value(), args)
|
||||
let (display_pat, display_members) = display_pat_members(fields);
|
||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||
(display_pat, fmt.value(), args)
|
||||
}
|
||||
Url::DocsRs => {
|
||||
let pat = gen_unused_pat(fields);
|
||||
let fmt =
|
||||
"https://docs.rs/{crate_name}/{crate_version}/{crate_name}/{item_path}".into();
|
||||
let item_path = format!("struct.{}.html", struct_name);
|
||||
let args = quote! {
|
||||
,
|
||||
crate_name=env!("CARGO_PKG_NAME"),
|
||||
crate_version=env!("CARGO_PKG_VERSION"),
|
||||
item_path=#item_path
|
||||
};
|
||||
(fmt, args)
|
||||
(pat, fmt, args)
|
||||
}
|
||||
};
|
||||
let members = members.iter();
|
||||
let fields_pat = match fields {
|
||||
Fields::Named(_) => quote! {
|
||||
let Self { #(#members),* } = self;
|
||||
},
|
||||
Fields::Unnamed(_) => {
|
||||
let vars = members.map(|member| {
|
||||
if let syn::Member::Unnamed(member) = member {
|
||||
format_ident!("_{}", member)
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
quote! {
|
||||
let Self(#(#vars),*) = self;
|
||||
}
|
||||
}
|
||||
Fields::Unit => quote! {},
|
||||
};
|
||||
Some(quote! {
|
||||
fn url<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||
#[allow(unused_variables, deprecated)]
|
||||
#fields_pat
|
||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args)))
|
||||
let Self #pat = self;
|
||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
spanned::Spanned,
|
||||
};
|
||||
|
||||
pub(crate) enum MemberOrString {
|
||||
Member(syn::Member),
|
||||
|
|
@ -33,34 +36,103 @@ impl Parse for MemberOrString {
|
|||
}
|
||||
}
|
||||
|
||||
// bool here is whether to use curly braces
|
||||
pub fn single_field_name(fields: &syn::Fields) -> Option<(bool, &syn::Field)> {
|
||||
use crate::{
|
||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||
forward::WhichFn,
|
||||
};
|
||||
|
||||
pub(crate) fn gen_all_variants_with(
|
||||
variants: &[DiagnosticDef],
|
||||
which_fn: WhichFn,
|
||||
mut f: impl FnMut(&syn::Ident, &syn::Fields, &DiagnosticConcreteArgs) -> Option<TokenStream>,
|
||||
) -> Option<TokenStream> {
|
||||
let pairs = variants
|
||||
.iter()
|
||||
.map(|def| {
|
||||
def.args
|
||||
.forward_or_override_enum(&def.ident, which_fn, |concrete| {
|
||||
f(&def.ident, &def.fields, concrete)
|
||||
})
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
if pairs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let signature = which_fn.signature();
|
||||
let catchall = which_fn.catchall_arm();
|
||||
Some(quote! {
|
||||
#signature {
|
||||
#[allow(unused_variables, deprecated)]
|
||||
match self {
|
||||
#(#pairs)*
|
||||
#catchall
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
use crate::fmt::Display;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub(crate) fn gen_unused_pat(fields: &syn::Fields) -> TokenStream {
|
||||
match fields {
|
||||
syn::Fields::Named(f) if f.named.len() == 1 => f.named.first().map(|x| (true, x)),
|
||||
syn::Fields::Unnamed(f) if f.unnamed.len() == 1 => f.unnamed.first().map(|x| (false, x)),
|
||||
_ => None,
|
||||
syn::Fields::Named(_) => quote! { { .. } },
|
||||
syn::Fields::Unnamed(_) => quote! { ( .. ) },
|
||||
syn::Fields::Unit => quote! {},
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a match arm
|
||||
pub fn forward_to_single_field_variant(
|
||||
ident: &syn::Ident,
|
||||
fields: &syn::Fields,
|
||||
method_call: TokenStream,
|
||||
) -> TokenStream {
|
||||
if let Some((curly, single_field)) = single_field_name(fields) {
|
||||
let field_name = single_field
|
||||
/// Goes in the slot `let Self #pat = self;` or `match self { Self #pat => ... }`.
|
||||
fn gen_fields_pat(fields: &syn::Fields) -> TokenStream {
|
||||
let member_idents = fields.iter().enumerate().map(|(i, field)| {
|
||||
field
|
||||
.ident
|
||||
.clone()
|
||||
.unwrap_or_else(|| format_ident!("unnamed"));
|
||||
if curly {
|
||||
quote! { Self::#ident { #field_name } => #field_name.#method_call, }
|
||||
} else {
|
||||
quote! { Self::#ident(#field_name) => #field_name.#method_call, }
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
_ => compile_error!("miette: used `#[diagnostic(transparent)]` on variant without one single field"),
|
||||
}
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format_ident!("_{}", i))
|
||||
});
|
||||
match fields {
|
||||
syn::Fields::Named(_) => quote! {
|
||||
{ #(#member_idents),* }
|
||||
},
|
||||
syn::Fields::Unnamed(_) => quote! {
|
||||
( #(#member_idents),* )
|
||||
},
|
||||
syn::Fields::Unit => quote! {},
|
||||
}
|
||||
}
|
||||
|
||||
/// The returned tokens go in the slot `let Self #pat = self;` or `match self { Self #pat => ... }`.
|
||||
/// The members can be passed to `Display::expand_shorthand[_cloned]`.
|
||||
pub(crate) fn display_pat_members(fields: &syn::Fields) -> (TokenStream, HashSet<syn::Member>) {
|
||||
let pat = gen_fields_pat(fields);
|
||||
let members: HashSet<syn::Member> = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
if let Some(ident) = field.ident.as_ref().cloned() {
|
||||
syn::Member::Named(ident)
|
||||
} else {
|
||||
syn::Member::Unnamed(syn::Index {
|
||||
index: i as u32,
|
||||
span: field.span(),
|
||||
})
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
(pat, members)
|
||||
}
|
||||
|
||||
impl Display {
|
||||
/// Returns `(fmt, args)` which must be passed to some kind of format macro without tokens in between, i.e. `format!(#fmt #args)`.
|
||||
pub(crate) fn expand_shorthand_cloned(
|
||||
&self,
|
||||
members: &HashSet<syn::Member>,
|
||||
) -> (syn::LitStr, TokenStream) {
|
||||
let mut display = self.clone();
|
||||
display.expand_shorthand(members);
|
||||
let Display { fmt, args, .. } = display;
|
||||
(fmt, args)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,3 +92,38 @@ struct SingleFieldTests;
|
|||
#[allow(dead_code)]
|
||||
#[doc(hidden)]
|
||||
struct TransparentCombinations;
|
||||
|
||||
/// Forwarding without overriding the code (struct)
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use thiserror::Error;
|
||||
/// use miette_derive::Diagnostic;
|
||||
/// #[derive(Debug, Diagnostic, Error)]
|
||||
/// #[error("welp")]
|
||||
/// #[diagnostic(code(foo::bar::baz))]
|
||||
/// struct Foo {}
|
||||
/// #[derive(Debug, Diagnostic, Error)]
|
||||
/// #[error("welp")]
|
||||
/// #[diagnostic(forward(0))]
|
||||
/// struct Bar(Foo);
|
||||
/// ```
|
||||
///
|
||||
/// Forwarding without overriding the code (enum)
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// use thiserror::Error;
|
||||
/// use miette_derive::Diagnostic;
|
||||
/// #[derive(Debug, Diagnostic, Error)]
|
||||
/// #[error("welp")]
|
||||
/// #[diagnostic(code(foo::bar::baz))]
|
||||
/// struct Foo {}
|
||||
/// #[derive(Debug, Diagnostic, Error)]
|
||||
/// enum Enum {
|
||||
/// #[error("welp")]
|
||||
/// #[diagnostic(forward(0))]
|
||||
/// Bar(Foo) }
|
||||
/// ```
|
||||
///
|
||||
#[allow(dead_code)]
|
||||
#[doc(hidden)]
|
||||
struct ForwardWithoutCode;
|
||||
|
|
|
|||
124
tests/derive.rs
124
tests/derive.rs
|
|
@ -322,7 +322,8 @@ const SNIPPET_TEXT: &str = "hello from miette";
|
|||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("welp")]
|
||||
#[diagnostic(
|
||||
code(foo::bar::baz),
|
||||
// code not necessary.
|
||||
// code(foo::bar::baz),
|
||||
url("https://example.com"),
|
||||
help("help"),
|
||||
severity(Warning)
|
||||
|
|
@ -345,13 +346,16 @@ impl ForwardsTo {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_snippets(diag: &impl Diagnostic) {
|
||||
fn check_all(diag: &impl Diagnostic) {
|
||||
// check Diagnostic impl forwards all these methods
|
||||
assert_eq!(diag.code().unwrap().to_string(), "foo::bar::baz");
|
||||
assert_eq!(diag.code().as_ref().map(|x| x.to_string()), None);
|
||||
assert_eq!(diag.url().unwrap().to_string(), "https://example.com");
|
||||
assert_eq!(diag.help().unwrap().to_string(), "help");
|
||||
assert_eq!(diag.severity().unwrap(), miette::Severity::Warning);
|
||||
check_snippets(diag);
|
||||
}
|
||||
|
||||
fn check_snippets(diag: &impl Diagnostic) {
|
||||
type Snip = (Option<String>, usize, usize);
|
||||
let snips: Vec<(Snip, Vec<Snip>)> = diag
|
||||
.snippets()
|
||||
|
|
@ -394,7 +398,7 @@ fn test_transparent_enum_unnamed() {
|
|||
|
||||
let variant = Enum::FooVariant(ForwardsTo::new());
|
||||
|
||||
check_snippets(&variant);
|
||||
check_all(&variant);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -416,7 +420,7 @@ fn test_transparent_enum_named() {
|
|||
single_field: ForwardsTo::new(),
|
||||
};
|
||||
|
||||
check_snippets(&variant);
|
||||
check_all(&variant);
|
||||
|
||||
let bar_variant = Enum::BarVariant;
|
||||
assert_eq!(
|
||||
|
|
@ -436,7 +440,7 @@ fn test_transparent_struct_named() {
|
|||
}
|
||||
// Also check the From impl here
|
||||
let variant: Struct = ForwardsTo::new().into();
|
||||
check_snippets(&variant);
|
||||
check_all(&variant);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -446,5 +450,113 @@ fn test_transparent_struct_unnamed() {
|
|||
#[diagnostic(transparent)]
|
||||
struct Struct(#[from] ForwardsTo);
|
||||
let variant = Struct(ForwardsTo::new());
|
||||
check_all(&variant);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_forward_struct_named() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("display")]
|
||||
#[diagnostic(
|
||||
code(foo::bar::overridden),
|
||||
severity(Advice),
|
||||
help("{help}"),
|
||||
forward(span)
|
||||
)]
|
||||
struct Struct {
|
||||
span: ForwardsTo,
|
||||
help: &'static str,
|
||||
}
|
||||
// Also check the From impl here
|
||||
let diag = Struct {
|
||||
span: ForwardsTo::new(),
|
||||
help: "overridden help please",
|
||||
};
|
||||
assert_eq!(diag.code().unwrap().to_string(), "foo::bar::overridden");
|
||||
assert_eq!(diag.help().unwrap().to_string(), "overridden help please");
|
||||
assert_eq!(diag.severity(), Some(Severity::Advice));
|
||||
// this comes from <ForwardsTo as Diagnostic>::snippets()
|
||||
check_snippets(&diag);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_forward_struct_unnamed() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("display")]
|
||||
#[diagnostic(code(foo::bar::overridden), url("{1}"), forward(0))]
|
||||
struct Struct(ForwardsTo, &'static str);
|
||||
|
||||
// Also check the From impl here
|
||||
let diag = Struct(ForwardsTo::new(), "url here");
|
||||
assert_eq!(diag.code().unwrap().to_string(), "foo::bar::overridden");
|
||||
assert_eq!(diag.url().unwrap().to_string(), "url here");
|
||||
// this comes from <ForwardsTo as Diagnostic>::snippets()
|
||||
check_snippets(&diag);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_forward_enum_named() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
enum Enum {
|
||||
#[error("help: {help_text}")]
|
||||
#[diagnostic(code(foo::bar::overridden), help("{help_text}"), forward(span))]
|
||||
Variant {
|
||||
span: ForwardsTo,
|
||||
help_text: &'static str,
|
||||
},
|
||||
}
|
||||
// Also check the From impl here
|
||||
let variant: Enum = Enum::Variant {
|
||||
span: ForwardsTo::new(),
|
||||
help_text: "overridden help please",
|
||||
};
|
||||
assert_eq!(variant.code().unwrap().to_string(), "foo::bar::overridden");
|
||||
assert_eq!(
|
||||
variant.help().unwrap().to_string(),
|
||||
"overridden help please"
|
||||
);
|
||||
// this comes from <ForwardsTo as Diagnostic>::snippets()
|
||||
check_snippets(&variant);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_forward_enum_unnamed() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
enum ForwardEnumUnnamed {
|
||||
#[error("help: {1}")]
|
||||
#[diagnostic(code(foo::bar::overridden), help("{1}"), forward(0))]
|
||||
Variant(ForwardsTo, &'static str),
|
||||
}
|
||||
// Also check the From impl here
|
||||
let variant = ForwardEnumUnnamed::Variant(ForwardsTo::new(), "overridden help please");
|
||||
assert_eq!(variant.code().unwrap().to_string(), "foo::bar::overridden");
|
||||
assert_eq!(
|
||||
variant.help().unwrap().to_string(),
|
||||
"overridden help please"
|
||||
);
|
||||
// this comes from <ForwardsTo as Diagnostic>::snippets()
|
||||
check_snippets(&variant);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unit_struct_display() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
#[error("unit only")]
|
||||
#[diagnostic(code(foo::bar::overridden), help("hello from unit help"))]
|
||||
struct UnitOnly;
|
||||
assert_eq!(UnitOnly.help().unwrap().to_string(), "hello from unit help")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unit_enum_display() {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
enum Enum {
|
||||
#[error("unit only")]
|
||||
#[diagnostic(code(foo::bar::overridden), help("hello from unit help"))]
|
||||
UnitVariant,
|
||||
}
|
||||
assert_eq!(
|
||||
Enum::UnitVariant.help().unwrap().to_string(),
|
||||
"hello from unit help"
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,6 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
|
@ -234,8 +233,8 @@ fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
|||
|
||||
‽ try doing it better next time?
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
.trim_start()
|
||||
.to_string();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue