Skip to content

Commit

Permalink
feat(related): Add related diagnostics (#68)
Browse files Browse the repository at this point in the history
Fixes: #47
  • Loading branch information
zkat authored Sep 22, 2021
1 parent 491ce7c commit 25e434a
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 57 deletions.
14 changes: 14 additions & 0 deletions miette-derive/src/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::diagnostic_arg::DiagnosticArg;
use crate::forward::{Forward, WhichFn};
use crate::help::Help;
use crate::label::Labels;
use crate::related::Related;
use crate::severity::Severity;
use crate::source_code::SourceCode;
use crate::url::Url;
Expand Down Expand Up @@ -64,6 +65,7 @@ pub struct DiagnosticConcreteArgs {
pub source_code: Option<SourceCode>,
pub url: Option<Url>,
pub forward: Option<Forward>,
pub related: Option<Related>,
}

impl DiagnosticConcreteArgs {
Expand Down Expand Up @@ -103,9 +105,11 @@ impl DiagnosticConcreteArgs {
}
let labels = Labels::from_fields(fields)?;
let source_code = SourceCode::from_fields(fields)?;
let related = Related::from_fields(fields)?;
let concrete = DiagnosticConcreteArgs {
code,
help,
related,
severity,
labels,
url,
Expand Down Expand Up @@ -215,6 +219,7 @@ impl Diagnostic {
let labels_method = forward.gen_struct_method(WhichFn::Labels);
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);

quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
Expand All @@ -224,6 +229,7 @@ impl Diagnostic {
#labels_method
#severity_method
#source_code_method
#related_method
}
}
}
Expand All @@ -249,6 +255,11 @@ impl Diagnostic {
.as_ref()
.and_then(|x| x.gen_struct())
.or_else(|| forward(WhichFn::Severity));
let rel_body = concrete
.related
.as_ref()
.and_then(|x| x.gen_struct())
.or_else(|| forward(WhichFn::Related));
let url_body = concrete
.url
.as_ref()
Expand All @@ -269,6 +280,7 @@ impl Diagnostic {
#code_body
#help_body
#sev_body
#rel_body
#url_body
#labels_body
#src_body
Expand All @@ -288,6 +300,7 @@ impl Diagnostic {
let sev_body = Severity::gen_enum(variants);
let labels_body = Labels::gen_enum(variants);
let src_body = SourceCode::gen_enum(variants);
let rel_body = Related::gen_enum(variants);
let url_body = Url::gen_enum(ident, variants);
quote! {
impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause {
Expand All @@ -296,6 +309,7 @@ impl Diagnostic {
#sev_body
#labels_body
#src_body
#rel_body
#url_body
}
}
Expand Down
5 changes: 5 additions & 0 deletions miette-derive/src/forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub enum WhichFn {
Severity,
Labels,
SourceCode,
Related,
}

impl WhichFn {
Expand All @@ -48,6 +49,7 @@ impl WhichFn {
Self::Severity => quote! { severity() },
Self::Labels => quote! { labels() },
Self::SourceCode => quote! { source_code() },
Self::Related => quote! { related() },
}
}

Expand All @@ -65,6 +67,9 @@ impl WhichFn {
Self::Severity => quote! {
fn severity(&self) -> std::option::Option<miette::Severity>
},
Self::Related => quote! {
fn related(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = &dyn miette::Diagnostic> + '_>>
},
Self::Labels => quote! {
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>>
},
Expand Down
3 changes: 2 additions & 1 deletion miette-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ mod fmt;
mod forward;
mod help;
mod label;
mod related;
mod severity;
mod source_code;
mod url;
mod utils;

#[proc_macro_derive(Diagnostic, attributes(diagnostic, label, source_code))]
#[proc_macro_derive(Diagnostic, attributes(diagnostic, source_code, label, related))]
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
76 changes: 76 additions & 0 deletions miette-derive/src/related.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::spanned::Spanned;

use crate::{
diagnostic::{DiagnosticConcreteArgs, DiagnosticDef},
forward::WhichFn,
utils::{display_pat_members, gen_all_variants_with},
};

pub struct Related(syn::Member);

impl Related {
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("related") {
let related = 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(Related(related)));
}
}
}
Ok(None)
}

pub(crate) fn gen_enum(variants: &[DiagnosticDef]) -> Option<TokenStream> {
gen_all_variants_with(
variants,
WhichFn::Related,
|ident, fields, DiagnosticConcreteArgs { related, .. }| {
let (display_pat, _display_members) = display_pat_members(fields);
related.as_ref().map(|related| {
let rel = match &related.0 {
syn::Member::Named(ident) => ident.clone(),
syn::Member::Unnamed(syn::Index { index, .. }) => {
format_ident!("_{}", index)
}
};
quote! {
Self::#ident #display_pat => {
std::option::Option::Some(std::boxed::Box::new(
#rel.iter().map(|x| -> &(dyn Diagnostic) { &*x })
))
}
}
})
},
)
}

pub(crate) fn gen_struct(&self) -> Option<TokenStream> {
let rel = &self.0;
Some(quote! {
fn related<'a>(&'a self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = &'a dyn miette::Diagnostic> + 'a>> {
std::option::Option::Some(std::boxed::Box::new(self.#rel.iter().map(|x| -> &(dyn Diagnostic) { &*x })))
}
})
}
}
136 changes: 80 additions & 56 deletions src/handlers/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,17 @@ impl GraphicalReportHandler {
self.render_header(f, diagnostic)?;
writeln!(f)?;
self.render_causes(f, diagnostic)?;

if let Some(source) = diagnostic.source_code() {
if let Some(labels) = diagnostic.labels() {
let mut labels = labels.collect::<Vec<_>>();
labels.sort_unstable_by_key(|l| l.inner().offset());
if !labels.is_empty() {
writeln!(f)?;
self.render_snippets(f, source, labels)?;
}
}
}

self.render_snippets(f, diagnostic)?;
self.render_footer(f, diagnostic)?;
self.render_related(f, diagnostic)?;
if let Some(footer) = &self.footer {
writeln!(f)?;
let width = self.termwidth.saturating_sub(4);
let opts = textwrap::Options::new(width)
.initial_indent(" ")
.subsequent_indent(" ");
writeln!(f, "{}", textwrap::fill(footer, opts))?;
}
Ok(())
}

Expand Down Expand Up @@ -214,63 +212,89 @@ impl GraphicalReportHandler {
.subsequent_indent(" ");
writeln!(f, "{}", textwrap::fill(&help.to_string(), opts))?;
}
if let Some(footer) = &self.footer {
Ok(())
}

fn render_related(
&self,
f: &mut impl fmt::Write,
diagnostic: &(dyn Diagnostic),
) -> fmt::Result {
if let Some(related) = diagnostic.related() {
writeln!(f)?;
let width = self.termwidth.saturating_sub(4);
let opts = textwrap::Options::new(width)
.initial_indent(" ")
.subsequent_indent(" ");
writeln!(f, "{}", textwrap::fill(footer, opts))?;
for rel in related {
write!(f, "Error: ")?;
self.render_header(f, rel)?;
writeln!(f)?;
self.render_causes(f, rel)?;
self.render_snippets(f, rel)?;
self.render_footer(f, rel)?;
self.render_related(f, rel)?;
}
}
Ok(())
}

fn render_snippets(
&self,
f: &mut impl fmt::Write,
source: &dyn SourceCode,
labels: Vec<LabeledSpan>,
diagnostic: &(dyn Diagnostic),
) -> fmt::Result {
let contents = labels
.iter()
.map(|label| source.read_span(label.inner(), self.context_lines, self.context_lines))
.collect::<Result<Vec<Box<dyn SpanContents<'_>>>, MietteError>>()
.map_err(|_| fmt::Error)?;
let contexts = labels.iter().cloned().zip(contents.iter()).coalesce(
|(left, left_conts), (right, right_conts)| {
let left_end = left.offset() + left.len();
let right_end = right.offset() + right.len();
if left_conts.line() + left_conts.line_count() >= right_conts.line() {
// The snippets will overlap, so we create one Big Chunky Boi
let new_span = LabeledSpan::new(
left.label().map(String::from),
left.offset(),
if right_end >= left_end {
// Right end goes past left end
right_end - left.offset()
} else {
// right is contained inside left
left.len()
if let Some(source) = diagnostic.source_code() {
if let Some(labels) = diagnostic.labels() {
let mut labels = labels.collect::<Vec<_>>();
labels.sort_unstable_by_key(|l| l.inner().offset());
if !labels.is_empty() {
writeln!(f)?;
let contents = labels
.iter()
.map(|label| {
source.read_span(label.inner(), self.context_lines, self.context_lines)
})
.collect::<Result<Vec<Box<dyn SpanContents<'_>>>, MietteError>>()
.map_err(|_| fmt::Error)?;
let contexts = labels.iter().cloned().zip(contents.iter()).coalesce(
|(left, left_conts), (right, right_conts)| {
let left_end = left.offset() + left.len();
let right_end = right.offset() + right.len();
if left_conts.line() + left_conts.line_count() >= right_conts.line() {
// The snippets will overlap, so we create one Big Chunky Boi
let new_span = LabeledSpan::new(
left.label().map(String::from),
left.offset(),
if right_end >= left_end {
// Right end goes past left end
right_end - left.offset()
} else {
// right is contained inside left
left.len()
},
);
if source
.read_span(
new_span.inner(),
self.context_lines,
self.context_lines,
)
.is_ok()
{
Ok((
new_span, // We'll throw this away later
left_conts,
))
} else {
Err(((left, left_conts), (right, right_conts)))
}
} else {
Err(((left, left_conts), (right, right_conts)))
}
},
);
if source
.read_span(new_span.inner(), self.context_lines, self.context_lines)
.is_ok()
{
Ok((
new_span, // We'll throw this away later
left_conts,
))
} else {
Err(((left, left_conts), (right, right_conts)))
for (ctx, _) in contexts {
self.render_context(f, source, &ctx, &labels[..])?;
}
} else {
Err(((left, left_conts), (right, right_conts)))
}
},
);
for (ctx, _) in contexts {
self.render_context(f, source, &ctx, &labels[..])?;
}
}
Ok(())
}
Expand Down
5 changes: 5 additions & 0 deletions src/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ pub trait Diagnostic: std::error::Error {
fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
None
}

/// Additional related Diagnostics.
fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
None
}
}

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

0 comments on commit 25e434a

Please sign in to comment.