mirror of https://github.com/zkat/miette.git
feat(diagnostic_source): add protocol method for Diagnostic-aware source chaining (#165)
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
This commit is contained in:
parent
33c8903313
commit
bc449c8426
|
|
@ -4,6 +4,7 @@ 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::diagnostic_source::DiagnosticSource;
|
||||||
use crate::forward::{Forward, WhichFn};
|
use crate::forward::{Forward, WhichFn};
|
||||||
use crate::help::Help;
|
use crate::help::Help;
|
||||||
use crate::label::Labels;
|
use crate::label::Labels;
|
||||||
|
|
@ -66,6 +67,7 @@ pub struct DiagnosticConcreteArgs {
|
||||||
pub url: Option<Url>,
|
pub url: Option<Url>,
|
||||||
pub forward: Option<Forward>,
|
pub forward: Option<Forward>,
|
||||||
pub related: Option<Related>,
|
pub related: Option<Related>,
|
||||||
|
pub diagnostic_source: Option<DiagnosticSource>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagnosticConcreteArgs {
|
impl DiagnosticConcreteArgs {
|
||||||
|
|
@ -74,6 +76,7 @@ impl DiagnosticConcreteArgs {
|
||||||
let source_code = SourceCode::from_fields(fields)?;
|
let source_code = SourceCode::from_fields(fields)?;
|
||||||
let related = Related::from_fields(fields)?;
|
let related = Related::from_fields(fields)?;
|
||||||
let help = Help::from_fields(fields)?;
|
let help = Help::from_fields(fields)?;
|
||||||
|
let diagnostic_source = DiagnosticSource::from_fields(fields)?;
|
||||||
Ok(DiagnosticConcreteArgs {
|
Ok(DiagnosticConcreteArgs {
|
||||||
code: None,
|
code: None,
|
||||||
help,
|
help,
|
||||||
|
|
@ -83,6 +86,7 @@ impl DiagnosticConcreteArgs {
|
||||||
url: None,
|
url: None,
|
||||||
forward: None,
|
forward: None,
|
||||||
source_code,
|
source_code,
|
||||||
|
diagnostic_source,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,6 +287,8 @@ impl Diagnostic {
|
||||||
let source_code_method = forward.gen_struct_method(WhichFn::SourceCode);
|
let source_code_method = forward.gen_struct_method(WhichFn::SourceCode);
|
||||||
let severity_method = forward.gen_struct_method(WhichFn::Severity);
|
let severity_method = forward.gen_struct_method(WhichFn::Severity);
|
||||||
let related_method = forward.gen_struct_method(WhichFn::Related);
|
let related_method = forward.gen_struct_method(WhichFn::Related);
|
||||||
|
let diagnostic_source_method =
|
||||||
|
forward.gen_struct_method(WhichFn::DiagnosticSource);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
|
||||||
|
|
@ -293,6 +299,7 @@ impl Diagnostic {
|
||||||
#severity_method
|
#severity_method
|
||||||
#source_code_method
|
#source_code_method
|
||||||
#related_method
|
#related_method
|
||||||
|
#diagnostic_source_method
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -338,6 +345,11 @@ impl Diagnostic {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|x| x.gen_struct(fields))
|
.and_then(|x| x.gen_struct(fields))
|
||||||
.or_else(|| forward(WhichFn::SourceCode));
|
.or_else(|| forward(WhichFn::SourceCode));
|
||||||
|
let diagnostic_source = concrete
|
||||||
|
.diagnostic_source
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|x| x.gen_struct())
|
||||||
|
.or_else(|| forward(WhichFn::DiagnosticSource));
|
||||||
quote! {
|
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
|
#code_body
|
||||||
|
|
@ -347,6 +359,7 @@ impl Diagnostic {
|
||||||
#url_body
|
#url_body
|
||||||
#labels_body
|
#labels_body
|
||||||
#src_body
|
#src_body
|
||||||
|
#diagnostic_source
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -365,6 +378,7 @@ impl Diagnostic {
|
||||||
let src_body = SourceCode::gen_enum(variants);
|
let src_body = SourceCode::gen_enum(variants);
|
||||||
let rel_body = Related::gen_enum(variants);
|
let rel_body = Related::gen_enum(variants);
|
||||||
let url_body = Url::gen_enum(ident, variants);
|
let url_body = Url::gen_enum(ident, variants);
|
||||||
|
let diagnostic_source_body = DiagnosticSource::gen_enum(variants);
|
||||||
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
|
#code_body
|
||||||
|
|
@ -374,6 +388,7 @@ impl Diagnostic {
|
||||||
#src_body
|
#src_body
|
||||||
#rel_body
|
#rel_body
|
||||||
#url_body
|
#url_body
|
||||||
|
#diagnostic_source_body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::spanned::Spanned;
|
||||||
|
|
||||||
|
use crate::forward::WhichFn;
|
||||||
|
use crate::{
|
||||||
|
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
|
||||||
|
utils::{display_pat_members, gen_all_variants_with},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct DiagnosticSource(syn::Member);
|
||||||
|
|
||||||
|
impl DiagnosticSource {
|
||||||
|
pub(crate) fn from_fields(fields: &syn::Fields) -> syn::Result<Option<Self>> {
|
||||||
|
match fields {
|
||||||
|
syn::Fields::Named(named) => Self::from_fields_vec(named.named.iter().collect()),
|
||||||
|
syn::Fields::Unnamed(unnamed) => {
|
||||||
|
Self::from_fields_vec(unnamed.unnamed.iter().collect())
|
||||||
|
}
|
||||||
|
syn::Fields::Unit => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_fields_vec(fields: Vec<&syn::Field>) -> syn::Result<Option<Self>> {
|
||||||
|
for (i, field) in fields.iter().enumerate() {
|
||||||
|
for attr in &field.attrs {
|
||||||
|
if attr.path.is_ident("diagnostic_source") {
|
||||||
|
let diagnostic_source = if let Some(ident) = field.ident.clone() {
|
||||||
|
syn::Member::Named(ident)
|
||||||
|
} else {
|
||||||
|
syn::Member::Unnamed(syn::Index {
|
||||||
|
index: i as u32,
|
||||||
|
span: field.span(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
return Ok(Some(DiagnosticSource(diagnostic_source)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
|
||||||
|
gen_all_variants_with(
|
||||||
|
variants,
|
||||||
|
WhichFn::DiagnosticSource,
|
||||||
|
|ident,
|
||||||
|
fields,
|
||||||
|
DiagnosticConcreteArgs {
|
||||||
|
diagnostic_source, ..
|
||||||
|
}| {
|
||||||
|
let (display_pat, _display_members) = display_pat_members(fields);
|
||||||
|
diagnostic_source.as_ref().map(|diagnostic_source| {
|
||||||
|
let rel = match &diagnostic_source.0 {
|
||||||
|
syn::Member::Named(ident) => ident.clone(),
|
||||||
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
quote::format_ident!("_{}", index)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
quote! {
|
||||||
|
Self::#ident #display_pat => {
|
||||||
|
std::option::Option::Some(#rel.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
|
||||||
|
let rel = &self.0;
|
||||||
|
Some(quote! {
|
||||||
|
fn diagnostic_source<'a>(&'a self) -> std::option::Option<&'a dyn miette::Diagnostic> {
|
||||||
|
std::option::Option::Some(&self.#rel)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -38,6 +38,7 @@ pub enum WhichFn {
|
||||||
Labels,
|
Labels,
|
||||||
SourceCode,
|
SourceCode,
|
||||||
Related,
|
Related,
|
||||||
|
DiagnosticSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WhichFn {
|
impl WhichFn {
|
||||||
|
|
@ -50,6 +51,7 @@ impl WhichFn {
|
||||||
Self::Labels => quote! { labels() },
|
Self::Labels => quote! { labels() },
|
||||||
Self::SourceCode => quote! { source_code() },
|
Self::SourceCode => quote! { source_code() },
|
||||||
Self::Related => quote! { related() },
|
Self::Related => quote! { related() },
|
||||||
|
Self::DiagnosticSource => quote! { diagnostic_source() },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,6 +78,9 @@ impl WhichFn {
|
||||||
Self::SourceCode => quote! {
|
Self::SourceCode => quote! {
|
||||||
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode>
|
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode>
|
||||||
},
|
},
|
||||||
|
Self::DiagnosticSource => quote! {
|
||||||
|
fn diagnostic_source(&self) -> std::option::Option<&dyn miette::Diagnostic>
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use diagnostic::Diagnostic;
|
||||||
mod code;
|
mod code;
|
||||||
mod diagnostic;
|
mod diagnostic;
|
||||||
mod diagnostic_arg;
|
mod diagnostic_arg;
|
||||||
|
mod diagnostic_source;
|
||||||
mod fmt;
|
mod fmt;
|
||||||
mod forward;
|
mod forward;
|
||||||
mod help;
|
mod help;
|
||||||
|
|
@ -16,7 +17,10 @@ mod source_code;
|
||||||
mod url;
|
mod url;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
#[proc_macro_derive(Diagnostic, attributes(diagnostic, source_code, label, related, help))]
|
#[proc_macro_derive(
|
||||||
|
Diagnostic,
|
||||||
|
attributes(diagnostic, source_code, label, related, help, diagnostic_source)
|
||||||
|
)]
|
||||||
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn derive_diagnostic(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let input = parse_macro_input!(input as DeriveInput);
|
let input = parse_macro_input!(input as DeriveInput);
|
||||||
let cmd = match Diagnostic::from_derive_input(input) {
|
let cmd = match Diagnostic::from_derive_input(input) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*!
|
||||||
|
Iterate over error `.diagnostic_source()` chains.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::protocol::Diagnostic;
|
||||||
|
|
||||||
|
/// Iterator of a chain of cause errors.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub(crate) struct DiagnosticChain<'a> {
|
||||||
|
state: Option<ErrorKind<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DiagnosticChain<'a> {
|
||||||
|
pub(crate) fn from_diagnostic(head: &'a dyn Diagnostic) -> Self {
|
||||||
|
DiagnosticChain {
|
||||||
|
state: Some(ErrorKind::Diagnostic(head)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_stderror(head: &'a (dyn std::error::Error + 'static)) -> Self {
|
||||||
|
DiagnosticChain {
|
||||||
|
state: Some(ErrorKind::StdError(head)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for DiagnosticChain<'a> {
|
||||||
|
type Item = ErrorKind<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if let Some(err) = self.state.take() {
|
||||||
|
self.state = err.get_nested();
|
||||||
|
Some(err)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
|
let len = self.len();
|
||||||
|
(len, Some(len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExactSizeIterator for DiagnosticChain<'_> {
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
fn depth(d: Option<&ErrorKind<'_>>) -> usize {
|
||||||
|
match d {
|
||||||
|
Some(d) => 1 + depth(d.get_nested().as_ref()),
|
||||||
|
None => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
depth(self.state.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) enum ErrorKind<'a> {
|
||||||
|
Diagnostic(&'a dyn Diagnostic),
|
||||||
|
StdError(&'a (dyn std::error::Error + 'static)),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ErrorKind<'a> {
|
||||||
|
fn get_nested(&self) -> Option<ErrorKind<'a>> {
|
||||||
|
match self {
|
||||||
|
ErrorKind::Diagnostic(d) => d
|
||||||
|
.diagnostic_source()
|
||||||
|
.map(ErrorKind::Diagnostic)
|
||||||
|
.or_else(|| d.source().map(ErrorKind::StdError)),
|
||||||
|
ErrorKind::StdError(e) => e.source().map(ErrorKind::StdError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::fmt::Debug for ErrorKind<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ErrorKind::Diagnostic(d) => d.fmt(f),
|
||||||
|
ErrorKind::StdError(e) => e.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::fmt::Display for ErrorKind<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ErrorKind::Diagnostic(d) => d.fmt(f),
|
||||||
|
ErrorKind::StdError(e) => e.fmt(f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -51,6 +51,9 @@ impl DebugReportHandler {
|
||||||
let labels: Vec<_> = labels.collect();
|
let labels: Vec<_> = labels.collect();
|
||||||
diag.field("labels", &format!("{:?}", labels));
|
diag.field("labels", &format!("{:?}", labels));
|
||||||
}
|
}
|
||||||
|
if let Some(cause) = diagnostic.diagnostic_source() {
|
||||||
|
diag.field("caused by", &format!("{:?}", cause));
|
||||||
|
}
|
||||||
diag.finish()?;
|
diag.finish()?;
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
writeln!(f, "NOTE: If you're looking for the fancy error reports, install miette with the `fancy` feature, or write your own and hook it up with miette::set_hook().")
|
writeln!(f, "NOTE: If you're looking for the fancy error reports, install miette with the `fancy` feature, or write your own and hook it up with miette::set_hook().")
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::fmt::{self, Write};
|
||||||
|
|
||||||
use owo_colors::{OwoColorize, Style};
|
use owo_colors::{OwoColorize, Style};
|
||||||
|
|
||||||
use crate::chain::Chain;
|
use crate::diagnostic_chain::DiagnosticChain;
|
||||||
use crate::handlers::theme::*;
|
use crate::handlers::theme::*;
|
||||||
use crate::protocol::{Diagnostic, Severity};
|
use crate::protocol::{Diagnostic, Severity};
|
||||||
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
||||||
|
|
@ -199,8 +199,12 @@ impl GraphicalReportHandler {
|
||||||
|
|
||||||
writeln!(f, "{}", textwrap::fill(&diagnostic.to_string(), opts))?;
|
writeln!(f, "{}", textwrap::fill(&diagnostic.to_string(), opts))?;
|
||||||
|
|
||||||
if let Some(cause) = diagnostic.source() {
|
if let Some(mut cause_iter) = diagnostic
|
||||||
let mut cause_iter = Chain::new(cause).peekable();
|
.diagnostic_source()
|
||||||
|
.map(DiagnosticChain::from_diagnostic)
|
||||||
|
.or_else(|| diagnostic.source().map(DiagnosticChain::from_stderror))
|
||||||
|
.map(|it| it.peekable())
|
||||||
|
{
|
||||||
while let Some(error) = cause_iter.next() {
|
while let Some(error) = cause_iter.next() {
|
||||||
let is_last = cause_iter.peek().is_none();
|
let is_last = cause_iter.peek().is_none();
|
||||||
let char = if !is_last {
|
let char = if !is_last {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use std::fmt;
|
||||||
|
|
||||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||||
|
|
||||||
use crate::chain::Chain;
|
use crate::diagnostic_chain::DiagnosticChain;
|
||||||
use crate::protocol::{Diagnostic, Severity};
|
use crate::protocol::{Diagnostic, Severity};
|
||||||
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
||||||
|
|
||||||
|
|
@ -80,8 +80,12 @@ impl NarratableReportHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
|
||||||
if let Some(cause) = diagnostic.source() {
|
if let Some(cause_iter) = diagnostic
|
||||||
for error in Chain::new(cause) {
|
.diagnostic_source()
|
||||||
|
.map(DiagnosticChain::from_diagnostic)
|
||||||
|
.or_else(|| diagnostic.source().map(DiagnosticChain::from_stderror))
|
||||||
|
{
|
||||||
|
for error in cause_iter {
|
||||||
writeln!(f, " Caused by: {}", error)?;
|
writeln!(f, " Caused by: {}", error)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -584,6 +584,7 @@ pub use panic::*;
|
||||||
pub use protocol::*;
|
pub use protocol::*;
|
||||||
|
|
||||||
mod chain;
|
mod chain;
|
||||||
|
mod diagnostic_chain;
|
||||||
mod error;
|
mod error;
|
||||||
mod eyreish;
|
mod eyreish;
|
||||||
#[cfg(feature = "fancy-no-backtrace")]
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,11 @@ pub trait Diagnostic: std::error::Error {
|
||||||
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The cause of the error.
|
||||||
|
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::error::Error for Box<dyn Diagnostic> {
|
impl std::error::Error for Box<dyn Diagnostic> {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
use miette::Diagnostic;
|
||||||
|
|
||||||
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
|
#[error("AnErr")]
|
||||||
|
struct AnErr;
|
||||||
|
|
||||||
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
|
#[error("TestError")]
|
||||||
|
struct TestError {
|
||||||
|
#[diagnostic_source]
|
||||||
|
asdf_inner_foo: AnErr,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_diagnostic_source() {
|
||||||
|
let error = TestError {
|
||||||
|
asdf_inner_foo: AnErr,
|
||||||
|
};
|
||||||
|
assert!(error.diagnostic_source().is_some());
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue