From 521ef91f77a143eb5cedfa1344428b804802179d Mon Sep 17 00:00:00 2001 From: Brooks Rady Date: Sat, 26 Apr 2025 19:34:21 +0100 Subject: [PATCH] fix(deps): miette can now be used without syn (#436) * style(clippy): quickly fix up clippy lints * refactor(miette): remove `thiserror` from `error.rs` * fix(fancy): `fancy` no longer depends on `derive` Before this change, the `fancy` feature required the `derive` feature to compile. Despite this, `derive` was not listed as a required feature to use `fancy`. This meant that building with: `cargo build --no-default-features --features fancy` would result in a compilation error! Now `fancy` can be used without `derive` enabled, and another use of `thiserror` has been removed! * refactor(miette): remove `thiserror` from `into_diagnostic.rs` * refactor(miette): reuse `DiagnosticError` in `protocol.rs` * refactor(miette): make `thiserror` a dev-dependency * fix(miette): correctly forward error sources * fix(miette): match `TestError` visibility with `mod tests` * fix(miette): maintain 1.70 MSRV * fix(miette): another fix for MSRV 1.70 * docs(miette): sync README and `rustdoc` --- Cargo.toml | 2 +- src/error.rs | 88 +++++++++++++++++++++++++++++++--- src/eyreish/error.rs | 2 +- src/eyreish/into_diagnostic.rs | 41 ++++++++++++++-- src/handler.rs | 5 +- src/highlighters/syntect.rs | 6 +-- src/lib.rs | 32 +++++++++++++ src/panic.rs | 58 +++++++++++++++++++--- src/protocol.rs | 15 +----- 9 files changed, 214 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index eba604b..0daf2f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ rust-version = "1.70.0" exclude = ["images/", "tests/", "miette-derive/"] [dependencies] -thiserror = "2.0.11" miette-derive = { path = "miette-derive", version = "=7.5.0", optional = true } unicode-width = "0.1.11" cfg-if = "1.0.0" @@ -30,6 +29,7 @@ serde = { version = "1.0.196", features = ["derive"], optional = true } syntect = { version = "5.1.0", optional = true } [dev-dependencies] +thiserror = "2.0.11" semver = "1.0.21" # Eyre devdeps diff --git a/src/error.rs b/src/error.rs index 4e57a78..f77ce1e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,25 +1,51 @@ -use std::{fmt, io}; - -use thiserror::Error; +use std::{ + error::Error, + fmt::{self, Display}, + io, +}; use crate::Diagnostic; /** Error enum for miette. Used by certain operations in the protocol. */ -#[derive(Debug, Error)] +#[derive(Debug)] pub enum MietteError { /// Wrapper around [`std::io::Error`]. This is returned when something went /// wrong while reading a [`SourceCode`](crate::SourceCode). - #[error(transparent)] - IoError(#[from] io::Error), + IoError(io::Error), /// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the /// bounds of a given [`SourceCode`](crate::SourceCode). - #[error("The given offset is outside the bounds of its Source")] OutOfBounds, } +impl Display for MietteError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + MietteError::IoError(error) => write!(f, "{error}"), + MietteError::OutOfBounds => { + write!(f, "The given offset is outside the bounds of its Source") + } + } + } +} + +impl Error for MietteError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + MietteError::IoError(error) => error.source(), + MietteError::OutOfBounds => None, + } + } +} + +impl From for MietteError { + fn from(value: io::Error) -> Self { + Self::IoError(value) + } +} + impl Diagnostic for MietteError { fn code<'a>(&'a self) -> Option> { match self { @@ -49,3 +75,51 @@ impl Diagnostic for MietteError { ))) } } + +#[cfg(test)] +pub(crate) mod tests { + use std::{error::Error, io::ErrorKind}; + + use super::*; + + #[derive(Debug)] + pub(crate) struct TestError(pub(crate) io::Error); + + impl Display for TestError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "testing, testing...") + } + } + + impl Error for TestError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.0) + } + } + + #[test] + fn io_error() { + let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire"); + let outer_error = TestError(inner_error); + let io_error = io::Error::new(ErrorKind::Other, outer_error); + + let miette_error = MietteError::from(io_error); + + assert_eq!(miette_error.to_string(), "testing, testing..."); + assert_eq!( + miette_error.source().unwrap().to_string(), + "halt and catch fire" + ); + } + + #[test] + fn out_of_bounds() { + let miette_error = MietteError::OutOfBounds; + + assert_eq!( + miette_error.to_string(), + "The given offset is outside the bounds of its Source" + ); + assert_eq!(miette_error.source().map(ToString::to_string), None); + } +} diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs index cbb4aee..706470e 100644 --- a/src/eyreish/error.rs +++ b/src/eyreish/error.rs @@ -280,7 +280,7 @@ impl Report { /// The root cause is the last error in the iterator produced by /// [`chain()`](Report::chain). pub fn root_cause(&self) -> &(dyn StdError + 'static) { - self.chain().last().unwrap() + self.chain().next_back().unwrap() } /// Returns true if `E` is the type held by this error object. diff --git a/src/eyreish/into_diagnostic.rs b/src/eyreish/into_diagnostic.rs index f862ac6..1f90c34 100644 --- a/src/eyreish/into_diagnostic.rs +++ b/src/eyreish/into_diagnostic.rs @@ -1,12 +1,24 @@ -use thiserror::Error; +use std::{error::Error, fmt::Display}; use crate::{Diagnostic, Report}; /// Convenience [`Diagnostic`] that can be used as an "anonymous" wrapper for /// Errors. This is intended to be paired with [`IntoDiagnostic`]. -#[derive(Debug, Error)] -#[error(transparent)] +#[derive(Debug)] pub(crate) struct DiagnosticError(pub(crate) Box); + +impl Display for DiagnosticError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let msg = &self.0; + write!(f, "{msg}") + } +} +impl Error for DiagnosticError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + self.0.source() + } +} + impl Diagnostic for DiagnosticError {} /** @@ -31,3 +43,26 @@ impl IntoDiagnostic for R self.map_err(|e| DiagnosticError(Box::new(e)).into()) } } + +#[cfg(test)] +mod tests { + use std::io::{self, ErrorKind}; + + use super::*; + + use crate::error::tests::TestError; + + #[test] + fn diagnostic_error() { + let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire"); + let outer_error: Result<(), _> = Err(TestError(inner_error)); + + let diagnostic_error = outer_error.into_diagnostic().unwrap_err(); + + assert_eq!(diagnostic_error.to_string(), "testing, testing..."); + assert_eq!( + diagnostic_error.source().unwrap().to_string(), + "halt and catch fire" + ); + } +} diff --git a/src/handler.rs b/src/handler.rs index c6ac348..c993c43 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -424,7 +424,7 @@ impl HighlighterOption { highlighter: Option, supports_color: bool, ) -> HighlighterOption { - if color == Some(false) || (color == None && !supports_color) { + if color == Some(false) || (color.is_none() && !supports_color) { return HighlighterOption::Disable; } highlighter @@ -433,6 +433,9 @@ impl HighlighterOption { } } +// NOTE: This is manually implemented so that it's clearer what's going on with +// the conditional compilation — clippy isn't picking up the `cfg` stuff here +#[allow(clippy::derivable_impls)] impl Default for HighlighterOption { fn default() -> Self { cfg_if! { diff --git a/src/highlighters/syntect.rs b/src/highlighters/syntect.rs index 538124c..55aa8b6 100644 --- a/src/highlighters/syntect.rs +++ b/src/highlighters/syntect.rs @@ -96,12 +96,12 @@ impl SyntectHighlighter { } } // finally, attempt to guess syntax based on first line - return self.syntax_set.find_syntax_by_first_line( + self.syntax_set.find_syntax_by_first_line( std::str::from_utf8(contents.data()) .ok()? .split('\n') .next()?, - ); + ) } } @@ -115,7 +115,7 @@ pub(crate) struct SyntectHighlighterState<'h> { use_bg_color: bool, } -impl<'h> HighlighterState for SyntectHighlighterState<'h> { +impl HighlighterState for SyntectHighlighterState<'_> { fn highlight_line<'s>(&mut self, line: &'s str) -> Vec> { if let Ok(ops) = self.parse_state.parse_line(line, self.syntax_set) { let use_bg_color = self.use_bg_color; diff --git a/src/lib.rs b/src/lib.rs index 6396c9f..2e07663 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,7 @@ //! - [... handler options](#-handler-options) //! - [... dynamic diagnostics](#-dynamic-diagnostics) //! - [... syntax highlighting](#-syntax-highlighting) +//! - [... primary label](#-primary-label) //! - [... collection of labels](#-collection-of-labels) //! - [Acknowledgements](#acknowledgements) //! - [License](#license) @@ -690,6 +691,37 @@ //! [`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting) //! method. See the [`highlighters`] module docs for more details. //! +//! ### ... primary label +//! +//! You can use the `primary` parameter to `label` to indicate that the label +//! is the primary label. +//! +//! ```rust,ignore +//! #[derive(Debug, Diagnostic, Error)] +//! #[error("oops!")] +//! struct MyError { +//! #[label(primary, "main issue")] +//! primary_span: SourceSpan, +//! +//! #[label("other label")] +//! other_span: SourceSpan, +//! } +//! ``` +//! +//! The `primary` parameter can be used at most once: +//! +//! ```rust,ignore +//! #[derive(Debug, Diagnostic, Error)] +//! #[error("oops!")] +//! struct MyError { +//! #[label(primary, "main issue")] +//! primary_span: SourceSpan, +//! +//! #[label(primary, "other label")] // Error: Cannot have more than one primary label. +//! other_span: SourceSpan, +//! } +//! ``` +//! //! ### ... collection of labels //! //! When the number of labels is unknown, you can use a collection of `SourceSpan` diff --git a/src/panic.rs b/src/panic.rs index 123ab68..b1dfd4a 100644 --- a/src/panic.rs +++ b/src/panic.rs @@ -1,7 +1,8 @@ -use backtrace::Backtrace; -use thiserror::Error; +use std::{error::Error, fmt::Display}; -use crate::{self as miette, Context, Diagnostic, Result}; +use backtrace::Backtrace; + +use crate::{Context, Diagnostic, Result}; /// Tells miette to render panics using its rendering engine. pub fn set_panic_hook() { @@ -25,11 +26,27 @@ pub fn set_panic_hook() { })); } -#[derive(Debug, Error, Diagnostic)] -#[error("{0}{panic}", panic = Panic::backtrace())] -#[diagnostic(help("set the `RUST_BACKTRACE=1` environment variable to display a backtrace."))] +#[derive(Debug)] struct Panic(String); +impl Display for Panic { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let msg = &self.0; + let panic = Panic::backtrace(); + write!(f, "{msg}{panic}") + } +} + +impl Error for Panic {} + +impl Diagnostic for Panic { + fn help<'a>(&'a self) -> Option> { + Some(Box::new( + "set the `RUST_BACKTRACE=1` environment variable to display a backtrace.", + )) + } +} + impl Panic { fn backtrace() -> String { use std::fmt::Write; @@ -84,3 +101,32 @@ impl Panic { "".into() } } + +#[cfg(test)] +mod tests { + use std::error::Error; + + use super::*; + + #[test] + fn panic() { + let panic = Panic("ruh roh raggy".to_owned()); + + assert_eq!(panic.to_string(), "ruh roh raggy"); + assert!(panic.source().is_none()); + assert!(panic.code().is_none()); + assert!(panic.severity().is_none()); + assert_eq!( + panic.help().map(|h| h.to_string()), + Some( + "set the `RUST_BACKTRACE=1` environment variable to display a backtrace." + .to_owned() + ) + ); + assert!(panic.url().is_none()); + assert!(panic.source_code().is_none()); + assert!(panic.labels().is_none()); + assert!(panic.related().is_none()); + assert!(panic.diagnostic_source().is_none()); + } +} diff --git a/src/protocol.rs b/src/protocol.rs index c2df294..96ae9a3 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::{DiagnosticError, MietteError}; /// Adds rich metadata to your Error that can be used by /// [`Report`](crate::Report) to print really nice and human-friendly error @@ -174,18 +174,7 @@ impl From for Box { impl From> for Box { fn from(s: Box) -> Self { - #[derive(thiserror::Error)] - #[error(transparent)] - struct BoxedDiagnostic(Box); - impl fmt::Debug for BoxedDiagnostic { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Debug::fmt(&self.0, f) - } - } - - impl Diagnostic for BoxedDiagnostic {} - - Box::new(BoxedDiagnostic(s)) + Box::new(DiagnosticError(s)) } }