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::{
|
use crate::{
|
||||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||||
utils::forward_to_single_field_variant,
|
forward::WhichFn,
|
||||||
|
utils::gen_all_variants_with,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -48,41 +49,24 @@ impl Parse for Code {
|
||||||
|
|
||||||
impl Code {
|
impl Code {
|
||||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
let code_pairs = variants.iter().map(
|
gen_all_variants_with(
|
||||||
|DiagnosticDef {
|
variants,
|
||||||
ident,
|
WhichFn::Code,
|
||||||
fields,
|
|ident, fields, DiagnosticConcreteArgs { code, .. }| {
|
||||||
args,
|
let code = &code.as_ref()?.0;
|
||||||
}| {
|
Some(match fields {
|
||||||
match args {
|
syn::Fields::Named(_) => {
|
||||||
DiagnosticDefArgs::Transparent => {
|
quote! { Self::#ident { .. } => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||||
Some(forward_to_single_field_variant(ident, fields, quote! { code() }))
|
|
||||||
}
|
}
|
||||||
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { code, .. }) => {
|
syn::Fields::Unnamed(_) => {
|
||||||
let code = &code.as_ref()?.0;
|
quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(#code)), }
|
||||||
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::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> {
|
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote};
|
use quote::quote;
|
||||||
use syn::{punctuated::Punctuated, DeriveInput, Token};
|
use syn::{punctuated::Punctuated, DeriveInput, Token};
|
||||||
|
|
||||||
use crate::code::Code;
|
use crate::code::Code;
|
||||||
use crate::diagnostic_arg::DiagnosticArg;
|
use crate::diagnostic_arg::DiagnosticArg;
|
||||||
|
use crate::forward::{Forward, WhichFn};
|
||||||
use crate::help::Help;
|
use crate::help::Help;
|
||||||
use crate::severity::Severity;
|
use crate::severity::Severity;
|
||||||
use crate::snippets::Snippets;
|
use crate::snippets::Snippets;
|
||||||
|
|
@ -30,10 +31,29 @@ pub struct DiagnosticDef {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum DiagnosticDefArgs {
|
pub enum DiagnosticDefArgs {
|
||||||
Transparent,
|
Transparent(Forward),
|
||||||
Concrete(DiagnosticConcreteArgs),
|
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)]
|
#[derive(Default)]
|
||||||
pub struct DiagnosticConcreteArgs {
|
pub struct DiagnosticConcreteArgs {
|
||||||
pub code: Option<Code>,
|
pub code: Option<Code>,
|
||||||
|
|
@ -41,6 +61,7 @@ pub struct DiagnosticConcreteArgs {
|
||||||
pub help: Option<Help>,
|
pub help: Option<Help>,
|
||||||
pub snippets: Option<Snippets>,
|
pub snippets: Option<Snippets>,
|
||||||
pub url: Option<Url>,
|
pub url: Option<Url>,
|
||||||
|
pub forward: Option<Forward>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticConcreteArgs {
|
impl DiagnosticConcreteArgs {
|
||||||
|
|
@ -54,11 +75,15 @@ impl DiagnosticConcreteArgs {
|
||||||
let mut severity = None;
|
let mut severity = None;
|
||||||
let mut help = None;
|
let mut help = None;
|
||||||
let mut url = None;
|
let mut url = None;
|
||||||
|
let mut forward = None;
|
||||||
for arg in args {
|
for arg in args {
|
||||||
match arg {
|
match arg {
|
||||||
DiagnosticArg::Transparent => {
|
DiagnosticArg::Transparent => {
|
||||||
return Err(syn::Error::new_spanned(attr, "transparent not allowed"));
|
return Err(syn::Error::new_spanned(attr, "transparent not allowed"));
|
||||||
}
|
}
|
||||||
|
DiagnosticArg::Forward(to_field) => {
|
||||||
|
forward = Some(to_field);
|
||||||
|
}
|
||||||
DiagnosticArg::Code(new_code) => {
|
DiagnosticArg::Code(new_code) => {
|
||||||
// TODO: error on multiple?
|
// TODO: error on multiple?
|
||||||
code = Some(new_code);
|
code = Some(new_code);
|
||||||
|
|
@ -81,6 +106,7 @@ impl DiagnosticConcreteArgs {
|
||||||
severity,
|
severity,
|
||||||
snippets,
|
snippets,
|
||||||
url,
|
url,
|
||||||
|
forward,
|
||||||
};
|
};
|
||||||
Ok(concrete)
|
Ok(concrete)
|
||||||
}
|
}
|
||||||
|
|
@ -92,14 +118,15 @@ impl DiagnosticDefArgs {
|
||||||
fields: &syn::Fields,
|
fields: &syn::Fields,
|
||||||
attr: &syn::Attribute,
|
attr: &syn::Attribute,
|
||||||
allow_transparent: bool,
|
allow_transparent: bool,
|
||||||
) -> Result<Self, syn::Error> {
|
) -> syn::Result<Self> {
|
||||||
let args =
|
let args =
|
||||||
attr.parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated)?;
|
attr.parse_args_with(Punctuated::<DiagnosticArg, Token![,]>::parse_terminated)?;
|
||||||
if allow_transparent
|
if allow_transparent
|
||||||
&& args.len() == 1
|
&& args.len() == 1
|
||||||
&& matches!(args.first(), Some(DiagnosticArg::Transparent))
|
&& 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)) {
|
} else if args.iter().any(|x| matches!(x, DiagnosticArg::Transparent)) {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
attr,
|
attr,
|
||||||
|
|
@ -177,65 +204,55 @@ impl Diagnostic {
|
||||||
} => {
|
} => {
|
||||||
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
||||||
match args {
|
match args {
|
||||||
DiagnosticDefArgs::Transparent => {
|
DiagnosticDefArgs::Transparent(forward) => {
|
||||||
if fields.iter().len() != 1 {
|
let code_method = forward.gen_struct_method(WhichFn::Code);
|
||||||
return quote! {
|
let help_method = forward.gen_struct_method(WhichFn::Help);
|
||||||
compile_error!("you can only use #[diagnostic(transparent)] on a struct with exactly one field");
|
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);
|
||||||
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")
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
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>> {
|
#code_method
|
||||||
#matcher
|
#help_method
|
||||||
#field_name.code()
|
#url_method
|
||||||
}
|
#severity_method
|
||||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
#snippets_method
|
||||||
#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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DiagnosticDefArgs::Concrete(concrete) => {
|
DiagnosticDefArgs::Concrete(concrete) => {
|
||||||
let code_body = concrete.code.as_ref().and_then(|x| x.gen_struct());
|
let forward = |which| {
|
||||||
let help_body = concrete.help.as_ref().and_then(|x| x.gen_struct(fields));
|
concrete
|
||||||
let sev_body = concrete.severity.as_ref().and_then(|x| x.gen_struct());
|
.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
|
let snip_body = concrete
|
||||||
.snippets
|
.snippets
|
||||||
.as_ref()
|
.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
|
let url_body = concrete
|
||||||
.url
|
.url
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|x| x.gen_struct(ident, fields));
|
.and_then(|x| x.gen_struct(ident, fields))
|
||||||
|
.or_else(|| forward(WhichFn::Url));
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
|
||||||
use crate::code::Code;
|
use crate::code::Code;
|
||||||
|
use crate::forward::Forward;
|
||||||
use crate::help::Help;
|
use crate::help::Help;
|
||||||
use crate::severity::Severity;
|
use crate::severity::Severity;
|
||||||
use crate::url::Url;
|
use crate::url::Url;
|
||||||
|
|
@ -11,6 +12,7 @@ pub enum DiagnosticArg {
|
||||||
Severity(Severity),
|
Severity(Severity),
|
||||||
Help(Help),
|
Help(Help),
|
||||||
Url(Url),
|
Url(Url),
|
||||||
|
Forward(Forward),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for DiagnosticArg {
|
impl Parse for DiagnosticArg {
|
||||||
|
|
@ -20,6 +22,8 @@ impl Parse for DiagnosticArg {
|
||||||
// consume the token
|
// consume the token
|
||||||
let _: syn::Ident = input.parse()?;
|
let _: syn::Ident = input.parse()?;
|
||||||
Ok(DiagnosticArg::Transparent)
|
Ok(DiagnosticArg::Transparent)
|
||||||
|
} else if ident == "forward" {
|
||||||
|
Ok(DiagnosticArg::Forward(input.parse()?))
|
||||||
} else if ident == "code" {
|
} else if ident == "code" {
|
||||||
Ok(DiagnosticArg::Code(input.parse()?))
|
Ok(DiagnosticArg::Code(input.parse()?))
|
||||||
} else if ident == "severity" {
|
} 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 proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote};
|
use quote::quote;
|
||||||
use syn::{
|
use syn::{
|
||||||
parenthesized,
|
parenthesized,
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
spanned::Spanned,
|
|
||||||
Fields, Token,
|
Fields, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::fmt::{self, Display};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||||
utils::forward_to_single_field_variant,
|
utils::{display_pat_members, gen_all_variants_with},
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
fmt::{self, Display},
|
||||||
|
forward::WhichFn,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Help {
|
pub struct Help {
|
||||||
|
|
@ -31,7 +31,6 @@ impl Parse for Help {
|
||||||
let args = if content.is_empty() {
|
let args = if content.is_empty() {
|
||||||
TokenStream::new()
|
TokenStream::new()
|
||||||
} else {
|
} else {
|
||||||
content.parse::<Token![,]>()?;
|
|
||||||
fmt::parse_token_expr(&content, false)?
|
fmt::parse_token_expr(&content, false)?
|
||||||
};
|
};
|
||||||
let display = Display {
|
let display = Display {
|
||||||
|
|
@ -58,110 +57,28 @@ impl Parse for Help {
|
||||||
|
|
||||||
impl Help {
|
impl Help {
|
||||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
let help_pairs = variants
|
gen_all_variants_with(
|
||||||
.iter()
|
variants,
|
||||||
.map(
|
WhichFn::Help,
|
||||||
|DiagnosticDef {
|
|ident, fields, DiagnosticConcreteArgs { help, .. }| {
|
||||||
ident,
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
fields,
|
let display = &help.as_ref()?.display;
|
||||||
args,
|
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))),
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen_struct(&self, fields: &Fields) -> Option<TokenStream> {
|
pub(crate) fn gen_struct(&self, fields: &Fields) -> Option<TokenStream> {
|
||||||
let mut display = self.display.clone();
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
let members: HashSet<syn::Member> = fields
|
let (fmt, args) = self.display.expand_shorthand_cloned(&display_members);
|
||||||
.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! {},
|
|
||||||
};
|
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
fn help<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||||
#[allow(unused_variables, deprecated)]
|
#[allow(unused_variables, deprecated)]
|
||||||
#fields_pat
|
let Self #display_pat = self;
|
||||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args)))
|
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ mod code;
|
||||||
mod diagnostic;
|
mod diagnostic;
|
||||||
mod diagnostic_arg;
|
mod diagnostic_arg;
|
||||||
mod fmt;
|
mod fmt;
|
||||||
|
mod forward;
|
||||||
mod help;
|
mod help;
|
||||||
mod severity;
|
mod severity;
|
||||||
mod snippets;
|
mod snippets;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ use syn::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||||
utils::forward_to_single_field_variant,
|
forward::WhichFn,
|
||||||
|
utils::gen_all_variants_with,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Severity(pub syn::Ident);
|
pub struct Severity(pub syn::Ident);
|
||||||
|
|
@ -60,42 +61,21 @@ fn get_severity(input: &str, span: Span) -> syn::Result<String> {
|
||||||
|
|
||||||
impl Severity {
|
impl Severity {
|
||||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
let sev_pairs = variants
|
gen_all_variants_with(
|
||||||
.iter()
|
variants,
|
||||||
.map(
|
WhichFn::Severity,
|
||||||
|DiagnosticDef {
|
|ident, fields, DiagnosticConcreteArgs { severity, .. }| {
|
||||||
ident, fields, args
|
let severity = &severity.as_ref()?.0;
|
||||||
}| {
|
let fields = match fields {
|
||||||
match args {
|
syn::Fields::Named(_) => quote! { { .. } },
|
||||||
DiagnosticDefArgs::Transparent => {
|
syn::Fields::Unnamed(_) => quote! { (..) },
|
||||||
Some(forward_to_single_field_variant(ident, fields, quote!{ severity() }))
|
syn::Fields::Unit => quote! {},
|
||||||
}
|
};
|
||||||
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { severity, .. }) => {
|
Some(
|
||||||
let severity = &severity.as_ref()?.0;
|
quote! { Self::#ident #fields => std::option::Option::Some(miette::Severity::#severity), },
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
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 proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
|
|
@ -10,11 +10,10 @@ use syn::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnostic::DiagnosticConcreteArgs, fmt::Display, utils::forward_to_single_field_variant,
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||||
};
|
fmt::{self, Display},
|
||||||
use crate::{
|
forward::WhichFn,
|
||||||
diagnostic::{DiagnosticDef, DiagnosticDefArgs},
|
utils::{display_pat_members, gen_all_variants_with},
|
||||||
fmt,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Snippets(Vec<Snippet>);
|
pub struct Snippets(Vec<Snippet>);
|
||||||
|
|
@ -57,7 +56,6 @@ impl Parse for SnippetAttr {
|
||||||
let args = if content.is_empty() {
|
let args = if content.is_empty() {
|
||||||
TokenStream::new()
|
TokenStream::new()
|
||||||
} else {
|
} else {
|
||||||
content.parse::<Token![,]>()?;
|
|
||||||
fmt::parse_token_expr(&content, false)?
|
fmt::parse_token_expr(&content, false)?
|
||||||
};
|
};
|
||||||
let display = Display {
|
let display = Display {
|
||||||
|
|
@ -106,7 +104,6 @@ impl Parse for HighlightAttr {
|
||||||
let args = if content.is_empty() {
|
let args = if content.is_empty() {
|
||||||
TokenStream::new()
|
TokenStream::new()
|
||||||
} else {
|
} else {
|
||||||
content.parse::<Token![,]>()?;
|
|
||||||
fmt::parse_token_expr(&content, false)?
|
fmt::parse_token_expr(&content, false)?
|
||||||
};
|
};
|
||||||
let display = Display {
|
let display = Display {
|
||||||
|
|
@ -210,28 +207,15 @@ impl Snippets {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
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| {
|
let snippets = self.0.iter().map(|snippet| {
|
||||||
// snippet message
|
// snippet message
|
||||||
let msg = if let Some(display) = &snippet.message {
|
let msg = if let Some(display) = &snippet.message {
|
||||||
let members: HashSet<syn::Member> = fields
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
.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;
|
|
||||||
quote! {
|
quote! {
|
||||||
message: std::option::Option::Some(format!(#fmt, #args)),
|
message: {
|
||||||
|
std::option::Option::Some(format!(#fmt #args))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
|
|
@ -254,12 +238,11 @@ impl Snippets {
|
||||||
// Highlights
|
// Highlights
|
||||||
let highlights = snippet.highlights.iter().map(|highlight| {
|
let highlights = snippet.highlights.iter().map(|highlight| {
|
||||||
let Highlight { highlight, label } = 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! {
|
quote! {
|
||||||
(
|
(
|
||||||
std::option::Option::Some(
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
format!(#fmt, #args)
|
|
||||||
),
|
|
||||||
self.#highlight.clone().into()
|
self.#highlight.clone().into()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -288,6 +271,7 @@ impl Snippets {
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>> {
|
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![
|
Some(Box::new(vec![
|
||||||
#(#snippets),*
|
#(#snippets),*
|
||||||
].into_iter()))
|
].into_iter()))
|
||||||
|
|
@ -296,31 +280,16 @@ impl Snippets {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
let variant_arms = variants.iter().map(|variant| {
|
gen_all_variants_with(
|
||||||
let DiagnosticDef { ident, fields, args: def_args } = variant;
|
variants,
|
||||||
match def_args {
|
WhichFn::Snippets,
|
||||||
DiagnosticDefArgs::Transparent => {
|
|ident, fields, DiagnosticConcreteArgs { snippets, .. }| {
|
||||||
Some(forward_to_single_field_variant(
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
ident,
|
snippets.as_ref().and_then(|snippets| {
|
||||||
fields,
|
|
||||||
quote! { snippets() },
|
|
||||||
))
|
|
||||||
}
|
|
||||||
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { snippets, .. }) => {
|
|
||||||
snippets.as_ref().and_then(|snippets| {
|
|
||||||
let variant_snippets = snippets.0.iter().map(|snippet| {
|
let variant_snippets = snippets.0.iter().map(|snippet| {
|
||||||
// snippet message
|
// snippet message
|
||||||
let msg = if let Some(display) = &snippet.message {
|
let msg = if let Some(display) = &snippet.message {
|
||||||
let members: HashSet<syn::Member> = variant.fields.iter().enumerate().map(|(i, field)| {
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
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;
|
|
||||||
quote! {
|
quote! {
|
||||||
message: std::option::Option::Some(format!(#fmt, #args)),
|
message: std::option::Option::Some(format!(#fmt, #args)),
|
||||||
}
|
}
|
||||||
|
|
@ -390,40 +359,17 @@ impl Snippets {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let variant_name = variant.ident.clone();
|
let variant_name = ident.clone();
|
||||||
let members = variant.fields.iter().enumerate().map(|(i, field)| {
|
match &fields {
|
||||||
field
|
|
||||||
.ident
|
|
||||||
.as_ref()
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(|| format_ident!("_{}", i))
|
|
||||||
});
|
|
||||||
match &variant.fields {
|
|
||||||
syn::Fields::Unit => None,
|
syn::Fields::Unit => None,
|
||||||
syn::Fields::Named(_) => Some(quote! {
|
_ => Some(quote! {
|
||||||
Self::#variant_name { #(#members),* } => std::option::Option::Some(std::boxed::Box::new(vec![
|
Self::#variant_name #display_pat => 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![
|
|
||||||
#(#variant_snippets),*
|
#(#variant_snippets),*
|
||||||
].into_iter())),
|
].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 proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote};
|
use quote::quote;
|
||||||
use syn::{
|
use syn::{
|
||||||
parenthesized,
|
parenthesized,
|
||||||
parse::{Parse, ParseStream},
|
parse::{Parse, ParseStream},
|
||||||
spanned::Spanned,
|
|
||||||
Fields, Token,
|
Fields, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::fmt::{self, Display};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||||
utils::forward_to_single_field_variant,
|
utils::{display_pat_members, gen_all_variants_with, gen_unused_pat},
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
fmt::{self, Display},
|
||||||
|
forward::WhichFn,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum Url {
|
pub enum Url {
|
||||||
|
|
@ -33,7 +33,6 @@ impl Parse for Url {
|
||||||
let args = if content.is_empty() {
|
let args = if content.is_empty() {
|
||||||
TokenStream::new()
|
TokenStream::new()
|
||||||
} else {
|
} else {
|
||||||
content.parse::<Token![,]>()?;
|
|
||||||
fmt::parse_token_expr(&content, false)?
|
fmt::parse_token_expr(&content, false)?
|
||||||
};
|
};
|
||||||
let display = Display {
|
let display = Display {
|
||||||
|
|
@ -69,78 +68,37 @@ impl Url {
|
||||||
enum_name: &syn::Ident,
|
enum_name: &syn::Ident,
|
||||||
variants: &[DiagnosticDef],
|
variants: &[DiagnosticDef],
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
let url_pairs = variants.iter().map(|variant| {
|
gen_all_variants_with(
|
||||||
let DiagnosticDef { ident, fields, args: def_args } = variant;
|
variants,
|
||||||
match def_args {
|
WhichFn::Url,
|
||||||
DiagnosticDefArgs::Transparent => {
|
|ident, fields, DiagnosticConcreteArgs { url, .. }| {
|
||||||
Some(forward_to_single_field_variant(
|
let (pat, fmt, args) = match url.as_ref()? {
|
||||||
ident,
|
// fall through to `_ => None` below
|
||||||
fields,
|
Url::Display(display) => {
|
||||||
quote! { url() },
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
))
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
}
|
(display_pat, fmt.value(), args)
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
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(
|
pub(crate) fn gen_struct(
|
||||||
|
|
@ -148,63 +106,31 @@ impl Url {
|
||||||
struct_name: &syn::Ident,
|
struct_name: &syn::Ident,
|
||||||
fields: &Fields,
|
fields: &Fields,
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
let members: HashSet<syn::Member> = fields
|
let (pat, fmt, args) = match self {
|
||||||
.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 {
|
|
||||||
Url::Display(display) => {
|
Url::Display(display) => {
|
||||||
let mut display = display.clone();
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
display.expand_shorthand(&members);
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
let Display { fmt, args, .. } = display;
|
(display_pat, fmt.value(), args)
|
||||||
(fmt.value(), args)
|
|
||||||
}
|
}
|
||||||
Url::DocsRs => {
|
Url::DocsRs => {
|
||||||
|
let pat = gen_unused_pat(fields);
|
||||||
let fmt =
|
let fmt =
|
||||||
"https://docs.rs/{crate_name}/{crate_version}/{crate_name}/{item_path}".into();
|
"https://docs.rs/{crate_name}/{crate_version}/{crate_name}/{item_path}".into();
|
||||||
let item_path = format!("struct.{}.html", struct_name);
|
let item_path = format!("struct.{}.html", struct_name);
|
||||||
let args = quote! {
|
let args = quote! {
|
||||||
|
,
|
||||||
crate_name=env!("CARGO_PKG_NAME"),
|
crate_name=env!("CARGO_PKG_NAME"),
|
||||||
crate_version=env!("CARGO_PKG_VERSION"),
|
crate_version=env!("CARGO_PKG_VERSION"),
|
||||||
item_path=#item_path
|
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! {
|
Some(quote! {
|
||||||
fn url<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
fn url<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::fmt::Display + 'a>> {
|
||||||
#[allow(unused_variables, deprecated)]
|
#[allow(unused_variables, deprecated)]
|
||||||
#fields_pat
|
let Self #pat = self;
|
||||||
std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args)))
|
std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::{format_ident, quote, ToTokens};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::{
|
||||||
|
parse::{Parse, ParseStream},
|
||||||
|
spanned::Spanned,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) enum MemberOrString {
|
pub(crate) enum MemberOrString {
|
||||||
Member(syn::Member),
|
Member(syn::Member),
|
||||||
|
|
@ -33,34 +36,103 @@ impl Parse for MemberOrString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// bool here is whether to use curly braces
|
use crate::{
|
||||||
pub fn single_field_name(fields: &syn::Fields) -> Option<(bool, &syn::Field)> {
|
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 {
|
match fields {
|
||||||
syn::Fields::Named(f) if f.named.len() == 1 => f.named.first().map(|x| (true, x)),
|
syn::Fields::Named(_) => quote! { { .. } },
|
||||||
syn::Fields::Unnamed(f) if f.unnamed.len() == 1 => f.unnamed.first().map(|x| (false, x)),
|
syn::Fields::Unnamed(_) => quote! { ( .. ) },
|
||||||
_ => None,
|
syn::Fields::Unit => quote! {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a match arm
|
/// Goes in the slot `let Self #pat = self;` or `match self { Self #pat => ... }`.
|
||||||
pub fn forward_to_single_field_variant(
|
fn gen_fields_pat(fields: &syn::Fields) -> TokenStream {
|
||||||
ident: &syn::Ident,
|
let member_idents = fields.iter().enumerate().map(|(i, field)| {
|
||||||
fields: &syn::Fields,
|
field
|
||||||
method_call: TokenStream,
|
|
||||||
) -> TokenStream {
|
|
||||||
if let Some((curly, single_field)) = single_field_name(fields) {
|
|
||||||
let field_name = single_field
|
|
||||||
.ident
|
.ident
|
||||||
.clone()
|
.as_ref()
|
||||||
.unwrap_or_else(|| format_ident!("unnamed"));
|
.cloned()
|
||||||
if curly {
|
.unwrap_or_else(|| format_ident!("_{}", i))
|
||||||
quote! { Self::#ident { #field_name } => #field_name.#method_call, }
|
});
|
||||||
} else {
|
match fields {
|
||||||
quote! { Self::#ident(#field_name) => #field_name.#method_call, }
|
syn::Fields::Named(_) => quote! {
|
||||||
}
|
{ #(#member_idents),* }
|
||||||
} else {
|
},
|
||||||
quote! {
|
syn::Fields::Unnamed(_) => quote! {
|
||||||
_ => compile_error!("miette: used `#[diagnostic(transparent)]` on variant without one single field"),
|
( #(#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)]
|
#[allow(dead_code)]
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
struct TransparentCombinations;
|
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)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
#[error("welp")]
|
#[error("welp")]
|
||||||
#[diagnostic(
|
#[diagnostic(
|
||||||
code(foo::bar::baz),
|
// code not necessary.
|
||||||
|
// code(foo::bar::baz),
|
||||||
url("https://example.com"),
|
url("https://example.com"),
|
||||||
help("help"),
|
help("help"),
|
||||||
severity(Warning)
|
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
|
// 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.url().unwrap().to_string(), "https://example.com");
|
||||||
assert_eq!(diag.help().unwrap().to_string(), "help");
|
assert_eq!(diag.help().unwrap().to_string(), "help");
|
||||||
assert_eq!(diag.severity().unwrap(), miette::Severity::Warning);
|
assert_eq!(diag.severity().unwrap(), miette::Severity::Warning);
|
||||||
|
check_snippets(diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_snippets(diag: &impl Diagnostic) {
|
||||||
type Snip = (Option<String>, usize, usize);
|
type Snip = (Option<String>, usize, usize);
|
||||||
let snips: Vec<(Snip, Vec<Snip>)> = diag
|
let snips: Vec<(Snip, Vec<Snip>)> = diag
|
||||||
.snippets()
|
.snippets()
|
||||||
|
|
@ -394,7 +398,7 @@ fn test_transparent_enum_unnamed() {
|
||||||
|
|
||||||
let variant = Enum::FooVariant(ForwardsTo::new());
|
let variant = Enum::FooVariant(ForwardsTo::new());
|
||||||
|
|
||||||
check_snippets(&variant);
|
check_all(&variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -416,7 +420,7 @@ fn test_transparent_enum_named() {
|
||||||
single_field: ForwardsTo::new(),
|
single_field: ForwardsTo::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
check_snippets(&variant);
|
check_all(&variant);
|
||||||
|
|
||||||
let bar_variant = Enum::BarVariant;
|
let bar_variant = Enum::BarVariant;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -436,7 +440,7 @@ fn test_transparent_struct_named() {
|
||||||
}
|
}
|
||||||
// Also check the From impl here
|
// Also check the From impl here
|
||||||
let variant: Struct = ForwardsTo::new().into();
|
let variant: Struct = ForwardsTo::new().into();
|
||||||
check_snippets(&variant);
|
check_all(&variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -446,5 +450,113 @@ fn test_transparent_struct_unnamed() {
|
||||||
#[diagnostic(transparent)]
|
#[diagnostic(transparent)]
|
||||||
struct Struct(#[from] ForwardsTo);
|
struct Struct(#[from] ForwardsTo);
|
||||||
let variant = Struct(ForwardsTo::new());
|
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);
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -234,8 +233,8 @@ fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
||||||
|
|
||||||
‽ try doing it better next time?
|
‽ try doing it better next time?
|
||||||
"#
|
"#
|
||||||
.trim_start()
|
.trim_start()
|
||||||
.to_string();
|
.to_string();
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue