mirror of https://github.com/zkat/miette.git
feat(derive): Add `#[diagnostic(transparent,forward)]` (#36)
Fixes: https://github.com/zkat/miette/issues/16
This commit is contained in:
parent
2009ab238c
commit
53f5d6d1d6
|
|
@ -6,7 +6,10 @@ use syn::{
|
||||||
Token,
|
Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::diagnostic::DiagnosticVariant;
|
use crate::{
|
||||||
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
||||||
|
utils::forward_to_single_field_variant,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Code(pub String);
|
pub struct Code(pub String);
|
||||||
|
|
@ -44,23 +47,31 @@ impl Parse for Code {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Code {
|
impl Code {
|
||||||
pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option<TokenStream> {
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
let code_pairs = variants.iter().map(
|
let code_pairs = variants.iter().map(
|
||||||
|DiagnosticVariant {
|
|DiagnosticDef {
|
||||||
ref ident,
|
ident,
|
||||||
ref code,
|
fields,
|
||||||
ref fields,
|
args,
|
||||||
..
|
|
||||||
}| {
|
}| {
|
||||||
let code = &code.0;
|
match args {
|
||||||
match fields {
|
DiagnosticDefArgs::Transparent => {
|
||||||
syn::Fields::Named(_) => {
|
forward_to_single_field_variant(ident, fields, quote! { code() })
|
||||||
quote! { Self::#ident { .. } => std::boxed::Box::new(#code), }
|
|
||||||
}
|
}
|
||||||
syn::Fields::Unnamed(_) => {
|
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { code, .. }) => {
|
||||||
quote! { Self::#ident(..) => std::boxed::Box::new(#code), }
|
let code = &code.0;
|
||||||
|
match fields {
|
||||||
|
syn::Fields::Named(_) => {
|
||||||
|
quote! { Self::#ident { .. } => std::boxed::Box::new(#code), }
|
||||||
|
}
|
||||||
|
syn::Fields::Unnamed(_) => {
|
||||||
|
quote! { Self::#ident(..) => std::boxed::Box::new(#code), }
|
||||||
|
}
|
||||||
|
syn::Fields::Unit => {
|
||||||
|
quote! { Self::#ident => std::boxed::Box::new(#code), }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
syn::Fields::Unit => quote! { Self::#ident => std::boxed::Box::new(#code), },
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::quote;
|
use quote::{format_ident, quote};
|
||||||
use syn::{punctuated::Punctuated, DeriveInput, Token};
|
use syn::{punctuated::Punctuated, DeriveInput, Token};
|
||||||
|
|
||||||
use crate::code::Code;
|
use crate::code::Code;
|
||||||
|
|
@ -11,25 +11,30 @@ use crate::url::Url;
|
||||||
|
|
||||||
pub enum Diagnostic {
|
pub enum Diagnostic {
|
||||||
Struct {
|
Struct {
|
||||||
fields: syn::Fields,
|
|
||||||
ident: syn::Ident,
|
|
||||||
generics: syn::Generics,
|
generics: syn::Generics,
|
||||||
code: Code,
|
ident: syn::Ident,
|
||||||
severity: Option<Severity>,
|
fields: syn::Fields,
|
||||||
help: Option<Help>,
|
args: DiagnosticDefArgs,
|
||||||
snippets: Option<Snippets>,
|
|
||||||
url: Option<Url>,
|
|
||||||
},
|
},
|
||||||
Enum {
|
Enum {
|
||||||
ident: syn::Ident,
|
ident: syn::Ident,
|
||||||
generics: syn::Generics,
|
generics: syn::Generics,
|
||||||
variants: Vec<DiagnosticVariant>,
|
variants: Vec<DiagnosticDef>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DiagnosticVariant {
|
pub struct DiagnosticDef {
|
||||||
pub ident: syn::Ident,
|
pub ident: syn::Ident,
|
||||||
pub fields: syn::Fields,
|
pub fields: syn::Fields,
|
||||||
|
pub args: DiagnosticDefArgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DiagnosticDefArgs {
|
||||||
|
Transparent,
|
||||||
|
Concrete(DiagnosticConcreteArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DiagnosticConcreteArgs {
|
||||||
pub code: Code,
|
pub code: Code,
|
||||||
pub severity: Option<Severity>,
|
pub severity: Option<Severity>,
|
||||||
pub help: Option<Help>,
|
pub help: Option<Help>,
|
||||||
|
|
@ -37,46 +42,94 @@ pub struct DiagnosticVariant {
|
||||||
pub url: Option<Url>,
|
pub url: Option<Url>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DiagnosticConcreteArgs {
|
||||||
|
fn parse(
|
||||||
|
ident: &syn::Ident,
|
||||||
|
fields: &syn::Fields,
|
||||||
|
attr: &syn::Attribute,
|
||||||
|
args: impl Iterator<Item = DiagnosticArg>,
|
||||||
|
) -> Result<Self, syn::Error> {
|
||||||
|
let mut code = None;
|
||||||
|
let mut severity = None;
|
||||||
|
let mut help = None;
|
||||||
|
let mut url = None;
|
||||||
|
for arg in args {
|
||||||
|
match arg {
|
||||||
|
DiagnosticArg::Transparent => {
|
||||||
|
return Err(syn::Error::new_spanned(attr, "transparent not allowed"));
|
||||||
|
}
|
||||||
|
DiagnosticArg::Code(new_code) => {
|
||||||
|
// TODO: error on multiple?
|
||||||
|
code = Some(new_code);
|
||||||
|
}
|
||||||
|
DiagnosticArg::Severity(sev) => {
|
||||||
|
severity = Some(sev);
|
||||||
|
}
|
||||||
|
DiagnosticArg::Help(hl) => {
|
||||||
|
help = Some(hl);
|
||||||
|
}
|
||||||
|
DiagnosticArg::Url(u) => {
|
||||||
|
url = Some(u);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let snippets = Snippets::from_fields(fields)?;
|
||||||
|
let concrete = DiagnosticConcreteArgs {
|
||||||
|
code: code
|
||||||
|
.ok_or_else(|| syn::Error::new(ident.span(), "Diagnostic code is required."))?,
|
||||||
|
help,
|
||||||
|
severity,
|
||||||
|
snippets,
|
||||||
|
url,
|
||||||
|
};
|
||||||
|
Ok(concrete)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiagnosticDefArgs {
|
||||||
|
fn parse(
|
||||||
|
ident: &syn::Ident,
|
||||||
|
fields: &syn::Fields,
|
||||||
|
attr: &syn::Attribute,
|
||||||
|
allow_transparent: bool,
|
||||||
|
) -> Result<Self, syn::Error> {
|
||||||
|
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);
|
||||||
|
} else if args.iter().any(|x| matches!(x, DiagnosticArg::Transparent)) {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
attr,
|
||||||
|
if allow_transparent {
|
||||||
|
"diagnostic(transparent) not allowed in combination with other args"
|
||||||
|
} else {
|
||||||
|
"diagnostic(transparent) not allowed here"
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let args = args
|
||||||
|
.into_iter()
|
||||||
|
.filter(|x| !matches!(x, DiagnosticArg::Transparent));
|
||||||
|
let concrete = DiagnosticConcreteArgs::parse(ident, fields, attr, args)?;
|
||||||
|
Ok(DiagnosticDefArgs::Concrete(concrete))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Diagnostic {
|
impl Diagnostic {
|
||||||
pub fn from_derive_input(input: DeriveInput) -> Result<Self, syn::Error> {
|
pub fn from_derive_input(input: DeriveInput) -> Result<Self, syn::Error> {
|
||||||
Ok(match input.data {
|
Ok(match input.data {
|
||||||
syn::Data::Struct(data_struct) => {
|
syn::Data::Struct(data_struct) => {
|
||||||
if let Some(attr) = input.attrs.iter().find(|x| x.path.is_ident("diagnostic")) {
|
if let Some(attr) = input.attrs.iter().find(|x| x.path.is_ident("diagnostic")) {
|
||||||
let args = attr.parse_args_with(
|
let args =
|
||||||
Punctuated::<DiagnosticArg, Token![,]>::parse_terminated,
|
DiagnosticDefArgs::parse(&input.ident, &data_struct.fields, attr, true)?;
|
||||||
)?;
|
|
||||||
let mut code = None;
|
|
||||||
let mut severity = None;
|
|
||||||
let mut help = None;
|
|
||||||
let mut url = None;
|
|
||||||
for arg in args {
|
|
||||||
match arg {
|
|
||||||
DiagnosticArg::Code(new_code) => {
|
|
||||||
// TODO: error on multiple?
|
|
||||||
code = Some(new_code);
|
|
||||||
}
|
|
||||||
DiagnosticArg::Url(u) => {
|
|
||||||
url = Some(u);
|
|
||||||
}
|
|
||||||
DiagnosticArg::Severity(sev) => {
|
|
||||||
severity = Some(sev);
|
|
||||||
}
|
|
||||||
DiagnosticArg::Help(hl) => help = Some(hl),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let snippets = Snippets::from_fields(&data_struct.fields)?;
|
|
||||||
let ident = input.ident.clone();
|
|
||||||
Diagnostic::Struct {
|
Diagnostic::Struct {
|
||||||
fields: data_struct.fields,
|
fields: data_struct.fields,
|
||||||
ident: input.ident,
|
ident: input.ident,
|
||||||
generics: input.generics,
|
generics: input.generics,
|
||||||
code: code.ok_or_else(|| {
|
args,
|
||||||
syn::Error::new(ident.span(), "Diagnostic code is required.")
|
|
||||||
})?,
|
|
||||||
help,
|
|
||||||
severity,
|
|
||||||
snippets,
|
|
||||||
url,
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Also handle when there's multiple `#[diagnostic]` attrs?
|
// Also handle when there's multiple `#[diagnostic]` attrs?
|
||||||
|
|
@ -90,42 +143,11 @@ impl Diagnostic {
|
||||||
let mut vars = Vec::new();
|
let mut vars = Vec::new();
|
||||||
for var in variants {
|
for var in variants {
|
||||||
if let Some(attr) = var.attrs.iter().find(|x| x.path.is_ident("diagnostic")) {
|
if let Some(attr) = var.attrs.iter().find(|x| x.path.is_ident("diagnostic")) {
|
||||||
let args = attr.parse_args_with(
|
let args = DiagnosticDefArgs::parse(&var.ident, &var.fields, attr, true)?;
|
||||||
Punctuated::<DiagnosticArg, Token![,]>::parse_terminated,
|
vars.push(DiagnosticDef {
|
||||||
)?;
|
|
||||||
let mut code = None;
|
|
||||||
let mut severity = None;
|
|
||||||
let mut help = None;
|
|
||||||
let mut url = None;
|
|
||||||
for arg in args {
|
|
||||||
match arg {
|
|
||||||
DiagnosticArg::Code(new_code) => {
|
|
||||||
// TODO: error on multiple?
|
|
||||||
code = Some(new_code);
|
|
||||||
}
|
|
||||||
DiagnosticArg::Severity(sev) => {
|
|
||||||
severity = Some(sev);
|
|
||||||
}
|
|
||||||
DiagnosticArg::Help(hl) => {
|
|
||||||
help = Some(hl);
|
|
||||||
}
|
|
||||||
DiagnosticArg::Url(u) => {
|
|
||||||
url = Some(u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let snippets = Snippets::from_fields(&var.fields)?;
|
|
||||||
let ident = input.ident.clone();
|
|
||||||
vars.push(DiagnosticVariant {
|
|
||||||
ident: var.ident,
|
ident: var.ident,
|
||||||
fields: var.fields,
|
fields: var.fields,
|
||||||
code: code.ok_or_else(|| {
|
args,
|
||||||
syn::Error::new(ident.span(), "Diagnostic code is required.")
|
|
||||||
})?,
|
|
||||||
help,
|
|
||||||
severity,
|
|
||||||
snippets,
|
|
||||||
url,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Also handle when there's multiple `#[diagnostic]` attrs?
|
// Also handle when there's multiple `#[diagnostic]` attrs?
|
||||||
|
|
@ -153,29 +175,82 @@ impl Diagnostic {
|
||||||
pub fn gen(&self) -> TokenStream {
|
pub fn gen(&self) -> TokenStream {
|
||||||
match self {
|
match self {
|
||||||
Self::Struct {
|
Self::Struct {
|
||||||
fields,
|
|
||||||
ident,
|
ident,
|
||||||
|
fields,
|
||||||
generics,
|
generics,
|
||||||
code,
|
args,
|
||||||
severity,
|
|
||||||
help,
|
|
||||||
snippets,
|
|
||||||
url,
|
|
||||||
} => {
|
} => {
|
||||||
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = &generics.split_for_impl();
|
||||||
let code_body = code.gen_struct();
|
match args {
|
||||||
let help_body = help.as_ref().and_then(|x| x.gen_struct(fields));
|
DiagnosticDefArgs::Transparent => {
|
||||||
let sev_body = severity.as_ref().and_then(|x| x.gen_struct());
|
if fields.iter().len() != 1 {
|
||||||
let snip_body = snippets.as_ref().and_then(|x| x.gen_struct(fields));
|
return quote! {
|
||||||
let url_body = url.as_ref().and_then(|x| x.gen_struct(ident, fields));
|
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")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||||
#code_body
|
fn code<'a>(&'a self) -> std::boxed::Box<dyn std::fmt::Display + 'a> {
|
||||||
#help_body
|
#matcher
|
||||||
#sev_body
|
#field_name.code()
|
||||||
#snip_body
|
}
|
||||||
#url_body
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DiagnosticDefArgs::Concrete(concrete) => {
|
||||||
|
let code_body = concrete.code.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 snip_body = concrete
|
||||||
|
.snippets
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.gen_struct(fields));
|
||||||
|
let url_body = concrete
|
||||||
|
.url
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.gen_struct(ident, fields));
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||||
|
#code_body
|
||||||
|
#help_body
|
||||||
|
#sev_body
|
||||||
|
#snip_body
|
||||||
|
#url_body
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use crate::severity::Severity;
|
||||||
use crate::url::Url;
|
use crate::url::Url;
|
||||||
|
|
||||||
pub enum DiagnosticArg {
|
pub enum DiagnosticArg {
|
||||||
|
Transparent,
|
||||||
Code(Code),
|
Code(Code),
|
||||||
Severity(Severity),
|
Severity(Severity),
|
||||||
Help(Help),
|
Help(Help),
|
||||||
|
|
@ -15,7 +16,11 @@ pub enum DiagnosticArg {
|
||||||
impl Parse for DiagnosticArg {
|
impl Parse for DiagnosticArg {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
let ident = input.fork().parse::<syn::Ident>()?;
|
let ident = input.fork().parse::<syn::Ident>()?;
|
||||||
if ident == "code" {
|
if ident == "transparent" {
|
||||||
|
// consume the token
|
||||||
|
let _: syn::Ident = input.parse()?;
|
||||||
|
Ok(DiagnosticArg::Transparent)
|
||||||
|
} else if ident == "code" {
|
||||||
Ok(DiagnosticArg::Code(input.parse()?))
|
Ok(DiagnosticArg::Code(input.parse()?))
|
||||||
} else if ident == "severity" {
|
} else if ident == "severity" {
|
||||||
Ok(DiagnosticArg::Severity(input.parse()?))
|
Ok(DiagnosticArg::Severity(input.parse()?))
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,11 @@ use syn::{
|
||||||
Fields, Token,
|
Fields, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::diagnostic::DiagnosticVariant;
|
|
||||||
use crate::fmt::{self, Display};
|
use crate::fmt::{self, Display};
|
||||||
|
use crate::{
|
||||||
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
||||||
|
utils::forward_to_single_field_variant,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Help {
|
pub struct Help {
|
||||||
pub display: Display,
|
pub display: Display,
|
||||||
|
|
@ -54,46 +57,53 @@ impl Parse for Help {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Help {
|
impl Help {
|
||||||
pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option<TokenStream> {
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
let help_pairs = variants
|
let help_pairs = variants
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|v| v.help.is_some())
|
|
||||||
.map(
|
.map(
|
||||||
|DiagnosticVariant {
|
|DiagnosticDef {
|
||||||
ref ident,
|
ident,
|
||||||
ref help,
|
fields,
|
||||||
ref fields,
|
args,
|
||||||
..
|
..
|
||||||
}| {
|
}| {
|
||||||
let mut display = help.as_ref().expect("already checked for Some").display.clone();
|
match args {
|
||||||
let member_idents = fields.iter().enumerate().map(|(i, field)| {
|
DiagnosticDefArgs::Transparent => {
|
||||||
field
|
Some(forward_to_single_field_variant(ident, fields, quote!{ help() } ))
|
||||||
.ident
|
}
|
||||||
.as_ref()
|
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { help, .. }) => {
|
||||||
.cloned()
|
let mut display = help.as_ref()?.display.clone();
|
||||||
.unwrap_or_else(|| format_ident!("_{}", i))
|
let member_idents = fields.iter().enumerate().map(|(i, field)| {
|
||||||
});
|
field
|
||||||
let members: HashSet<syn::Member> = fields.iter().enumerate().map(|(i, field)| {
|
.ident
|
||||||
if let Some(ident) = field.ident.as_ref().cloned() {
|
.as_ref()
|
||||||
syn::Member::Named(ident)
|
.cloned()
|
||||||
} else {
|
.unwrap_or_else(|| format_ident!("_{}", i))
|
||||||
syn::Member::Unnamed(syn::Index { index: i as u32, span: field.span() })
|
});
|
||||||
}
|
let members: HashSet<syn::Member> = fields.iter().enumerate().map(|(i, field)| {
|
||||||
}).collect();
|
if let Some(ident) = field.ident.as_ref().cloned() {
|
||||||
display.expand_shorthand(&members);
|
syn::Member::Named(ident)
|
||||||
let Display { fmt, args, .. } = display;
|
} else {
|
||||||
match fields {
|
syn::Member::Unnamed(syn::Index { index: i as u32, span: field.span() })
|
||||||
syn::Fields::Named(_) => {
|
}
|
||||||
quote! { Self::#ident{ #(#member_idents),* } => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), }
|
}).collect();
|
||||||
}
|
display.expand_shorthand(&members);
|
||||||
syn::Fields::Unnamed(_) => {
|
let Display { fmt, args, .. } = display;
|
||||||
quote! { Self::#ident( #(#member_idents),* ) => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), }
|
Some(match fields {
|
||||||
}
|
syn::Fields::Named(_) => {
|
||||||
syn::Fields::Unit =>
|
quote! { Self::#ident{ #(#member_idents),* } => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), }
|
||||||
quote! { Self::#ident => 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<_>>();
|
.collect::<Vec<_>>();
|
||||||
if help_pairs.is_empty() {
|
if help_pairs.is_empty() {
|
||||||
None
|
None
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@ use syn::{
|
||||||
Token,
|
Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::diagnostic::DiagnosticVariant;
|
use crate::{
|
||||||
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
||||||
|
utils::forward_to_single_field_variant,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Severity(pub syn::Ident);
|
pub struct Severity(pub syn::Ident);
|
||||||
|
|
||||||
|
|
@ -56,23 +59,30 @@ fn get_severity(input: &str, span: Span) -> syn::Result<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Severity {
|
impl Severity {
|
||||||
pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option<TokenStream> {
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
let sev_pairs = variants
|
let sev_pairs = variants
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|v| v.severity.is_some())
|
|
||||||
.map(
|
.map(
|
||||||
|DiagnosticVariant {
|
|DiagnosticDef {
|
||||||
ident, severity, fields, ..
|
ident, fields, args
|
||||||
}| {
|
}| {
|
||||||
let severity = &severity.as_ref().unwrap().0;
|
match args {
|
||||||
let fields = match fields {
|
DiagnosticDefArgs::Transparent => {
|
||||||
syn::Fields::Named(_) => quote! { { .. } },
|
Some(forward_to_single_field_variant(ident, fields, quote!{ severity() }))
|
||||||
syn::Fields::Unnamed(_) => quote! { (..) },
|
}
|
||||||
syn::Fields::Unit => quote!{},
|
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { severity, .. }) => {
|
||||||
};
|
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<_>>();
|
.collect::<Vec<_>>();
|
||||||
if sev_pairs.is_empty() {
|
if sev_pairs.is_empty() {
|
||||||
None
|
None
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,13 @@ use syn::{
|
||||||
Token,
|
Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::fmt;
|
use crate::{
|
||||||
use crate::{diagnostic::DiagnosticVariant, fmt::Display};
|
diagnostic::DiagnosticConcreteArgs, fmt::Display, utils::forward_to_single_field_variant,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
diagnostic::{DiagnosticDef, DiagnosticDefArgs},
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Snippets(Vec<Snippet>);
|
pub struct Snippets(Vec<Snippet>);
|
||||||
|
|
||||||
|
|
@ -290,114 +295,127 @@ impl Snippets {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn gen_enum(variants: &[DiagnosticVariant]) -> Option<TokenStream> {
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
let variant_arms = variants.iter().map(|variant| {
|
let variant_arms = variants.iter().map(|variant| {
|
||||||
variant.snippets.as_ref().map(|snippets| {
|
let DiagnosticDef { ident, fields, args: def_args } = variant;
|
||||||
let variant_snippets = snippets.0.iter().map(|snippet| {
|
match def_args {
|
||||||
// snippet message
|
DiagnosticDefArgs::Transparent => {
|
||||||
let msg = if let Some(display) = &snippet.message {
|
Some(forward_to_single_field_variant(
|
||||||
let members: HashSet<syn::Member> = variant.fields.iter().enumerate().map(|(i, field)| {
|
ident,
|
||||||
if let Some(ident) = field.ident.as_ref().cloned() {
|
fields,
|
||||||
syn::Member::Named(ident)
|
quote! { snippets() },
|
||||||
} 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! {
|
|
||||||
message: std::option::Option::Some(format!(#fmt, #args)),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
message: std::option::Option::None,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Source field
|
|
||||||
let src_ident = match &snippet.source {
|
|
||||||
syn::Member::Named(id) => id.clone(),
|
|
||||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
|
||||||
format_ident!("_{}", index)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let src_ident = quote! {
|
|
||||||
// TODO: I don't like this. Think about it more and maybe improve protocol?
|
|
||||||
source: #src_ident,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Context
|
|
||||||
let context = match &snippet.snippet {
|
|
||||||
syn::Member::Named(id) => id.clone(),
|
|
||||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
|
||||||
format_ident!("_{}", index)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let context = quote! {
|
|
||||||
context: #context.clone().into(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Highlights
|
|
||||||
let highlights = snippet.highlights.iter().map(|highlight| {
|
|
||||||
let Highlight { highlight, label } = highlight;
|
|
||||||
let m = match highlight {
|
|
||||||
syn::Member::Named(id) => id.clone(),
|
|
||||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
|
||||||
format_ident!("_{}", index)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if let Some(Display { fmt, args, ..}) = label {
|
|
||||||
quote! {
|
|
||||||
(
|
|
||||||
std::option::Option::Some(format!(#fmt, #args)),
|
|
||||||
#m.clone().into()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
(std::option::Option::None, #m.clone().into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let highlights = quote! {
|
|
||||||
highlights: std::option::Option::Some(vec![
|
|
||||||
#(#highlights),*
|
|
||||||
]),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate the snippet itself
|
|
||||||
quote! {
|
|
||||||
miette::DiagnosticSnippet {
|
|
||||||
#msg
|
|
||||||
#src_ident
|
|
||||||
#context
|
|
||||||
#highlights
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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 {
|
|
||||||
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![
|
|
||||||
#(#variant_snippets),*
|
|
||||||
].into_iter())),
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
})
|
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { snippets, .. }) => {
|
||||||
});
|
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;
|
||||||
|
quote! {
|
||||||
|
message: std::option::Option::Some(format!(#fmt, #args)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
message: std::option::Option::None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Source field
|
||||||
|
let src_ident = match &snippet.source {
|
||||||
|
syn::Member::Named(id) => id.clone(),
|
||||||
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
format_ident!("_{}", index)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let src_ident = quote! {
|
||||||
|
// TODO: I don't like this. Think about it more and maybe improve protocol?
|
||||||
|
source: #src_ident,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Context
|
||||||
|
let context = match &snippet.snippet {
|
||||||
|
syn::Member::Named(id) => id.clone(),
|
||||||
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
format_ident!("_{}", index)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let context = quote! {
|
||||||
|
context: #context.clone().into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Highlights
|
||||||
|
let highlights = snippet.highlights.iter().map(|highlight| {
|
||||||
|
let Highlight { highlight, label } = highlight;
|
||||||
|
let m = match highlight {
|
||||||
|
syn::Member::Named(id) => id.clone(),
|
||||||
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
format_ident!("_{}", index)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Some(Display { fmt, args, ..}) = label {
|
||||||
|
quote! {
|
||||||
|
(
|
||||||
|
std::option::Option::Some(format!(#fmt, #args)),
|
||||||
|
#m.clone().into()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
(std::option::Option::None, #m.clone().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let highlights = quote! {
|
||||||
|
highlights: std::option::Option::Some(vec![
|
||||||
|
#(#highlights),*
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate the snippet itself
|
||||||
|
quote! {
|
||||||
|
miette::DiagnosticSnippet {
|
||||||
|
#msg
|
||||||
|
#src_ident
|
||||||
|
#context
|
||||||
|
#highlights
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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 {
|
||||||
|
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![
|
||||||
|
#(#variant_snippets),*
|
||||||
|
].into_iter())),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
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> + '_>> {
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,11 @@ use syn::{
|
||||||
Fields, Token,
|
Fields, Token,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::diagnostic::DiagnosticVariant;
|
|
||||||
use crate::fmt::{self, Display};
|
use crate::fmt::{self, Display};
|
||||||
|
use crate::{
|
||||||
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef, DiagnosticDefArgs},
|
||||||
|
utils::forward_to_single_field_variant,
|
||||||
|
};
|
||||||
|
|
||||||
pub enum Url {
|
pub enum Url {
|
||||||
Display(Display),
|
Display(Display),
|
||||||
|
|
@ -64,18 +67,19 @@ impl Parse for Url {
|
||||||
impl Url {
|
impl Url {
|
||||||
pub(crate) fn gen_enum(
|
pub(crate) fn gen_enum(
|
||||||
enum_name: &syn::Ident,
|
enum_name: &syn::Ident,
|
||||||
variants: &[DiagnosticVariant],
|
variants: &[DiagnosticDef],
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
let url_pairs = variants
|
let url_pairs = variants.iter().map(|variant| {
|
||||||
.iter()
|
let DiagnosticDef { ident, fields, args: def_args } = variant;
|
||||||
.filter(|v| v.url.is_some())
|
match def_args {
|
||||||
.map(
|
DiagnosticDefArgs::Transparent => {
|
||||||
|DiagnosticVariant {
|
Some(forward_to_single_field_variant(
|
||||||
ref ident,
|
ident,
|
||||||
ref url,
|
fields,
|
||||||
ref fields,
|
quote! { url() },
|
||||||
..
|
))
|
||||||
}| {
|
}
|
||||||
|
DiagnosticDefArgs::Concrete(DiagnosticConcreteArgs { ref url, .. }) => {
|
||||||
let member_idents = fields.iter().enumerate().map(|(i, field)| {
|
let member_idents = fields.iter().enumerate().map(|(i, field)| {
|
||||||
field
|
field
|
||||||
.ident
|
.ident
|
||||||
|
|
@ -90,7 +94,8 @@ impl Url {
|
||||||
syn::Member::Unnamed(syn::Index { index: i as u32, span: field.span() })
|
syn::Member::Unnamed(syn::Index { index: i as u32, span: field.span() })
|
||||||
}
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
let (fmt, args) = match url.as_ref().expect("MIETTE BUG: we already checked for `Some`") {
|
let (fmt, args) = match url.as_ref()? {
|
||||||
|
// fall through to `_ => None` below
|
||||||
Url::Display(display) => {
|
Url::Display(display) => {
|
||||||
let mut display = display.clone();
|
let mut display = display.clone();
|
||||||
display.expand_shorthand(&members);
|
display.expand_shorthand(&members);
|
||||||
|
|
@ -108,7 +113,7 @@ impl Url {
|
||||||
(fmt, args)
|
(fmt, args)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match fields {
|
Some(match fields {
|
||||||
syn::Fields::Named(_) => {
|
syn::Fields::Named(_) => {
|
||||||
quote! { Self::#ident{ #(#member_idents),* } => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), }
|
quote! { Self::#ident{ #(#member_idents),* } => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), }
|
||||||
}
|
}
|
||||||
|
|
@ -117,10 +122,12 @@ impl Url {
|
||||||
}
|
}
|
||||||
syn::Fields::Unit =>
|
syn::Fields::Unit =>
|
||||||
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), },
|
quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(format!(#fmt, #args))), },
|
||||||
}
|
})
|
||||||
},
|
}
|
||||||
)
|
}
|
||||||
.collect::<Vec<_>>();
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
if url_pairs.is_empty() {
|
if url_pairs.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use quote::ToTokens;
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use syn::parse::{Parse, ParseStream};
|
use syn::parse::{Parse, ParseStream};
|
||||||
|
|
||||||
pub(crate) enum MemberOrString {
|
pub(crate) enum MemberOrString {
|
||||||
|
|
@ -32,3 +32,35 @@ impl Parse for MemberOrString {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bool here is whether to use curly braces
|
||||||
|
pub fn single_field_name(fields: &syn::Fields) -> Option<(bool, &syn::Field)> {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
.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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
//! A hacky but perfectly good method of adding compile_fail doctests. You can't do this in a
|
||||||
|
//! regular tests/blah.rs file.
|
||||||
|
|
||||||
|
/// ```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 Variants {
|
||||||
|
/// #[error("no")]
|
||||||
|
/// #[diagnostic(transparent)]
|
||||||
|
/// One,
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```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 Variants {
|
||||||
|
/// #[error("no")]
|
||||||
|
/// #[diagnostic(transparent)]
|
||||||
|
/// One {
|
||||||
|
/// one: Foo,
|
||||||
|
/// two: u32,
|
||||||
|
/// },
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```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 Variants {
|
||||||
|
/// #[error("no")]
|
||||||
|
/// #[diagnostic(transparent)]
|
||||||
|
/// One(Foo, u32),
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
struct SingleFieldTests;
|
||||||
|
|
||||||
|
/// Directly on a struct with any other arg
|
||||||
|
///
|
||||||
|
/// ```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(transparent, code(invalid::combo))]
|
||||||
|
/// struct Bar(Foo);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// With any other arg to diagnostic()
|
||||||
|
///
|
||||||
|
/// ```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 Variants {
|
||||||
|
/// #[error("no")]
|
||||||
|
/// #[diagnostic(transparent, code(invalid::combo))]
|
||||||
|
/// One(Foo),
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[doc(hidden)]
|
||||||
|
struct TransparentCombinations;
|
||||||
|
|
@ -15,3 +15,6 @@ mod printer;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod source_impls;
|
mod source_impls;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
|
#[cfg(doctest)]
|
||||||
|
mod compile_test;
|
||||||
|
|
|
||||||
129
tests/derive.rs
129
tests/derive.rs
|
|
@ -310,3 +310,132 @@ fn url_docsrs() {
|
||||||
Foo {}.url().unwrap().to_string()
|
Foo {}.url().unwrap().to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SNIPPET_TEXT: &str = "hello from miette";
|
||||||
|
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("welp")]
|
||||||
|
#[diagnostic(
|
||||||
|
code(foo::bar::baz),
|
||||||
|
url("https://example.com"),
|
||||||
|
help("help"),
|
||||||
|
severity(Warning)
|
||||||
|
)]
|
||||||
|
struct ForwardsTo {
|
||||||
|
src: String,
|
||||||
|
#[snippet(src, message("snippet text"))]
|
||||||
|
snip: miette::SourceSpan,
|
||||||
|
#[highlight(snip, label("highlight text"))]
|
||||||
|
highlight: miette::SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ForwardsTo {
|
||||||
|
fn new() -> Self {
|
||||||
|
ForwardsTo {
|
||||||
|
src: SNIPPET_TEXT.into(),
|
||||||
|
snip: SourceSpan::new(0.into(), SNIPPET_TEXT.len().into()),
|
||||||
|
highlight: SourceSpan::new(11.into(), 6.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_snippets(diag: &impl Diagnostic) {
|
||||||
|
// check Diagnostic impl forwards all these methods
|
||||||
|
assert_eq!(diag.code().to_string(), "foo::bar::baz");
|
||||||
|
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);
|
||||||
|
|
||||||
|
type Snip = (Option<String>, usize, usize);
|
||||||
|
let snips: Vec<(Snip, Vec<Snip>)> = diag
|
||||||
|
.snippets()
|
||||||
|
.unwrap()
|
||||||
|
.map(
|
||||||
|
|miette::DiagnosticSnippet {
|
||||||
|
message,
|
||||||
|
context,
|
||||||
|
highlights,
|
||||||
|
..
|
||||||
|
}| {
|
||||||
|
(
|
||||||
|
(message, context.offset(), context.len()),
|
||||||
|
highlights
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|(msg, span)| (msg, span.offset(), span.len()))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.collect();
|
||||||
|
assert_eq!(
|
||||||
|
&snips[..],
|
||||||
|
&[(
|
||||||
|
(Some("snippet text".into()), 0, SNIPPET_TEXT.len()),
|
||||||
|
vec![(Some("highlight text".into()), 11, 6)]
|
||||||
|
)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transparent_enum_unnamed() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
enum Enum {
|
||||||
|
#[error("enum")]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
|
FooVariant(#[from] ForwardsTo),
|
||||||
|
}
|
||||||
|
|
||||||
|
let variant = Enum::FooVariant(ForwardsTo::new());
|
||||||
|
|
||||||
|
check_snippets(&variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transparent_enum_named() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
enum Enum {
|
||||||
|
#[error("enum")]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
|
FooVariant {
|
||||||
|
#[from]
|
||||||
|
single_field: ForwardsTo,
|
||||||
|
},
|
||||||
|
#[error("foo")]
|
||||||
|
#[diagnostic(code(foo::bar::bar_variant))]
|
||||||
|
BarVariant,
|
||||||
|
}
|
||||||
|
|
||||||
|
let variant = Enum::FooVariant {
|
||||||
|
single_field: ForwardsTo::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
check_snippets(&variant);
|
||||||
|
|
||||||
|
let bar_variant = Enum::BarVariant;
|
||||||
|
assert_eq!(bar_variant.code().to_string(), "foo::bar::bar_variant");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transparent_struct_named() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
|
struct Struct {
|
||||||
|
#[from]
|
||||||
|
single_field: ForwardsTo,
|
||||||
|
}
|
||||||
|
// Also check the From impl here
|
||||||
|
let variant: Struct = ForwardsTo::new().into();
|
||||||
|
check_snippets(&variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transparent_struct_unnamed() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(transparent)]
|
||||||
|
struct Struct(#[from] ForwardsTo);
|
||||||
|
let variant = Struct(ForwardsTo::new());
|
||||||
|
check_snippets(&variant);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue