feat!(render): `render_report` can accept more types

You can now pass a `&dyn Diagnostic` (as before), or a `Report` (or
`&Report`, `&mut Report`, or `Box<Report>`), or anything else that
implements `Diagnostic` to `render_report`.

More generally, users can use the `AsDiagnostic` trait wherever they want
to write functions that accept either a `Report` or `Diagnostic` — this
improves ergonomics in a world where `impl Diagnostic for Report` is
impossible.

BREAKING CHANGE: Because more types can now be passed to `render_report`,
`Report::as_ref()` can get confused about types and inference fails. The
solution is removing the `.as_ref()` altogether since `Report` is now a
directly accepted type.
This commit is contained in:
Brooks J Rady 2024-04-25 17:53:49 -07:00
parent 8d95934d86
commit dc8afb8fbc
8 changed files with 62 additions and 17 deletions

View File

@ -1,6 +1,6 @@
use std::fmt; use std::fmt;
use crate::{protocol::Diagnostic, ReportHandler}; use crate::{protocol::Diagnostic, AsDiagnostic, ReportHandler};
/** /**
[`ReportHandler`] that renders plain text and avoids extraneous graphics. [`ReportHandler`] that renders plain text and avoids extraneous graphics.
@ -31,8 +31,9 @@ impl DebugReportHandler {
pub fn render_report( pub fn render_report(
&self, &self,
f: &mut fmt::Formatter<'_>, f: &mut fmt::Formatter<'_>,
diagnostic: &(dyn Diagnostic), diagnostic: impl AsDiagnostic,
) -> fmt::Result { ) -> fmt::Result {
let diagnostic = diagnostic.as_dyn();
let mut diag = f.debug_struct("Diagnostic"); let mut diag = f.debug_struct("Diagnostic");
diag.field("message", &format!("{}", diagnostic)); diag.field("message", &format!("{}", diagnostic));
if let Some(code) = diagnostic.code() { if let Some(code) = diagnostic.code() {

View File

@ -7,7 +7,7 @@ use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
use crate::handlers::theme::*; use crate::handlers::theme::*;
use crate::highlighters::{Highlighter, MietteHighlighter}; use crate::highlighters::{Highlighter, MietteHighlighter};
use crate::protocol::{Diagnostic, Severity}; use crate::protocol::{Diagnostic, Severity};
use crate::{LabeledSpan, ReportHandler, SourceCode, SourceSpan, SpanContents}; use crate::{AsDiagnostic, LabeledSpan, ReportHandler, SourceCode, SourceSpan, SpanContents};
/** /**
A [`ReportHandler`] that displays a given [`Report`](crate::Report) in a A [`ReportHandler`] that displays a given [`Report`](crate::Report) in a
@ -215,8 +215,9 @@ impl GraphicalReportHandler {
pub fn render_report( pub fn render_report(
&self, &self,
f: &mut impl fmt::Write, f: &mut impl fmt::Write,
diagnostic: &(dyn Diagnostic), diagnostic: impl AsDiagnostic,
) -> fmt::Result { ) -> fmt::Result {
let diagnostic = diagnostic.as_dyn();
self.render_header(f, diagnostic)?; self.render_header(f, diagnostic)?;
self.render_causes(f, diagnostic)?; self.render_causes(f, diagnostic)?;
let src = diagnostic.source_code(); let src = diagnostic.source_code();

View File

@ -1,7 +1,8 @@
use std::fmt::{self, Write}; use std::fmt::{self, Write};
use crate::{ use crate::{
diagnostic_chain::DiagnosticChain, protocol::Diagnostic, ReportHandler, Severity, SourceCode, diagnostic_chain::DiagnosticChain, protocol::Diagnostic, AsDiagnostic, ReportHandler, Severity,
SourceCode,
}; };
/** /**
@ -60,8 +61,9 @@ impl JSONReportHandler {
pub fn render_report( pub fn render_report(
&self, &self,
f: &mut impl fmt::Write, f: &mut impl fmt::Write,
diagnostic: &(dyn Diagnostic), diagnostic: impl AsDiagnostic,
) -> fmt::Result { ) -> fmt::Result {
let diagnostic = diagnostic.as_dyn();
self._render_report(f, diagnostic, None) self._render_report(f, diagnostic, None)
} }

View File

@ -4,7 +4,9 @@ use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use crate::diagnostic_chain::DiagnosticChain; 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::{
AsDiagnostic, LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents,
};
/** /**
[`ReportHandler`] that renders plain text and avoids extraneous graphics. [`ReportHandler`] that renders plain text and avoids extraneous graphics.
@ -69,8 +71,9 @@ impl NarratableReportHandler {
pub fn render_report( pub fn render_report(
&self, &self,
f: &mut impl fmt::Write, f: &mut impl fmt::Write,
diagnostic: &(dyn Diagnostic), diagnostic: impl AsDiagnostic,
) -> fmt::Result { ) -> fmt::Result {
let diagnostic = diagnostic.as_dyn();
self.render_header(f, diagnostic)?; self.render_header(f, diagnostic)?;
if self.with_cause_chain { if self.with_cause_chain {
self.render_causes(f, diagnostic)?; self.render_causes(f, diagnostic)?;

View File

@ -12,7 +12,7 @@ use std::{
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::MietteError; use crate::{MietteError, Report};
/// Adds rich metadata to your Error that can be used by /// Adds rich metadata to your Error that can be used by
/// [`Report`](crate::Report) to print really nice and human-friendly error /// [`Report`](crate::Report) to print really nice and human-friendly error
@ -231,6 +231,44 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for Box<dyn Diagnostic + Sen
} }
} }
/// WOOF
pub trait AsDiagnostic {
/// BARK
fn as_dyn(&self) -> &dyn Diagnostic;
}
impl AsDiagnostic for &dyn Diagnostic {
fn as_dyn(&self) -> &dyn Diagnostic {
*self
}
}
impl AsDiagnostic for Report {
fn as_dyn(&self) -> &dyn Diagnostic {
self.as_ref()
}
}
macro_rules! blanket_ref_impls {
($($ref_type:ty),+ $(,)?) => {
$(
impl AsDiagnostic for $ref_type {
fn as_dyn(&self) -> &dyn Diagnostic {
(**self).as_dyn()
}
}
)+
};
}
blanket_ref_impls!(&Report, &mut Report, Box<Report>);
impl<T: Diagnostic> AsDiagnostic for T {
fn as_dyn(&self) -> &dyn Diagnostic {
self
}
}
/** /**
[`Diagnostic`] severity. Intended to be used by [`Diagnostic`] severity. Intended to be used by
[`ReportHandler`](crate::ReportHandler)s to change the way different [`ReportHandler`](crate::ReportHandler)s to change the way different

View File

@ -13,24 +13,24 @@ fn fmt_report(diag: Report) -> String {
GraphicalReportHandler::new_themed(GraphicalTheme::unicode()) GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
.with_width(80) .with_width(80)
.with_footer("this is a footer".into()) .with_footer("this is a footer".into())
.render_report(&mut out, diag.as_ref()) .render_report(&mut out, &diag)
.unwrap(); .unwrap();
} else if std::env::var("NARRATED").is_ok() { } else if std::env::var("NARRATED").is_ok() {
NarratableReportHandler::new() NarratableReportHandler::new()
.render_report(&mut out, diag.as_ref()) .render_report(&mut out, diag)
.unwrap(); .unwrap();
} else if let Ok(w) = std::env::var("REPLACE_TABS") { } else if let Ok(w) = std::env::var("REPLACE_TABS") {
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor()) GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
.without_syntax_highlighting() .without_syntax_highlighting()
.with_width(80) .with_width(80)
.tab_width(w.parse().expect("Invalid tab width.")) .tab_width(w.parse().expect("Invalid tab width."))
.render_report(&mut out, diag.as_ref()) .render_report(&mut out, &diag)
.unwrap(); .unwrap();
} else { } else {
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor()) GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
.without_syntax_highlighting() .without_syntax_highlighting()
.with_width(80) .with_width(80)
.render_report(&mut out, diag.as_ref()) .render_report(&mut out, &diag)
.unwrap(); .unwrap();
}; };
out out
@ -46,7 +46,7 @@ fn fmt_report_with_settings(
GraphicalTheme::unicode_nocolor(), GraphicalTheme::unicode_nocolor(),
)); ));
handler.render_report(&mut out, diag.as_ref()).unwrap(); handler.render_report(&mut out, diag).unwrap();
println!("Error:\n```\n{}\n```", out); println!("Error:\n```\n{}\n```", out);

View File

@ -12,11 +12,11 @@ fn fmt_report(diag: Report) -> String {
if cfg!(feature = "fancy-no-backtrace") && std::env::var("STYLE").is_ok() { if cfg!(feature = "fancy-no-backtrace") && std::env::var("STYLE").is_ok() {
#[cfg(feature = "fancy-no-backtrace")] #[cfg(feature = "fancy-no-backtrace")]
GraphicalReportHandler::new_themed(GraphicalTheme::unicode()) GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
.render_report(&mut out, diag.as_ref()) .render_report(&mut out, diag)
.unwrap(); .unwrap();
} else { } else {
NarratableReportHandler::new() NarratableReportHandler::new()
.render_report(&mut out, diag.as_ref()) .render_report(&mut out, diag)
.unwrap(); .unwrap();
}; };
out out

View File

@ -8,7 +8,7 @@ mod json_report_handler {
fn fmt_report(diag: Report) -> String { fn fmt_report(diag: Report) -> String {
let mut out = String::new(); let mut out = String::new();
JSONReportHandler::new() JSONReportHandler::new()
.render_report(&mut out, diag.as_ref()) .render_report(&mut out, diag)
.unwrap(); .unwrap();
out out
} }