From 8d95934d86cf2448b9f5a5a4595d1a9906251dfc Mon Sep 17 00:00:00 2001 From: Brooks J Rady Date: Tue, 23 Apr 2024 16:36:56 -0700 Subject: [PATCH 1/2] feat!(blanket_impls): impl `Diagnostic` for &T and Box Brings things in line with best practices as described in "Rust for Rustaceans" when it comes to "Ergonomic Trait Implementations" in Chapter 3. Practically means that people can pass more types to any functions taking either `dyn Diagnostic` or `impl Diagnostic`. BREAKING CHANGE: Added blanket impls may overlap with manual user impls. If these impls are just hacks to achieve the same as the blanket ones do, then they can simply be removed. If the impls for `T` and `&T` differ semantically, then the user would need to employ the newtype pattern to avoid overlap. --- src/protocol.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/test_blanket.rs | 13 +++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 tests/test_blanket.rs diff --git a/src/protocol.rs b/src/protocol.rs index 589cd0b..2f1c1b1 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -69,6 +69,48 @@ pub trait Diagnostic: std::error::Error { } } +macro_rules! blanket_ref_impls { + ($($ref_type:ty),+ $(,)?) => { + $( + impl Diagnostic for $ref_type { + fn code<'a>(&'a self) -> Option> { + (**self).code() + } + + fn severity(&self) -> Option { + (**self).severity() + } + + fn help<'a>(&'a self) -> Option> { + (**self).help() + } + + fn url<'a>(&'a self) -> Option> { + (**self).url() + } + + fn source_code(&self) -> Option<&dyn SourceCode> { + (**self).source_code() + } + + fn labels(&self) -> Option + '_>> { + (**self).labels() + } + + fn related<'a>(&'a self) -> Option + 'a>> { + (**self).related() + } + + fn diagnostic_source(&self) -> Option<&dyn Diagnostic> { + (**self).diagnostic_source() + } + } + )+ + }; +} + +blanket_ref_impls!(&T, Box); + macro_rules! box_error_impls { ($($box_type:ty),*) => { $( diff --git a/tests/test_blanket.rs b/tests/test_blanket.rs new file mode 100644 index 0000000..58271ca --- /dev/null +++ b/tests/test_blanket.rs @@ -0,0 +1,13 @@ +use miette::{Diagnostic, MietteDiagnostic}; + +fn assert_diagnostic() {} + +#[test] +fn test_ref() { + assert_diagnostic::<&MietteDiagnostic>() +} + +#[test] +fn test_box() { + assert_diagnostic::>() +} From dc8afb8fbc7c5c94a073b4775c0649a5842131d2 Mon Sep 17 00:00:00 2001 From: Brooks J Rady Date: Thu, 25 Apr 2024 17:53:49 -0700 Subject: [PATCH 2/2] feat!(render): `render_report` can accept more types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit You can now pass a `&dyn Diagnostic` (as before), or a `Report` (or `&Report`, `&mut Report`, or `Box`), 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. --- src/handlers/debug.rs | 5 +++-- src/handlers/graphical.rs | 5 +++-- src/handlers/json.rs | 6 ++++-- src/handlers/narratable.rs | 7 +++++-- src/protocol.rs | 40 +++++++++++++++++++++++++++++++++++++- tests/graphical.rs | 10 +++++----- tests/narrated.rs | 4 ++-- tests/test_json.rs | 2 +- 8 files changed, 62 insertions(+), 17 deletions(-) diff --git a/src/handlers/debug.rs b/src/handlers/debug.rs index 50450a4..db2bca2 100644 --- a/src/handlers/debug.rs +++ b/src/handlers/debug.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::{protocol::Diagnostic, ReportHandler}; +use crate::{protocol::Diagnostic, AsDiagnostic, ReportHandler}; /** [`ReportHandler`] that renders plain text and avoids extraneous graphics. @@ -31,8 +31,9 @@ impl DebugReportHandler { pub fn render_report( &self, f: &mut fmt::Formatter<'_>, - diagnostic: &(dyn Diagnostic), + diagnostic: impl AsDiagnostic, ) -> fmt::Result { + let diagnostic = diagnostic.as_dyn(); let mut diag = f.debug_struct("Diagnostic"); diag.field("message", &format!("{}", diagnostic)); if let Some(code) = diagnostic.code() { diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs index 9c1d7ab..db0bbfd 100644 --- a/src/handlers/graphical.rs +++ b/src/handlers/graphical.rs @@ -7,7 +7,7 @@ use crate::diagnostic_chain::{DiagnosticChain, ErrorKind}; use crate::handlers::theme::*; use crate::highlighters::{Highlighter, MietteHighlighter}; 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 @@ -215,8 +215,9 @@ impl GraphicalReportHandler { pub fn render_report( &self, f: &mut impl fmt::Write, - diagnostic: &(dyn Diagnostic), + diagnostic: impl AsDiagnostic, ) -> fmt::Result { + let diagnostic = diagnostic.as_dyn(); self.render_header(f, diagnostic)?; self.render_causes(f, diagnostic)?; let src = diagnostic.source_code(); diff --git a/src/handlers/json.rs b/src/handlers/json.rs index 0b4a405..9ad6fc4 100644 --- a/src/handlers/json.rs +++ b/src/handlers/json.rs @@ -1,7 +1,8 @@ use std::fmt::{self, Write}; 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( &self, f: &mut impl fmt::Write, - diagnostic: &(dyn Diagnostic), + diagnostic: impl AsDiagnostic, ) -> fmt::Result { + let diagnostic = diagnostic.as_dyn(); self._render_report(f, diagnostic, None) } diff --git a/src/handlers/narratable.rs b/src/handlers/narratable.rs index c809124..d0e3a11 100644 --- a/src/handlers/narratable.rs +++ b/src/handlers/narratable.rs @@ -4,7 +4,9 @@ use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use crate::diagnostic_chain::DiagnosticChain; 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. @@ -69,8 +71,9 @@ impl NarratableReportHandler { pub fn render_report( &self, f: &mut impl fmt::Write, - diagnostic: &(dyn Diagnostic), + diagnostic: impl AsDiagnostic, ) -> fmt::Result { + let diagnostic = diagnostic.as_dyn(); self.render_header(f, diagnostic)?; if self.with_cause_chain { self.render_causes(f, diagnostic)?; diff --git a/src/protocol.rs b/src/protocol.rs index 2f1c1b1..cd40960 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -12,7 +12,7 @@ use std::{ #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use crate::MietteError; +use crate::{MietteError, Report}; /// Adds rich metadata to your Error that can be used by /// [`Report`](crate::Report) to print really nice and human-friendly error @@ -231,6 +231,44 @@ impl From> for Box &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); + +impl AsDiagnostic for T { + fn as_dyn(&self) -> &dyn Diagnostic { + self + } +} + /** [`Diagnostic`] severity. Intended to be used by [`ReportHandler`](crate::ReportHandler)s to change the way different diff --git a/tests/graphical.rs b/tests/graphical.rs index c6b5e1a..8234a81 100644 --- a/tests/graphical.rs +++ b/tests/graphical.rs @@ -13,24 +13,24 @@ fn fmt_report(diag: Report) -> String { GraphicalReportHandler::new_themed(GraphicalTheme::unicode()) .with_width(80) .with_footer("this is a footer".into()) - .render_report(&mut out, diag.as_ref()) + .render_report(&mut out, &diag) .unwrap(); } else if std::env::var("NARRATED").is_ok() { NarratableReportHandler::new() - .render_report(&mut out, diag.as_ref()) + .render_report(&mut out, diag) .unwrap(); } else if let Ok(w) = std::env::var("REPLACE_TABS") { GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor()) .without_syntax_highlighting() .with_width(80) .tab_width(w.parse().expect("Invalid tab width.")) - .render_report(&mut out, diag.as_ref()) + .render_report(&mut out, &diag) .unwrap(); } else { GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor()) .without_syntax_highlighting() .with_width(80) - .render_report(&mut out, diag.as_ref()) + .render_report(&mut out, &diag) .unwrap(); }; out @@ -46,7 +46,7 @@ fn fmt_report_with_settings( 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); diff --git a/tests/narrated.rs b/tests/narrated.rs index 52acd13..b3cde4e 100644 --- a/tests/narrated.rs +++ b/tests/narrated.rs @@ -12,11 +12,11 @@ fn fmt_report(diag: Report) -> String { if cfg!(feature = "fancy-no-backtrace") && std::env::var("STYLE").is_ok() { #[cfg(feature = "fancy-no-backtrace")] GraphicalReportHandler::new_themed(GraphicalTheme::unicode()) - .render_report(&mut out, diag.as_ref()) + .render_report(&mut out, diag) .unwrap(); } else { NarratableReportHandler::new() - .render_report(&mut out, diag.as_ref()) + .render_report(&mut out, diag) .unwrap(); }; out diff --git a/tests/test_json.rs b/tests/test_json.rs index 664318a..091fa0a 100644 --- a/tests/test_json.rs +++ b/tests/test_json.rs @@ -8,7 +8,7 @@ mod json_report_handler { fn fmt_report(diag: Report) -> String { let mut out = String::new(); JSONReportHandler::new() - .render_report(&mut out, diag.as_ref()) + .render_report(&mut out, diag) .unwrap(); out }