Skip to content

Commit

Permalink
feat(diagnostic_source): add protocol method for Diagnostic-aware sou…
Browse files Browse the repository at this point in the history
…rce chaining (#165)

Signed-off-by: Matthias Beyer <[email protected]>
  • Loading branch information
matthiasbeyer authored May 5, 2022
1 parent 33c8903 commit bc449c8
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 7 deletions.
15 changes: 15 additions & 0 deletions miette-derive/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use syn::{punctuated::Punctuated, DeriveInput, Token};

use crate::code::Code;
use crate::diagnostic_arg::DiagnosticArg;
use crate::diagnostic_source::DiagnosticSource;
use crate::forward::{Forward, WhichFn};
use crate::help::Help;
use crate::label::Labels;
Expand Down Expand Up @@ -66,6 +67,7 @@ pub struct DiagnosticConcreteArgs {
pub url: Option<Url>,
pub forward: Option<Forward>,
pub related: Option<Related>,
pub diagnostic_source: Option<DiagnosticSource>,
}

impl DiagnosticConcreteArgs {
Expand All @@ -74,6 +76,7 @@ impl DiagnosticConcreteArgs {
let source_code = SourceCode::from_fields(fields)?;
let related = Related::from_fields(fields)?;
let help = Help::from_fields(fields)?;
let diagnostic_source = DiagnosticSource::from_fields(fields)?;
Ok(DiagnosticConcreteArgs {
code: None,
help,
Expand All @@ -83,6 +86,7 @@ impl DiagnosticConcreteArgs {
url: None,
forward: None,
source_code,
diagnostic_source,
})
}

Expand Down Expand Up @@ -283,6 +287,8 @@ impl Diagnostic {
let source_code_method = forward.gen_struct_method(WhichFn::SourceCode);
let severity_method = forward.gen_struct_method(WhichFn::Severity);
let related_method = forward.gen_struct_method(WhichFn::Related);
let diagnostic_source_method =
forward.gen_struct_method(WhichFn::DiagnosticSource);

quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
Expand All @@ -293,6 +299,7 @@ impl Diagnostic {
#severity_method
#source_code_method
#related_method
#diagnostic_source_method
}
}
}
Expand Down Expand Up @@ -338,6 +345,11 @@ impl Diagnostic {
.as_ref()
.and_then(|x| x.gen_struct(fields))
.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! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
#code_body
Expand All @@ -347,6 +359,7 @@ impl Diagnostic {
#url_body
#labels_body
#src_body
#diagnostic_source
}
}
}
Expand All @@ -365,6 +378,7 @@ impl Diagnostic {
let src_body = SourceCode::gen_enum(variants);
let rel_body = Related::gen_enum(variants);
let url_body = Url::gen_enum(ident, variants);
let diagnostic_source_body = DiagnosticSource::gen_enum(variants);
quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
#code_body
Expand All @@ -374,6 +388,7 @@ impl Diagnostic {
#src_body
#rel_body
#url_body
#diagnostic_source_body
}
}
}
Expand Down
78 changes: 78 additions & 0 deletions miette-derive/src/diagnostic_source.rs
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}
5 changes: 5 additions & 0 deletions miette-derive/src/forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub enum WhichFn {
Labels,
SourceCode,
Related,
DiagnosticSource,
}

impl WhichFn {
Expand All @@ -50,6 +51,7 @@ impl WhichFn {
Self::Labels => quote! { labels() },
Self::SourceCode => quote! { source_code() },
Self::Related => quote! { related() },
Self::DiagnosticSource => quote! { diagnostic_source() },
}
}

Expand All @@ -76,6 +78,9 @@ impl WhichFn {
Self::SourceCode => quote! {
fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode>
},
Self::DiagnosticSource => quote! {
fn diagnostic_source(&self) -> std::option::Option<&dyn miette::Diagnostic>
},
}
}

Expand Down
6 changes: 5 additions & 1 deletion miette-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use diagnostic::Diagnostic;
mod code;
mod diagnostic;
mod diagnostic_arg;
mod diagnostic_source;
mod fmt;
mod forward;
mod help;
Expand All @@ -16,7 +17,10 @@ mod source_code;
mod url;
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 {
let input = parse_macro_input!(input as DeriveInput);
let cmd = match Diagnostic::from_derive_input(input) {
Expand Down
93 changes: 93 additions & 0 deletions src/diagnostic_chain.rs
Original file line number Diff line number Diff line change
@@ -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),
}
}
}
3 changes: 3 additions & 0 deletions src/handlers/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ impl DebugReportHandler {
let labels: Vec<_> = labels.collect();
diag.field("labels", &format!("{:?}", labels));
}
if let Some(cause) = diagnostic.diagnostic_source() {
diag.field("caused by", &format!("{:?}", cause));
}
diag.finish()?;
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().")
Expand Down
10 changes: 7 additions & 3 deletions src/handlers/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::fmt::{self, Write};

use owo_colors::{OwoColorize, Style};

use crate::chain::Chain;
use crate::diagnostic_chain::DiagnosticChain;
use crate::handlers::theme::*;
use crate::protocol::{Diagnostic, Severity};
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
Expand Down Expand Up @@ -199,8 +199,12 @@ impl GraphicalReportHandler {

writeln!(f, "{}", textwrap::fill(&diagnostic.to_string(), opts))?;

if let Some(cause) = diagnostic.source() {
let mut cause_iter = Chain::new(cause).peekable();
if let Some(mut cause_iter) = diagnostic
.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() {
let is_last = cause_iter.peek().is_none();
let char = if !is_last {
Expand Down
10 changes: 7 additions & 3 deletions src/handlers/narratable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::fmt;

use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};

use crate::chain::Chain;
use crate::diagnostic_chain::DiagnosticChain;
use crate::protocol::{Diagnostic, Severity};
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};

Expand Down Expand Up @@ -80,8 +80,12 @@ impl NarratableReportHandler {
}

fn render_causes(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
if let Some(cause) = diagnostic.source() {
for error in Chain::new(cause) {
if let Some(cause_iter) = diagnostic
.diagnostic_source()
.map(DiagnosticChain::from_diagnostic)
.or_else(|| diagnostic.source().map(DiagnosticChain::from_stderror))
{
for error in cause_iter {
writeln!(f, " Caused by: {}", error)?;
}
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,7 @@ pub use panic::*;
pub use protocol::*;

mod chain;
mod diagnostic_chain;
mod error;
mod eyreish;
#[cfg(feature = "fancy-no-backtrace")]
Expand Down
5 changes: 5 additions & 0 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ pub trait Diagnostic: std::error::Error {
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
None
}

/// The cause of the error.
fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
None
}
}

impl std::error::Error for Box<dyn Diagnostic> {
Expand Down
Loading

0 comments on commit bc449c8

Please sign in to comment.