From 243ea534e84593652ff44f4f6a81e9127f5aa095 Mon Sep 17 00:00:00 2001 From: Paul Schoenfelder <955793+bitwalker@users.noreply.github.com> Date: Wed, 6 Mar 2024 06:45:47 -0500 Subject: [PATCH 01/10] fix: support no-std environments --- Cargo.toml | 11 ++++-- miette-derive/src/code.rs | 10 +++--- miette-derive/src/diagnostic_source.rs | 6 ++-- miette-derive/src/forward.rs | 18 +++++----- miette-derive/src/help.rs | 12 +++---- miette-derive/src/label.rs | 14 ++++---- miette-derive/src/related.rs | 8 ++--- miette-derive/src/severity.rs | 2 +- miette-derive/src/source_code.rs | 4 +-- miette-derive/src/url.rs | 6 ++-- src/chain.rs | 7 +++- src/diagnostic_chain.rs | 15 ++++---- src/diagnostic_impls.rs | 8 +++-- src/error.rs | 43 ++++++++++++++++++----- src/eyreish/context.rs | 9 ++++- src/eyreish/error.rs | 16 ++++++++- src/eyreish/into_diagnostic.rs | 26 +++++++++++--- src/eyreish/kind.rs | 6 ++++ src/eyreish/mod.rs | 29 ++++++++++++---- src/eyreish/ptr.rs | 6 +++- src/eyreish/wrapper.rs | 18 +++++----- src/handler.rs | 2 +- src/handlers/debug.rs | 19 +++++----- src/handlers/json.rs | 5 ++- src/handlers/narratable.rs | 9 +++-- src/lib.rs | 19 ++++++++++ src/macro_helpers.rs | 1 + src/miette_diagnostic.rs | 19 ++++++---- src/named_source.rs | 10 ++++-- src/protocol.rs | 48 +++++++++++++++++--------- src/source_impls.rs | 27 ++++++++++----- 31 files changed, 303 insertions(+), 130 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f89f14b..cbabc78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,11 @@ rust-version = "1.82.0" exclude = ["images/", "tests/", "miette-derive/"] [dependencies] +thiserror = "2.0.11" miette-derive = { path = "miette-derive", version = "=7.6.0", optional = true } -unicode-width = "0.2.0" +unicode-width = { version = "0.2.0", default-features = false } cfg-if = "1.0.0" +spin = { version = "0.9", default-features = false, features = ["mutex", "spin_mutex", "lazy"] } owo-colors = { version = "4.0.0", optional = true } textwrap = { version = "0.16.0", default-features = false, features = ["unicode-linebreak", "unicode-width"], optional = true } @@ -29,9 +31,11 @@ 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" +[build-dependencies] +rustc_version = "0.2" + # Eyre devdeps futures = { version = "0.3", default-features = false } indenter = "0.3.3" @@ -45,7 +49,8 @@ serde_json = "1.0.113" strip-ansi-escapes = "0.2.0" [features] -default = ["derive"] +default = ["derive", "std"] +std = ["thiserror/std", "fancy-no-syscall"] derive = ["dep:miette-derive"] no-format-args-capture = [] fancy-base = [ diff --git a/miette-derive/src/code.rs b/miette-derive/src/code.rs index 22dc795..8ebe920 100644 --- a/miette-derive/src/code.rs +++ b/miette-derive/src/code.rs @@ -56,13 +56,13 @@ impl Code { let code = &code.as_ref()?.0; Some(match fields { syn::Fields::Named(_) => { - quote! { Self::#ident { .. } => std::option::Option::Some(std::boxed::Box::new(#code)), } + quote! { Self::#ident { .. } => Option::Some(alloc::boxed::Box::new(#code)), } } syn::Fields::Unnamed(_) => { - quote! { Self::#ident(..) => std::option::Option::Some(std::boxed::Box::new(#code)), } + quote! { Self::#ident(..) => Option::Some(alloc::boxed::Box::new(#code)), } } syn::Fields::Unit => { - quote! { Self::#ident => std::option::Option::Some(std::boxed::Box::new(#code)), } + quote! { Self::#ident => Option::Some(alloc::boxed::Box::new(#code)), } } }) }, @@ -72,8 +72,8 @@ impl Code { pub(crate) fn gen_struct(&self) -> Option { let code = &self.0; Some(quote! { - fn code(&self) -> std::option::Option> { - std::option::Option::Some(std::boxed::Box::new(#code)) + fn code(&self) -> Option> { + Some(alloc::boxed::Box::new(#code)) } }) } diff --git a/miette-derive/src/diagnostic_source.rs b/miette-derive/src/diagnostic_source.rs index 1104eb7..7864e4a 100644 --- a/miette-derive/src/diagnostic_source.rs +++ b/miette-derive/src/diagnostic_source.rs @@ -59,7 +59,7 @@ impl DiagnosticSource { }; quote! { Self::#ident #display_pat => { - std::option::Option::Some(std::borrow::Borrow::borrow(#rel)) + Some(alloc::borrow::Borrow::borrow(#rel)) } } }) @@ -70,8 +70,8 @@ impl DiagnosticSource { pub(crate) fn gen_struct(&self) -> Option { let rel = &self.0; Some(quote! { - fn diagnostic_source<'a>(&'a self) -> std::option::Option<&'a dyn miette::Diagnostic> { - std::option::Option::Some(std::borrow::Borrow::borrow(&self.#rel)) + fn diagnostic_source<'a>(&'a self) -> Option<&'a dyn miette::Diagnostic> { + Some(alloc::borrow::Borrow::borrow(&self.#rel)) } }) } diff --git a/miette-derive/src/forward.rs b/miette-derive/src/forward.rs index d170366..cdc658f 100644 --- a/miette-derive/src/forward.rs +++ b/miette-derive/src/forward.rs @@ -58,34 +58,34 @@ impl WhichFn { pub fn signature(&self) -> TokenStream { match self { Self::Code => quote! { - fn code(& self) -> std::option::Option> + fn code(& self) -> Option> }, Self::Help => quote! { - fn help(& self) -> std::option::Option> + fn help(& self) -> Option> }, Self::Url => quote! { - fn url(& self) -> std::option::Option> + fn url(& self) -> Option> }, Self::Severity => quote! { - fn severity(&self) -> std::option::Option + fn severity(&self) -> Option }, Self::Related => quote! { - fn related(&self) -> std::option::Option + '_>> + fn related(&self) -> Option + '_>> }, Self::Labels => quote! { - fn labels(&self) -> std::option::Option + '_>> + fn labels(&self) -> Option + '_>> }, Self::SourceCode => quote! { - fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> + fn source_code(&self) -> Option<&dyn miette::SourceCode> }, Self::DiagnosticSource => quote! { - fn diagnostic_source(&self) -> std::option::Option<&dyn miette::Diagnostic> + fn diagnostic_source(&self) -> Option<&dyn miette::Diagnostic> }, } } pub fn catchall_arm(&self) -> TokenStream { - quote! { _ => std::option::Option::None } + quote! { _ => Option::None } } } diff --git a/miette-derive/src/help.rs b/miette-derive/src/help.rs index 1c21054..0169027 100644 --- a/miette-derive/src/help.rs +++ b/miette-derive/src/help.rs @@ -94,7 +94,7 @@ impl Help { Help::Display(display) => { let (fmt, args) = display.expand_shorthand_cloned(&display_members); Some(quote! { - Self::#ident #display_pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))), + Self::#ident #display_pat => Option::Some(alloc::boxed::Box::new(format!(#fmt #args))), }) } Help::Field(member, ty) => { @@ -108,7 +108,7 @@ impl Help { Some(quote! { Self::#ident #display_pat => { use miette::macro_helpers::ToOption; - miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&#help).as_ref().map(|#var| -> std::boxed::Box { std::boxed::Box::new(format!("{}", #var)) }) + miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&#help).as_ref().map(|#var| -> alloc::boxed::Box { alloc::boxed::Box::new(format!("{}", #var)) }) }, }) } @@ -123,21 +123,21 @@ impl Help { Help::Display(display) => { let (fmt, args) = display.expand_shorthand_cloned(&display_members); Some(quote! { - fn help(&self) -> std::option::Option> { + fn help(&self) -> Option> { #[allow(unused_variables, deprecated)] let Self #display_pat = self; - std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))) + Option::Some(alloc::boxed::Box::new(format!(#fmt #args))) } }) } Help::Field(member, ty) => { let var = quote! { __miette_internal_var }; Some(quote! { - fn help(&self) -> std::option::Option> { + fn help(&self) -> Option> { #[allow(unused_variables, deprecated)] let Self #display_pat = self; use miette::macro_helpers::ToOption; - miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#member).as_ref().map(|#var| -> std::boxed::Box { std::boxed::Box::new(format!("{}", #var)) }) + miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#member).as_ref().map(|#var| -> alloc::boxed::Box { alloc::boxed::Box::new(format!("{}", #var)) }) } }) } diff --git a/miette-derive/src/label.rs b/miette-derive/src/label.rs index ab2ceac..28db920 100644 --- a/miette-derive/src/label.rs +++ b/miette-derive/src/label.rs @@ -175,9 +175,9 @@ impl Labels { let var = quote! { __miette_internal_var }; let display = if let Some(display) = label { let (fmt, args) = display.expand_shorthand_cloned(&display_members); - quote! { std::option::Option::Some(format!(#fmt #args)) } + quote! { Option::Some(format!(#fmt #args)) } } else { - quote! { std::option::Option::None } + quote! { Option::None } }; let ctor = if *lbl_ty == LabelType::Primary { quote! { miette::LabeledSpan::new_primary_with_span } @@ -205,9 +205,9 @@ impl Labels { } let display = if let Some(display) = label { let (fmt, args) = display.expand_shorthand_cloned(&display_members); - quote! { std::option::Option::Some(format!(#fmt #args)) } + quote! { Option::Some(format!(#fmt #args)) } } else { - quote! { std::option::Option::None } + quote! { Option::None } }; Some(quote! { .chain({ @@ -226,7 +226,7 @@ impl Labels { Some(quote! { #[allow(unused_variables)] - fn labels(&self) -> std::option::Option + '_>> { + fn labels(&self) -> Option + '_>> { use miette::macro_helpers::ToOption; let Self #display_pat = self; @@ -236,7 +236,7 @@ impl Labels { .into_iter() #(#collections_chain)*; - std::option::Option::Some(Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap))) + Option::Some(alloc::boxed::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap))) } }) } @@ -322,7 +322,7 @@ impl Labels { ] .into_iter() #(#collections_chain)*; - std::option::Option::Some(std::boxed::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap))) + Option::Some(alloc::boxed::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap))) } }), } diff --git a/miette-derive/src/related.rs b/miette-derive/src/related.rs index 9b7f9e1..8158ea6 100644 --- a/miette-derive/src/related.rs +++ b/miette-derive/src/related.rs @@ -55,7 +55,7 @@ impl Related { }; quote! { Self::#ident #display_pat => { - std::option::Option::Some(std::boxed::Box::new( + Option::Some(alloc::boxed::Box::new( #rel.iter().map(|x| -> &(dyn miette::Diagnostic) { &*x }) )) } @@ -68,9 +68,9 @@ impl Related { pub(crate) fn gen_struct(&self) -> Option { let rel = &self.0; Some(quote! { - fn related<'a>(&'a self) -> std::option::Option + 'a>> { - use ::core::borrow::Borrow; - std::option::Option::Some(std::boxed::Box::new( + fn related<'a>(&'a self) -> Option + 'a>> { + use alloc::borrow::Borrow; + Option::Some(alloc::boxed::Box::new( self.#rel.iter().map(|x| -> &(dyn miette::Diagnostic) { &*x.borrow() }) )) } diff --git a/miette-derive/src/severity.rs b/miette-derive/src/severity.rs index 4f26e4e..40f1bd7 100644 --- a/miette-derive/src/severity.rs +++ b/miette-derive/src/severity.rs @@ -72,7 +72,7 @@ impl Severity { syn::Fields::Unit => quote! {}, }; Some( - quote! { Self::#ident #fields => std::option::Option::Some(miette::Severity::#severity), }, + quote! { Self::#ident #fields => Option::Some(miette::Severity::#severity), }, ) }, ) diff --git a/miette-derive/src/source_code.rs b/miette-derive/src/source_code.rs index e1b85ab..f130953 100644 --- a/miette-derive/src/source_code.rs +++ b/miette-derive/src/source_code.rs @@ -74,7 +74,7 @@ impl SourceCode { Some(quote! { #[allow(unused_variables)] - fn source_code(&self) -> std::option::Option<&dyn miette::SourceCode> { + fn source_code(&self) -> Option<&dyn miette::SourceCode> { let Self #display_pat = self; #ret } @@ -101,7 +101,7 @@ impl SourceCode { } } else { quote! { - std::option::Option::Some(#field) + Option::Some(#field) } }; match &fields { diff --git a/miette-derive/src/url.rs b/miette-derive/src/url.rs index 734d1a4..2583a1c 100644 --- a/miette-derive/src/url.rs +++ b/miette-derive/src/url.rs @@ -96,7 +96,7 @@ impl Url { } }; Some(quote! { - Self::#ident #pat => std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))), + Self::#ident #pat => Option::Some(alloc::boxed::Box::new(format!(#fmt #args))), }) }, ) @@ -129,10 +129,10 @@ impl Url { } }; Some(quote! { - fn url(&self) -> std::option::Option> { + fn url(&self) -> Option> { #[allow(unused_variables, deprecated)] let Self #pat = self; - std::option::Option::Some(std::boxed::Box::new(format!(#fmt #args))) + Option::Some(alloc::boxed::Box::new(format!(#fmt #args))) } }) } diff --git a/src/chain.rs b/src/chain.rs index 369ff7d..e21d82a 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -3,8 +3,13 @@ Iterate over error `.source()` chains. NOTE: This module is taken wholesale from . */ +extern crate alloc; + +#[cfg(feature = "std")] use std::error::Error as StdError; -use std::vec; +#[cfg(not(feature = "std"))] +use crate::StdError; +use alloc::vec::{self, Vec}; use ChainState::*; diff --git a/src/diagnostic_chain.rs b/src/diagnostic_chain.rs index 996fdba..e9dcccd 100644 --- a/src/diagnostic_chain.rs +++ b/src/diagnostic_chain.rs @@ -2,7 +2,10 @@ Iterate over error `.diagnostic_source()` chains. */ +extern crate alloc; + use crate::protocol::Diagnostic; +use alloc::string::ToString; /// Iterator of a chain of cause errors. #[derive(Clone, Default)] @@ -18,7 +21,7 @@ impl<'a> DiagnosticChain<'a> { } } - pub(crate) fn from_stderror(head: &'a (dyn std::error::Error + 'static)) -> Self { + pub(crate) fn from_stderror(head: &'a (dyn crate::StdError + 'static)) -> Self { DiagnosticChain { state: Some(ErrorKind::StdError(head)), } @@ -59,7 +62,7 @@ impl ExactSizeIterator for DiagnosticChain<'_> { #[derive(Clone)] pub(crate) enum ErrorKind<'a> { Diagnostic(&'a dyn Diagnostic), - StdError(&'a (dyn std::error::Error + 'static)), + StdError(&'a (dyn crate::StdError + 'static)), } impl<'a> ErrorKind<'a> { @@ -74,8 +77,8 @@ impl<'a> ErrorKind<'a> { } } -impl std::fmt::Debug for ErrorKind<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for ErrorKind<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { ErrorKind::Diagnostic(d) => d.fmt(f), ErrorKind::StdError(e) => e.fmt(f), @@ -83,8 +86,8 @@ impl std::fmt::Debug for ErrorKind<'_> { } } -impl std::fmt::Display for ErrorKind<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for ErrorKind<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { ErrorKind::Diagnostic(d) => d.fmt(f), ErrorKind::StdError(e) => e.fmt(f), diff --git a/src/diagnostic_impls.rs b/src/diagnostic_impls.rs index aebe56b..4bb7345 100644 --- a/src/diagnostic_impls.rs +++ b/src/diagnostic_impls.rs @@ -1,10 +1,14 @@ /*! Default trait implementations for [`Diagnostic`]. */ +extern crate alloc; -use std::{convert::Infallible, fmt::Display}; +use core::{convert::Infallible, fmt::Display}; +use alloc::boxed::Box; -use crate::{Diagnostic, LabeledSpan, Severity, SourceCode}; +use crate::{Diagnostic, LabeledSpan, Severity, SourceCode, StdError}; + +impl StdError for Infallible {} impl Diagnostic for Infallible { fn code<'a>(&'a self) -> Option> { diff --git a/src/error.rs b/src/error.rs index f77ce1e..5fab5b8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,8 +1,13 @@ -use std::{ - error::Error, - fmt::{self, Display}, - io, -}; +extern crate alloc; + +#[cfg(feature = "std")] +use std::io; +#[cfg(feature = "std")] +use std::error::Error; +#[cfg(not(feature = "std"))] +use crate::StdError as Error; +use core::fmt::{self, Display}; +use alloc::boxed::Box; use crate::Diagnostic; @@ -11,8 +16,9 @@ Error enum for miette. Used by certain operations in the protocol. */ #[derive(Debug)] pub enum MietteError { - /// Wrapper around [`std::io::Error`]. This is returned when something went + /// Wrapper around [`io::Error`]. This is returned when something went /// wrong while reading a [`SourceCode`](crate::SourceCode). + #[cfg(feature = "std")] IoError(io::Error), /// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the @@ -23,6 +29,7 @@ pub enum MietteError { impl Display for MietteError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + #[cfg(feature = "std")] MietteError::IoError(error) => write!(f, "{error}"), MietteError::OutOfBounds => { write!(f, "The given offset is outside the bounds of its Source") @@ -34,12 +41,14 @@ impl Display for MietteError { impl Error for MietteError { fn source(&self) -> Option<&(dyn Error + 'static)> { match self { + #[cfg(feature = "std")] MietteError::IoError(error) => error.source(), MietteError::OutOfBounds => None, } } } +#[cfg(feature = "std")] impl From for MietteError { fn from(value: io::Error) -> Self { Self::IoError(value) @@ -49,6 +58,7 @@ impl From for MietteError { impl Diagnostic for MietteError { fn code<'a>(&'a self) -> Option> { match self { + #[cfg(feature = "std")] MietteError::IoError(_) => Some(Box::new("miette::io_error")), MietteError::OutOfBounds => Some(Box::new("miette::span_out_of_bounds")), } @@ -56,6 +66,7 @@ impl Diagnostic for MietteError { fn help<'a>(&'a self) -> Option> { match self { + #[cfg(feature = "std")] MietteError::IoError(_) => None, MietteError::OutOfBounds => Some(Box::new( "Double-check your spans. Do you have an off-by-one error?", @@ -66,10 +77,11 @@ impl Diagnostic for MietteError { fn url<'a>(&'a self) -> Option> { let crate_version = env!("CARGO_PKG_VERSION"); let variant = match self { + #[cfg(feature = "std")] MietteError::IoError(_) => "#variant.IoError", MietteError::OutOfBounds => "#variant.OutOfBounds", }; - Some(Box::new(format!( + Some(Box::new(alloc::format!( "https://docs.rs/miette/{}/miette/enum.MietteError.html{}", crate_version, variant, ))) @@ -78,12 +90,18 @@ impl Diagnostic for MietteError { #[cfg(test)] pub(crate) mod tests { - use std::{error::Error, io::ErrorKind}; + #[cfg(feature = "std")] + use std::io::ErrorKind; + #[cfg(not(feature = "std"))] + use crate::StdError as Error; use super::*; #[derive(Debug)] + #[cfg(feature = "std")] pub(crate) struct TestError(pub(crate) io::Error); + #[cfg(not(feature = "std"))] + pub(crate) struct TestError(pub(crate) &'static str); impl Display for TestError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -91,12 +109,21 @@ pub(crate) mod tests { } } + #[cfg(feature = "std")] impl Error for TestError { fn source(&self) -> Option<&(dyn Error + 'static)> { Some(&self.0) } } + #[cfg(not(feature = "std"))] + impl Error for TestError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + None + } + } + + #[cfg(feature = "std")] #[test] fn io_error() { let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire"); diff --git a/src/eyreish/context.rs b/src/eyreish/context.rs index ee1fa08..1dbb543 100644 --- a/src/eyreish/context.rs +++ b/src/eyreish/context.rs @@ -1,8 +1,15 @@ +extern crate alloc; + use super::error::{ContextError, ErrorImpl}; use super::{Report, WrapErr}; use core::fmt::{self, Debug, Display, Write}; +use core::convert::Infallible; +#[cfg(feature = "std")] use std::error::Error as StdError; +#[cfg(not(feature = "std"))] +use crate::StdError as StdError; +use alloc::boxed::Box; use crate::{Diagnostic, LabeledSpan}; @@ -38,7 +45,7 @@ mod ext { } } -impl WrapErr for Option { +impl WrapErr for Option { fn wrap_err(self, msg: D) -> Result where D: Display + Send + Sync + 'static, diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs index 706470e..238e46c 100644 --- a/src/eyreish/error.rs +++ b/src/eyreish/error.rs @@ -1,8 +1,14 @@ +extern crate alloc; + use core::any::TypeId; use core::fmt::{self, Debug, Display}; use core::mem::ManuallyDrop; use core::ptr::{self, NonNull}; +#[cfg(feature = "std")] use std::error::Error as StdError; +#[cfg(not(feature = "std"))] +use crate::StdError as StdError; +use alloc::boxed::Box; use super::ptr::{Mut, Own, Ref}; use super::Report; @@ -429,7 +435,7 @@ impl Report { /// Construct a [`Report`] directly from an error-like type pub fn from_err(err: E) -> Self where - E: std::error::Error + Send + Sync + 'static, + E: StdError + Send + Sync + 'static, { super::DiagnosticError(Box::new(err)).into() } @@ -816,8 +822,16 @@ impl AsRef for Report { } } +#[cfg(feature = "std")] impl std::borrow::Borrow for Report { fn borrow(&self) -> &(dyn Diagnostic + 'static) { self.as_ref() } } + +#[cfg(not(feature = "std"))] +impl core::borrow::Borrow for Report { + fn borrow(&self) -> &(dyn Diagnostic + 'static) { + self.as_ref() + } +} diff --git a/src/eyreish/into_diagnostic.rs b/src/eyreish/into_diagnostic.rs index 1f90c34..b739b24 100644 --- a/src/eyreish/into_diagnostic.rs +++ b/src/eyreish/into_diagnostic.rs @@ -1,14 +1,21 @@ -use std::{error::Error, fmt::Display}; +extern crate alloc; + +#[cfg(feature = "std")] +use std::error::Error; +#[cfg(not(feature = "std"))] +use crate::StdError as Error; +use core::fmt::Display; +use alloc::boxed::Box; 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)] -pub(crate) struct DiagnosticError(pub(crate) Box); +pub(crate) struct DiagnosticError(pub(crate) Box); -impl Display for DiagnosticError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for DiagnosticError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let msg = &self.0; write!(f, "{msg}") } @@ -38,20 +45,31 @@ pub trait IntoDiagnostic { fn into_diagnostic(self) -> Result; } +#[cfg(feature = "std")] impl IntoDiagnostic for Result { fn into_diagnostic(self) -> Result { self.map_err(|e| DiagnosticError(Box::new(e)).into()) } } +#[cfg(not(feature = "std"))] +impl IntoDiagnostic for Result { + fn into_diagnostic(self) -> Result { + self.map_err(|e| DiagnosticError(Box::new(e)).into()) + } +} + #[cfg(test)] mod tests { + #[cfg(feature = "std")] use std::io::{self, ErrorKind}; use super::*; + #[cfg(feature = "std")] use crate::error::tests::TestError; + #[cfg(feature = "std")] #[test] fn diagnostic_error() { let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire"); diff --git a/src/eyreish/kind.rs b/src/eyreish/kind.rs index 6b0f5ec..e6dd978 100644 --- a/src/eyreish/kind.rs +++ b/src/eyreish/kind.rs @@ -45,11 +45,17 @@ // let error = $msg; // (&error).miette_kind().new(error) +#[cfg(not(feature = "std"))] +extern crate alloc; + use super::Report; use core::fmt::{Debug, Display}; use crate::Diagnostic; +#[cfg(not(feature = "std"))] +use alloc::boxed::Box; + pub struct Adhoc; pub trait AdhocKind: Sized { diff --git a/src/eyreish/mod.rs b/src/eyreish/mod.rs index 3f35103..8a4598c 100644 --- a/src/eyreish/mod.rs +++ b/src/eyreish/mod.rs @@ -4,10 +4,16 @@ clippy::new_ret_no_self, clippy::wrong_self_convention )] -use core::fmt::Display; +extern crate alloc; +use core::fmt::Display; +use alloc::boxed::Box; + +#[cfg(feature = "std")] use std::error::Error as StdError; -use std::sync::OnceLock; +#[cfg(not(feature = "std"))] +use crate::StdError as StdError; +use spin::Once; #[allow(unreachable_pub)] pub use into_diagnostic::*; @@ -61,7 +67,11 @@ unsafe impl Send for Report {} pub type ErrorHook = Box Box + Sync + Send + 'static>; -static HOOK: OnceLock = OnceLock::new(); +static HOOK: Once = Once::new(); + +fn default_hook() -> ErrorHook { + Box::new(get_default_printer) +} /// Error indicating that [`set_hook()`] was unable to install the provided /// [`ErrorHook`]. @@ -81,18 +91,23 @@ impl Diagnostic for InstallError {} Set the error hook. */ pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { - HOOK.set(hook).map_err(|_| InstallError) + HOOK.call_once(|| hook); + Ok(()) } #[cfg_attr(track_caller, track_caller)] #[cfg_attr(not(track_caller), allow(unused_mut))] fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box { - let hook = HOOK.get_or_init(|| Box::new(get_default_printer)).as_ref(); + static DEFAULT: Once = Once::new(); + let hook = HOOK.get().unwrap_or_else(|| { + DEFAULT.call_once(|| default_hook()); + DEFAULT.get().unwrap() + }); #[cfg(track_caller)] { let mut handler = hook(error); - handler.track_caller(std::panic::Location::caller()); + handler.track_caller(core::panic::Location::caller()); handler } #[cfg(not(track_caller))] @@ -193,7 +208,7 @@ pub trait ReportHandler: core::any::Any + Send + Sync { /// Store the location of the caller who constructed this error report #[allow(unused_variables)] - fn track_caller(&mut self, location: &'static std::panic::Location<'static>) {} + fn track_caller(&mut self, location: &'static core::panic::Location<'static>) {} } /// type alias for `Result` diff --git a/src/eyreish/ptr.rs b/src/eyreish/ptr.rs index 40c9c7b..947b5bd 100644 --- a/src/eyreish/ptr.rs +++ b/src/eyreish/ptr.rs @@ -1,4 +1,8 @@ -use std::{marker::PhantomData, ptr::NonNull}; +extern crate alloc; + +use core::marker::PhantomData; +use core::ptr::NonNull; +use alloc::boxed::Box; #[repr(transparent)] /// A raw pointer that owns its pointee diff --git a/src/eyreish/wrapper.rs b/src/eyreish/wrapper.rs index bab7320..6c03cc0 100644 --- a/src/eyreish/wrapper.rs +++ b/src/eyreish/wrapper.rs @@ -1,6 +1,14 @@ +extern crate alloc; + use core::fmt::{self, Debug, Display}; +#[cfg(feature = "std")] use std::error::Error as StdError; +#[cfg(not(feature = "std"))] +use crate::StdError as StdError; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec::Vec; use crate::{Diagnostic, LabeledSpan, Report, SourceCode}; @@ -107,16 +115,6 @@ impl StdError for BoxedError { fn source(&self) -> Option<&(dyn StdError + 'static)> { self.0.source() } - - fn description(&self) -> &str { - #[allow(deprecated)] - self.0.description() - } - - fn cause(&self) -> Option<&dyn StdError> { - #[allow(deprecated)] - self.0.cause() - } } pub(crate) struct WithSourceCode { diff --git a/src/handler.rs b/src/handler.rs index 5cd5a53..37e4826 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -8,7 +8,7 @@ use crate::ReportHandler; use crate::ThemeCharacters; use crate::ThemeStyles; use cfg_if::cfg_if; -use std::fmt; +use core::fmt; /// Settings to control the color format used for graphical rendering. #[derive(Copy, Clone, Debug, Eq, PartialEq, Default)] diff --git a/src/handlers/debug.rs b/src/handlers/debug.rs index 629908b..1c77062 100644 --- a/src/handlers/debug.rs +++ b/src/handlers/debug.rs @@ -1,4 +1,7 @@ -use std::fmt; +extern crate alloc; + +use core::fmt; +use alloc::vec::Vec; use crate::{protocol::Diagnostic, ReportHandler}; @@ -34,25 +37,25 @@ impl DebugReportHandler { diagnostic: &dyn Diagnostic, ) -> fmt::Result { let mut diag = f.debug_struct("Diagnostic"); - diag.field("message", &format!("{}", diagnostic)); + diag.field("message", &alloc::format!("{}", diagnostic)); if let Some(code) = diagnostic.code() { - diag.field("code", &code.to_string()); + diag.field("code", &alloc::format!("{}", code)); } if let Some(severity) = diagnostic.severity() { - diag.field("severity", &format!("{:?}", severity)); + diag.field("severity", &alloc::format!("{:?}", severity)); } if let Some(url) = diagnostic.url() { - diag.field("url", &url.to_string()); + diag.field("url", &alloc::format!("{}", url)); } if let Some(help) = diagnostic.help() { - diag.field("help", &help.to_string()); + diag.field("help", &alloc::format!("{}", help)); } if let Some(labels) = diagnostic.labels() { let labels: Vec<_> = labels.collect(); - diag.field("labels", &format!("{:?}", labels)); + diag.field("labels", &alloc::format!("{:?}", labels)); } if let Some(cause) = diagnostic.diagnostic_source() { - diag.field("caused by", &format!("{:?}", cause)); + diag.field("caused by", &alloc::format!("{:?}", cause)); } diag.finish()?; writeln!(f)?; diff --git a/src/handlers/json.rs b/src/handlers/json.rs index e37064c..966e348 100644 --- a/src/handlers/json.rs +++ b/src/handlers/json.rs @@ -1,4 +1,7 @@ -use std::fmt::{self, Write}; +extern crate alloc; + +use core::fmt::{self, Write}; +use alloc::string::ToString; use crate::{ diagnostic_chain::DiagnosticChain, protocol::Diagnostic, ReportHandler, Severity, SourceCode, diff --git a/src/handlers/narratable.rs b/src/handlers/narratable.rs index 8dead98..86f23b3 100644 --- a/src/handlers/narratable.rs +++ b/src/handlers/narratable.rs @@ -1,10 +1,15 @@ -use std::fmt; +extern crate alloc; + +use core::fmt; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use crate::diagnostic_chain::DiagnosticChain; use crate::protocol::{Diagnostic, Severity}; use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents}; +use alloc::string::String; +use alloc::vec::Vec; +use alloc::boxed::Box; /** [`ReportHandler`] that renders plain text and avoids extraneous graphics. @@ -290,7 +295,7 @@ impl NarratableReportHandler { let context_data = source .read_span(context_span, self.context_lines, self.context_lines) .map_err(|_| fmt::Error)?; - let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected"); + let context = core::str::from_utf8(context_data.data()).expect("Bad utf8 detected"); let mut line = context_data.line(); let mut column = context_data.column(); let mut offset = context_data.span().offset(); diff --git a/src/lib.rs b/src/lib.rs index 21ad61d..7c2fd56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![no_std] + #![deny(missing_docs, missing_debug_implementations, nonstandard_style)] #![warn(unreachable_pub, rust_2018_idioms)] #![allow(unexpected_cfgs)] @@ -815,6 +817,23 @@ //! and some from [`thiserror`](https://github.com/dtolnay/thiserror), also //! under the Apache License. Some code is taken from //! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed. +#[cfg(feature = "std")] +extern crate std; + +#[cfg(feature = "std")] +pub use std::error::Error as StdError; + +#[cfg(not(feature = "std"))] +/// Compatibility trait for error handling in no_std environments. +/// This trait provides a subset of `std::error::Error` functionality +/// suitable for no_std environments. +pub trait StdError: core::fmt::Debug + core::fmt::Display { + /// Returns the lower-level source of this error, if any. + fn source(&self) -> Option<&(dyn StdError + 'static)> { + None + } +} + #[cfg(feature = "derive")] pub use miette_derive::*; diff --git a/src/macro_helpers.rs b/src/macro_helpers.rs index 157f2b3..47a6e5f 100644 --- a/src/macro_helpers.rs +++ b/src/macro_helpers.rs @@ -51,6 +51,7 @@ impl ToLabeledSpan for ToLabelSpanWrapper { span } } +#[cfg(not(feature = "std"))] impl ToLabeledSpan for ToLabelSpanWrapper where T: Into, diff --git a/src/miette_diagnostic.rs b/src/miette_diagnostic.rs index 9863e88..07ed724 100644 --- a/src/miette_diagnostic.rs +++ b/src/miette_diagnostic.rs @@ -1,7 +1,14 @@ -use std::{ - error::Error, - fmt::{Debug, Display}, -}; +extern crate alloc; + +use core::fmt::{Debug, Display}; +#[cfg(feature = "std")] +use std::error::Error; +#[cfg(not(feature = "std"))] +use crate::StdError as Error; +use alloc::string::String; +use alloc::vec::{self, Vec}; +use alloc::format; +use alloc::boxed::Box; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -39,7 +46,7 @@ pub struct MietteDiagnostic { } impl Display for MietteDiagnostic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", &self.message) } } @@ -183,7 +190,7 @@ impl MietteDiagnostic { /// assert_eq!(diag.labels, Some(vec![label])); /// ``` pub fn with_label(mut self, label: impl Into) -> Self { - self.labels = Some(vec![label.into()]); + self.labels = Some(Vec::from([label.into()])); self } diff --git a/src/named_source.rs b/src/named_source.rs index ea11cd2..415f088 100644 --- a/src/named_source.rs +++ b/src/named_source.rs @@ -10,8 +10,14 @@ pub struct NamedSource { language: Option, } -impl std::fmt::Debug for NamedSource { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +extern crate alloc; + +use alloc::string::String; +use alloc::string::ToString; +use alloc::boxed::Box; + +impl core::fmt::Debug for NamedSource { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("NamedSource") .field("name", &self.name) .field("source", &"") diff --git a/src/protocol.rs b/src/protocol.rs index 96ae9a3..10bb283 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -3,11 +3,15 @@ This module defines the core of the miette protocol: a series of types and traits that you can implement to get access to miette's (and related library's) full reporting and such features. */ -use std::{ - fmt::{self, Display}, - fs, - panic::Location, -}; +extern crate alloc; + +use core::fmt::{self, Display}; +#[cfg(feature = "std")] +use std::fs; +use core::panic::Location; +use core::ops; +use alloc::boxed::Box; +use alloc::string::String; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -17,7 +21,7 @@ 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 /// messages. -pub trait Diagnostic: std::error::Error { +pub trait Diagnostic: crate::StdError { /// Unique diagnostic code that can be used to look up more information /// about this `Diagnostic`. Ideally also globally unique, and documented /// in the toplevel crate's documentation for easy searching. Rust path @@ -72,14 +76,10 @@ pub trait Diagnostic: std::error::Error { macro_rules! box_error_impls { ($($box_type:ty),*) => { $( - impl std::error::Error for $box_type { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + impl crate::StdError for $box_type { + fn source(&self) -> Option<&(dyn crate::StdError + 'static)> { (**self).source() } - - fn cause(&self) -> Option<&dyn std::error::Error> { - self.source() - } } )* } @@ -94,7 +94,7 @@ box_error_impls! { macro_rules! box_borrow_impls { ($($box_type:ty),*) => { $( - impl std::borrow::Borrow for $box_type { + impl core::borrow::Borrow for $box_type { fn borrow(&self) -> &(dyn Diagnostic + 'static) { self.as_ref() } @@ -152,7 +152,10 @@ impl From for Box { fn from(s: String) -> Self { struct StringError(String); + #[cfg(feature = "std")] impl std::error::Error for StringError {} + #[cfg(not(feature = "std"))] + impl crate::StdError for StringError {} impl Diagnostic for StringError {} impl Display for StringError { @@ -172,6 +175,7 @@ impl From for Box { } } +#[cfg(feature = "std")] impl From> for Box { fn from(s: Box) -> Self { Box::new(DiagnosticError(s)) @@ -594,8 +598,8 @@ impl From<(SourceOffset, usize)> for SourceSpan { } } -impl From> for SourceSpan { - fn from(range: std::ops::Range) -> Self { +impl From> for SourceSpan { + fn from(range: ops::Range) -> Self { Self { offset: range.start.into(), length: range.len(), @@ -603,12 +607,12 @@ impl From> for SourceSpan { } } -impl From> for SourceSpan { +impl From> for SourceSpan { /// # Panics /// /// Panics if the total length of the inclusive range would overflow a /// `usize`. This will only occur with the range `0..=usize::MAX`. - fn from(range: std::ops::RangeInclusive) -> Self { + fn from(range: ops::RangeInclusive) -> Self { let (start, end) = range.clone().into_inner(); Self { offset: start.into(), @@ -715,6 +719,7 @@ impl SourceOffset { /// you're shipping binaries for your application, you'll want to ignore /// the Err case or otherwise report it. #[track_caller] + #[cfg(feature = "std")] pub fn from_current_location() -> Result<(String, Self), MietteError> { let loc = Location::caller(); Ok(( @@ -723,6 +728,15 @@ impl SourceOffset { .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?, )) } + + /// Returns both the filename that was given and the offset of the caller + /// as a [`SourceOffset`]. + /// + /// In no_std environments, this is not supported and will return an error. + #[cfg(not(feature = "std"))] + pub fn from_current_location() -> Result<(String, Self), MietteError> { + Err(MietteError::OutOfBounds) + } } impl From for SourceOffset { diff --git a/src/source_impls.rs b/src/source_impls.rs index 9c26b71..4fa60c3 100644 --- a/src/source_impls.rs +++ b/src/source_impls.rs @@ -1,7 +1,16 @@ /*! Default trait implementations for [`SourceCode`]. */ -use std::{borrow::Cow, collections::VecDeque, fmt::Debug, sync::Arc}; +extern crate alloc; + +use core::fmt::Debug; +use alloc::borrow::Cow; +use alloc::borrow::ToOwned; +use alloc::collections::VecDeque; +use alloc::sync::Arc; +use alloc::string::String; +use alloc::vec::Vec; +use alloc::boxed::Box; use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents}; @@ -205,7 +214,7 @@ mod tests { fn basic() -> Result<(), MietteError> { let src = String::from("foo\n"); let contents = src.read_span(&(0, 4).into(), 0, 0)?; - assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap()); + assert_eq!("foo\n", core::str::from_utf8(contents.data()).unwrap()); assert_eq!(0, contents.line()); assert_eq!(0, contents.column()); Ok(()) @@ -215,7 +224,7 @@ mod tests { fn shifted() -> Result<(), MietteError> { let src = String::from("foobar"); let contents = src.read_span(&(3, 3).into(), 1, 1)?; - assert_eq!("foobar", std::str::from_utf8(contents.data()).unwrap()); + assert_eq!("foobar", core::str::from_utf8(contents.data()).unwrap()); assert_eq!(0, contents.line()); assert_eq!(0, contents.column()); Ok(()) @@ -225,7 +234,7 @@ mod tests { fn middle() -> Result<(), MietteError> { let src = String::from("foo\nbar\nbaz\n"); let contents = src.read_span(&(4, 4).into(), 0, 0)?; - assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap()); + assert_eq!("bar\n", core::str::from_utf8(contents.data()).unwrap()); assert_eq!(1, contents.line()); assert_eq!(0, contents.column()); Ok(()) @@ -235,7 +244,7 @@ mod tests { fn middle_of_line() -> Result<(), MietteError> { let src = String::from("foo\nbarbar\nbaz\n"); let contents = src.read_span(&(7, 4).into(), 0, 0)?; - assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap()); + assert_eq!("bar\n", core::str::from_utf8(contents.data()).unwrap()); assert_eq!(1, contents.line()); assert_eq!(3, contents.column()); Ok(()) @@ -245,7 +254,7 @@ mod tests { fn with_crlf() -> Result<(), MietteError> { let src = String::from("foo\r\nbar\r\nbaz\r\n"); let contents = src.read_span(&(5, 5).into(), 0, 0)?; - assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap()); + assert_eq!("bar\r\n", core::str::from_utf8(contents.data()).unwrap()); assert_eq!(1, contents.line()); assert_eq!(0, contents.column()); Ok(()) @@ -257,7 +266,7 @@ mod tests { let contents = src.read_span(&(8, 3).into(), 1, 1)?; assert_eq!( "foo\nbar\nbaz\n", - std::str::from_utf8(contents.data()).unwrap() + core::str::from_utf8(contents.data()).unwrap() ); assert_eq!(1, contents.line()); assert_eq!(0, contents.column()); @@ -270,7 +279,7 @@ mod tests { let contents = src.read_span(&(9, 11).into(), 1, 1)?; assert_eq!( "\nfoo\nbar\nbaz\n\n", - std::str::from_utf8(contents.data()).unwrap() + core::str::from_utf8(contents.data()).unwrap() ); assert_eq!(2, contents.line()); assert_eq!(0, contents.column()); @@ -285,7 +294,7 @@ mod tests { let contents = src.read_span(&(2, 0).into(), 2, 2)?; assert_eq!( "one\ntwo\n\n", - std::str::from_utf8(contents.data()).unwrap() + core::str::from_utf8(contents.data()).unwrap() ); assert_eq!(0, contents.line()); assert_eq!(0, contents.column()); From 79fbd0a07d7277556e2aaa9a97e10a3d73219b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Sat, 18 Oct 2025 07:44:18 -0400 Subject: [PATCH 02/10] feat(no_std): Implement comprehensive no_std support This commit finishes full no_std support to miette. --- Cargo.toml | 9 +++------ build.rs | 19 +++++++++++++++++++ examples/serde_json.rs | 2 ++ miette-derive/src/severity.rs | 4 +--- src/chain.rs | 3 --- src/diagnostic_chain.rs | 3 --- src/diagnostic_impls.rs | 6 ++---- src/error.rs | 24 ++++++++++++------------ src/eyreish/context.rs | 17 ++++++++++++----- src/eyreish/error.rs | 11 +++-------- src/eyreish/into_diagnostic.rs | 13 +++++++------ src/eyreish/kind.rs | 3 +++ src/eyreish/mod.rs | 23 +++++++++++++++++------ src/eyreish/ptr.rs | 2 +- src/eyreish/wrapper.rs | 12 ++++++------ src/handler.rs | 16 +++++++++++++--- src/handlers/debug.rs | 2 +- src/handlers/graphical.rs | 8 +++++++- src/handlers/json.rs | 2 +- src/handlers/narratable.rs | 2 +- src/handlers/theme.rs | 6 ++++-- src/highlighters/blank.rs | 5 +++++ src/highlighters/mod.rs | 5 +++++ src/highlighters/syntect.rs | 5 +++++ src/lib.rs | 18 +++++++++++++++++- src/macro_helpers.rs | 1 - src/miette_diagnostic.rs | 11 +++++------ src/named_source.rs | 2 +- src/panic.rs | 11 +++++++++-- src/protocol.rs | 13 +++++-------- src/source_impls.rs | 10 +++++----- tests/color_format.rs | 6 +++++- tests/common/mod.rs | 2 +- tests/derive.rs | 2 ++ tests/graphical.rs | 2 ++ tests/narrated.rs | 2 ++ tests/test_boxed.rs | 10 +++++----- tests/test_derive_attr.rs | 2 ++ tests/test_derive_collection.rs | 2 ++ tests/test_diagnostic_source_macro.rs | 2 ++ tests/test_json.rs | 2 ++ tests/test_source.rs | 2 +- 42 files changed, 198 insertions(+), 104 deletions(-) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index cbabc78..bc3ead4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,22 +32,19 @@ syntect = { version = "5.1.0", optional = true } [dev-dependencies] semver = "1.0.21" - -[build-dependencies] -rustc_version = "0.2" - -# Eyre devdeps futures = { version = "0.3", default-features = false } indenter = "0.3.3" rustversion = "1.0" trybuild = { version = "1.0.89", features = ["diff"] } syn = { version = "2.0.87", features = ["full"] } regex = "1.10" - serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.113" strip-ansi-escapes = "0.2.0" +[build-dependencies] +rustc_version = "0.2" + [features] default = ["derive", "std"] std = ["thiserror/std", "fancy-no-syscall"] diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..20e6ce0 --- /dev/null +++ b/build.rs @@ -0,0 +1,19 @@ +use rustc_version::{version_meta, Channel}; + +fn main() { + if let Channel::Nightly = version_meta().unwrap().channel { + println!("cargo:rustc-cfg=nightly") + } + + // Configure track_caller based on Rust version + if let Ok(version) = rustc_version::version() { + if version >= rustc_version::Version::new(1, 46, 0) { + println!("cargo:rustc-cfg=track_caller"); + } + } + + // Add check-cfg for conditional configurations + println!("cargo:rustc-check-cfg=cfg(doc_cfg)"); + println!("cargo:rustc-check-cfg=cfg(track_caller)"); + println!("cargo:rustc-check-cfg=cfg(nightly)"); +} diff --git a/examples/serde_json.rs b/examples/serde_json.rs index d57a76a..be3fbe1 100644 --- a/examples/serde_json.rs +++ b/examples/serde_json.rs @@ -2,6 +2,8 @@ //! so the decoding source will be annotated with the decoding error, //! providing contextual information about the error. +extern crate alloc; + use miette::{IntoDiagnostic, SourceOffset}; use serde_json::{self, json}; diff --git a/miette-derive/src/severity.rs b/miette-derive/src/severity.rs index 40f1bd7..a6f664a 100644 --- a/miette-derive/src/severity.rs +++ b/miette-derive/src/severity.rs @@ -71,9 +71,7 @@ impl Severity { syn::Fields::Unnamed(_) => quote! { (..) }, syn::Fields::Unit => quote! {}, }; - Some( - quote! { Self::#ident #fields => Option::Some(miette::Severity::#severity), }, - ) + Some(quote! { Self::#ident #fields => Option::Some(miette::Severity::#severity), }) }, ) } diff --git a/src/chain.rs b/src/chain.rs index e21d82a..6880678 100644 --- a/src/chain.rs +++ b/src/chain.rs @@ -5,9 +5,6 @@ NOTE: This module is taken wholesale from . */ extern crate alloc; -#[cfg(feature = "std")] -use std::error::Error as StdError; -#[cfg(not(feature = "std"))] use crate::StdError; use alloc::vec::{self, Vec}; diff --git a/src/diagnostic_chain.rs b/src/diagnostic_chain.rs index e9dcccd..7fcfa20 100644 --- a/src/diagnostic_chain.rs +++ b/src/diagnostic_chain.rs @@ -2,10 +2,7 @@ Iterate over error `.diagnostic_source()` chains. */ -extern crate alloc; - use crate::protocol::Diagnostic; -use alloc::string::ToString; /// Iterator of a chain of cause errors. #[derive(Clone, Default)] diff --git a/src/diagnostic_impls.rs b/src/diagnostic_impls.rs index 4bb7345..07391a4 100644 --- a/src/diagnostic_impls.rs +++ b/src/diagnostic_impls.rs @@ -3,12 +3,10 @@ Default trait implementations for [`Diagnostic`]. */ extern crate alloc; -use core::{convert::Infallible, fmt::Display}; use alloc::boxed::Box; +use core::{convert::Infallible, fmt::Display}; -use crate::{Diagnostic, LabeledSpan, Severity, SourceCode, StdError}; - -impl StdError for Infallible {} +use crate::{Diagnostic, LabeledSpan, Severity, SourceCode}; impl Diagnostic for Infallible { fn code<'a>(&'a self) -> Option> { diff --git a/src/error.rs b/src/error.rs index 5fab5b8..9e2faad 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,13 +1,13 @@ extern crate alloc; -#[cfg(feature = "std")] -use std::io; -#[cfg(feature = "std")] -use std::error::Error; #[cfg(not(feature = "std"))] use crate::StdError as Error; -use core::fmt::{self, Display}; use alloc::boxed::Box; +use core::fmt::{self, Display}; +#[cfg(feature = "std")] +use std::error::Error; +#[cfg(feature = "std")] +use std::io; use crate::Diagnostic; @@ -83,17 +83,17 @@ impl Diagnostic for MietteError { }; Some(Box::new(alloc::format!( "https://docs.rs/miette/{}/miette/enum.MietteError.html{}", - crate_version, variant, + crate_version, + variant, ))) } } #[cfg(test)] pub(crate) mod tests { - #[cfg(feature = "std")] - use std::io::ErrorKind; - #[cfg(not(feature = "std"))] - use crate::StdError as Error; + #[cfg(not(feature = "std"))] + use crate::StdError as Error; + use std::string::ToString; use super::*; @@ -126,9 +126,9 @@ pub(crate) mod tests { #[cfg(feature = "std")] #[test] fn io_error() { - let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire"); + let inner_error = io::Error::other("halt and catch fire"); let outer_error = TestError(inner_error); - let io_error = io::Error::new(ErrorKind::Other, outer_error); + let io_error = io::Error::other(outer_error); let miette_error = MietteError::from(io_error); diff --git a/src/eyreish/context.rs b/src/eyreish/context.rs index 1dbb543..86db506 100644 --- a/src/eyreish/context.rs +++ b/src/eyreish/context.rs @@ -2,13 +2,10 @@ extern crate alloc; use super::error::{ContextError, ErrorImpl}; use super::{Report, WrapErr}; -use core::fmt::{self, Debug, Display, Write}; use core::convert::Infallible; +use core::fmt::{self, Debug, Display, Write}; -#[cfg(feature = "std")] -use std::error::Error as StdError; -#[cfg(not(feature = "std"))] -use crate::StdError as StdError; +use crate::StdError; use alloc::boxed::Box; use crate::{Diagnostic, LabeledSpan}; @@ -27,6 +24,7 @@ mod ext { where E: Diagnostic + Send + Sync + 'static, { + #[cfg_attr(track_caller, track_caller)] fn ext_report(self, msg: D) -> Report where D: Display + Send + Sync + 'static, @@ -36,6 +34,7 @@ mod ext { } impl Diag for Report { + #[cfg_attr(track_caller, track_caller)] fn ext_report(self, msg: D) -> Report where D: Display + Send + Sync + 'static, @@ -46,6 +45,7 @@ mod ext { } impl WrapErr for Option { + #[cfg_attr(track_caller, track_caller)] fn wrap_err(self, msg: D) -> Result where D: Display + Send + Sync + 'static, @@ -56,6 +56,7 @@ impl WrapErr for Option { } } + #[cfg_attr(track_caller, track_caller)] fn wrap_err_with(self, msg: F) -> Result where D: Display + Send + Sync + 'static, @@ -67,6 +68,7 @@ impl WrapErr for Option { } } + #[cfg_attr(track_caller, track_caller)] fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static, @@ -74,6 +76,7 @@ impl WrapErr for Option { self.wrap_err(msg) } + #[cfg_attr(track_caller, track_caller)] fn with_context(self, msg: F) -> Result where D: Display + Send + Sync + 'static, @@ -87,6 +90,7 @@ impl WrapErr for Result where E: ext::Diag + Send + Sync + 'static, { + #[cfg_attr(track_caller, track_caller)] fn wrap_err(self, msg: D) -> Result where D: Display + Send + Sync + 'static, @@ -97,6 +101,7 @@ where } } + #[cfg_attr(track_caller, track_caller)] fn wrap_err_with(self, msg: F) -> Result where D: Display + Send + Sync + 'static, @@ -108,6 +113,7 @@ where } } + #[cfg_attr(track_caller, track_caller)] fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static, @@ -115,6 +121,7 @@ where self.wrap_err(msg) } + #[cfg_attr(track_caller, track_caller)] fn with_context(self, msg: F) -> Result where D: Display + Send + Sync + 'static, diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs index 238e46c..0add102 100644 --- a/src/eyreish/error.rs +++ b/src/eyreish/error.rs @@ -1,14 +1,11 @@ extern crate alloc; +use crate::StdError; +use alloc::boxed::Box; use core::any::TypeId; use core::fmt::{self, Debug, Display}; use core::mem::ManuallyDrop; use core::ptr::{self, NonNull}; -#[cfg(feature = "std")] -use std::error::Error as StdError; -#[cfg(not(feature = "std"))] -use crate::StdError as StdError; -use alloc::boxed::Box; use super::ptr::{Mut, Own, Ref}; use super::Report; @@ -93,7 +90,6 @@ impl Report { Report::from_boxed(error) } - #[cfg_attr(track_caller, track_caller)] #[cold] pub(crate) fn from_std(error: E) -> Self where @@ -160,7 +156,7 @@ impl Report { }; // Safety: passing vtable that operates on the right type. - let handler = Some(super::capture_handler(&error)); + let handler = Some(super::capture_handler_with_location(&error)); unsafe { Report::construct(error, vtable, handler) } } @@ -445,7 +441,6 @@ impl From for Report where E: Diagnostic + Send + Sync + 'static, { - #[cfg_attr(track_caller, track_caller)] #[cold] fn from(error: E) -> Self { Report::from_std(error) diff --git a/src/eyreish/into_diagnostic.rs b/src/eyreish/into_diagnostic.rs index b739b24..2ed2aae 100644 --- a/src/eyreish/into_diagnostic.rs +++ b/src/eyreish/into_diagnostic.rs @@ -1,10 +1,6 @@ extern crate alloc; -#[cfg(feature = "std")] -use std::error::Error; -#[cfg(not(feature = "std"))] use crate::StdError as Error; -use core::fmt::Display; use alloc::boxed::Box; use crate::{Diagnostic, Report}; @@ -42,11 +38,13 @@ inaccessible. If you have a type implementing [`Diagnostic`] consider simply ret pub trait IntoDiagnostic { /// Converts [`Result`] types that return regular [`std::error::Error`]s /// into a [`Result`] that returns a [`Diagnostic`]. + #[cfg_attr(track_caller, track_caller)] fn into_diagnostic(self) -> Result; } #[cfg(feature = "std")] impl IntoDiagnostic for Result { + #[cfg_attr(track_caller, track_caller)] fn into_diagnostic(self) -> Result { self.map_err(|e| DiagnosticError(Box::new(e)).into()) } @@ -54,6 +52,7 @@ impl IntoDiagnostic for R #[cfg(not(feature = "std"))] impl IntoDiagnostic for Result { + #[cfg_attr(track_caller, track_caller)] fn into_diagnostic(self) -> Result { self.map_err(|e| DiagnosticError(Box::new(e)).into()) } @@ -62,7 +61,9 @@ impl IntoDiagnostic for Result #[cfg(test)] mod tests { #[cfg(feature = "std")] - use std::io::{self, ErrorKind}; + use std::io::{self}; + #[cfg(feature = "std")] + use std::string::ToString; use super::*; @@ -72,7 +73,7 @@ mod tests { #[cfg(feature = "std")] #[test] fn diagnostic_error() { - let inner_error = io::Error::new(ErrorKind::Other, "halt and catch fire"); + let inner_error = io::Error::other("halt and catch fire"); let outer_error: Result<(), _> = Err(TestError(inner_error)); let diagnostic_error = outer_error.into_diagnostic().unwrap_err(); diff --git a/src/eyreish/kind.rs b/src/eyreish/kind.rs index e6dd978..2c1f49a 100644 --- a/src/eyreish/kind.rs +++ b/src/eyreish/kind.rs @@ -56,6 +56,9 @@ use crate::Diagnostic; #[cfg(not(feature = "std"))] use alloc::boxed::Box; +#[cfg(feature = "std")] +use std::boxed::Box; + pub struct Adhoc; pub trait AdhocKind: Sized { diff --git a/src/eyreish/mod.rs b/src/eyreish/mod.rs index 8a4598c..bf0076d 100644 --- a/src/eyreish/mod.rs +++ b/src/eyreish/mod.rs @@ -6,14 +6,14 @@ )] extern crate alloc; -use core::fmt::Display; use alloc::boxed::Box; +use core::fmt::Display; +#[cfg(not(feature = "std"))] +use crate::StdError; +use spin::Once; #[cfg(feature = "std")] use std::error::Error as StdError; -#[cfg(not(feature = "std"))] -use crate::StdError as StdError; -use spin::Once; #[allow(unreachable_pub)] pub use into_diagnostic::*; @@ -95,9 +95,20 @@ pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { Ok(()) } +pub(crate) fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box { + static DEFAULT: Once = Once::new(); + let hook = HOOK.get().unwrap_or_else(|| { + DEFAULT.call_once(|| default_hook()); + DEFAULT.get().unwrap() + }); + + hook(error) +} + #[cfg_attr(track_caller, track_caller)] -#[cfg_attr(not(track_caller), allow(unused_mut))] -fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box { +pub(crate) fn capture_handler_with_location( + error: &(dyn Diagnostic + 'static), +) -> Box { static DEFAULT: Once = Once::new(); let hook = HOOK.get().unwrap_or_else(|| { DEFAULT.call_once(|| default_hook()); diff --git a/src/eyreish/ptr.rs b/src/eyreish/ptr.rs index 947b5bd..9852b69 100644 --- a/src/eyreish/ptr.rs +++ b/src/eyreish/ptr.rs @@ -1,8 +1,8 @@ extern crate alloc; +use alloc::boxed::Box; use core::marker::PhantomData; use core::ptr::NonNull; -use alloc::boxed::Box; #[repr(transparent)] /// A raw pointer that owns its pointee diff --git a/src/eyreish/wrapper.rs b/src/eyreish/wrapper.rs index 6c03cc0..73c6a2e 100644 --- a/src/eyreish/wrapper.rs +++ b/src/eyreish/wrapper.rs @@ -2,13 +2,8 @@ extern crate alloc; use core::fmt::{self, Debug, Display}; -#[cfg(feature = "std")] -use std::error::Error as StdError; -#[cfg(not(feature = "std"))] -use crate::StdError as StdError; +use crate::StdError; use alloc::boxed::Box; -use alloc::string::String; -use alloc::vec::Vec; use crate::{Diagnostic, LabeledSpan, Report, SourceCode}; @@ -216,6 +211,11 @@ impl StdError for WithSourceCode { #[cfg(test)] mod tests { + + use std::{ + boxed::Box, + string::{String, ToString}, + }; use thiserror::Error; use crate::{Diagnostic, LabeledSpan, Report, SourceCode, SourceSpan}; diff --git a/src/handler.rs b/src/handler.rs index 37e4826..1efec17 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,3 +1,7 @@ +extern crate alloc; +use alloc::boxed::Box; +use alloc::string::String; + use crate::highlighters::Highlighter; use crate::highlighters::MietteHighlighter; use crate::protocol::Diagnostic; @@ -491,7 +495,10 @@ mod syscall { #[inline] pub(super) fn supports_color() -> bool { cfg_if! { - if #[cfg(feature = "fancy-no-syscall")] { + if #[cfg(feature = "fancy-no-backtrace")] { + supports_color::on(supports_color::Stream::Stderr).is_some() + } else if #[cfg(feature = "fancy-no-syscall")] { + // In no-std environment without color support, default to no color support false } else { supports_color::on(supports_color::Stream::Stderr).is_some() @@ -502,8 +509,11 @@ mod syscall { #[inline] pub(super) fn supports_color_has_16m() -> Option { cfg_if! { - if #[cfg(feature = "fancy-no-syscall")] { - None + if #[cfg(feature = "fancy-no-backtrace")] { + supports_color::on(supports_color::Stream::Stderr).map(|color| color.has_16m) + } else if #[cfg(feature = "fancy-no-syscall")] { + // In no-std environment without color support, default to no RGB color support + Some(false) } else { supports_color::on(supports_color::Stream::Stderr).map(|color| color.has_16m) } diff --git a/src/handlers/debug.rs b/src/handlers/debug.rs index 1c77062..8e9ff3c 100644 --- a/src/handlers/debug.rs +++ b/src/handlers/debug.rs @@ -1,7 +1,7 @@ extern crate alloc; -use core::fmt; use alloc::vec::Vec; +use core::fmt; use crate::{protocol::Diagnostic, ReportHandler}; diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs index 37b6bf8..3b66aa4 100644 --- a/src/handlers/graphical.rs +++ b/src/handlers/graphical.rs @@ -1,4 +1,10 @@ -use std::fmt::{self, Write}; +extern crate alloc; + +use alloc::boxed::Box; +use alloc::format; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::fmt::{self, Write}; use owo_colors::{OwoColorize, Style, StyledList}; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; diff --git a/src/handlers/json.rs b/src/handlers/json.rs index 966e348..a5d5708 100644 --- a/src/handlers/json.rs +++ b/src/handlers/json.rs @@ -1,7 +1,7 @@ extern crate alloc; -use core::fmt::{self, Write}; use alloc::string::ToString; +use core::fmt::{self, Write}; use crate::{ diagnostic_chain::DiagnosticChain, protocol::Diagnostic, ReportHandler, Severity, SourceCode, diff --git a/src/handlers/narratable.rs b/src/handlers/narratable.rs index 86f23b3..418b9ef 100644 --- a/src/handlers/narratable.rs +++ b/src/handlers/narratable.rs @@ -7,9 +7,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 alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; -use alloc::boxed::Box; /** [`ReportHandler`] that renders plain text and avoids extraneous graphics. diff --git a/src/handlers/theme.rs b/src/handlers/theme.rs index 360b2da..64980d8 100644 --- a/src/handlers/theme.rs +++ b/src/handlers/theme.rs @@ -1,5 +1,7 @@ -use std::io::IsTerminal; - +extern crate alloc; +use alloc::string::String; +use alloc::vec; +use alloc::vec::Vec; use owo_colors::Style; /** diff --git a/src/highlighters/blank.rs b/src/highlighters/blank.rs index 50a9c65..f32f9df 100644 --- a/src/highlighters/blank.rs +++ b/src/highlighters/blank.rs @@ -1,3 +1,8 @@ +extern crate alloc; + +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; use owo_colors::Style; use crate::SpanContents; diff --git a/src/highlighters/mod.rs b/src/highlighters/mod.rs index 0af1aa2..5f95836 100644 --- a/src/highlighters/mod.rs +++ b/src/highlighters/mod.rs @@ -13,6 +13,11 @@ use std::{ops::Deref, sync::Arc}; +extern crate alloc; +use alloc::boxed::Box; +use alloc::sync::Arc; +use alloc::vec::Vec; + use crate::SpanContents; use owo_colors::Styled; diff --git a/src/highlighters/syntect.rs b/src/highlighters/syntect.rs index 75759fc..ca848f6 100644 --- a/src/highlighters/syntect.rs +++ b/src/highlighters/syntect.rs @@ -1,5 +1,10 @@ use std::path::Path; +extern crate alloc; +use alloc::boxed::Box; +use alloc::vec; +use alloc::vec::Vec; + // all syntect imports are explicitly qualified, but their paths are shortened for convenience #[allow(clippy::module_inception)] mod syntect { diff --git a/src/lib.rs b/src/lib.rs index 7c2fd56..24cefff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![no_std] - #![deny(missing_docs, missing_debug_implementations, nonstandard_style)] #![warn(unreachable_pub, rust_2018_idioms)] #![allow(unexpected_cfgs)] + //! You run miette? You run her code like the software? Oh. Oh! Error code for //! coder! Error code for One Thousand Lines! //! @@ -98,6 +98,7 @@ //! ## Example //! //! ```rust +//! # extern crate alloc; //! /* //! You can derive a `Diagnostic` from any `std::error::Error` type. //! @@ -195,6 +196,7 @@ //! the trait directly, just like with `std::error::Error`. //! //! ```rust +//! # extern crate alloc; //! // lib/error.rs //! use miette::{Diagnostic, SourceSpan}; //! use thiserror::Error; @@ -362,6 +364,7 @@ //! attribute: //! //! ```rust +//! # extern crate alloc; //! use miette::Diagnostic; //! use thiserror::Error; //! @@ -382,6 +385,7 @@ //! (very high quality and detailed!) documentation on this diagnostic: //! //! ```rust +//! # extern crate alloc; //! use miette::Diagnostic; //! use thiserror::Error; //! @@ -412,6 +416,7 @@ //! `derive(Diagnostic)` macro: //! //! ```rust +//! # extern crate alloc; //! use miette::{Diagnostic, SourceSpan}; //! use thiserror::Error; //! @@ -450,6 +455,7 @@ //! enum variants: //! //! ```rust +//! # extern crate alloc; //! use miette::Diagnostic; //! use thiserror::Error; //! @@ -463,6 +469,7 @@ //! your diagnostic: //! //! ```rust +//! # extern crate alloc; //! use miette::Diagnostic; //! use thiserror::Error; //! @@ -501,6 +508,7 @@ //! `Diagnostic` type: //! //! ```rust +//! # extern crate alloc; //! use miette::Diagnostic; //! use thiserror::Error; //! @@ -519,6 +527,7 @@ //! method for that: //! //! ```rust,no_run +//! # extern crate alloc; //! use miette::{Diagnostic, SourceSpan}; //! use thiserror::Error; //! @@ -551,6 +560,7 @@ //! emitted at the same time: //! //! ```rust,no_run +//! # extern crate alloc; //! use miette::{Diagnostic, Report, SourceSpan}; //! use thiserror::Error; //! @@ -612,6 +622,7 @@ //! will likely want to use _both_: //! //! ```rust +//! # extern crate alloc; //! use miette::Diagnostic; //! use thiserror::Error; //! @@ -817,6 +828,11 @@ //! and some from [`thiserror`](https://github.com/dtolnay/thiserror), also //! under the Apache License. Some code is taken from //! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed. + +// For doctests that use Diagnostic derive macro +#[cfg(test)] +extern crate alloc; + #[cfg(feature = "std")] extern crate std; diff --git a/src/macro_helpers.rs b/src/macro_helpers.rs index 47a6e5f..157f2b3 100644 --- a/src/macro_helpers.rs +++ b/src/macro_helpers.rs @@ -51,7 +51,6 @@ impl ToLabeledSpan for ToLabelSpanWrapper { span } } -#[cfg(not(feature = "std"))] impl ToLabeledSpan for ToLabelSpanWrapper where T: Into, diff --git a/src/miette_diagnostic.rs b/src/miette_diagnostic.rs index 07ed724..dc0fa28 100644 --- a/src/miette_diagnostic.rs +++ b/src/miette_diagnostic.rs @@ -1,14 +1,13 @@ extern crate alloc; +#[cfg(not(feature = "std"))] +use crate::StdError as Error; +use alloc::boxed::Box; +use alloc::string::String; +use alloc::vec::Vec; use core::fmt::{Debug, Display}; #[cfg(feature = "std")] use std::error::Error; -#[cfg(not(feature = "std"))] -use crate::StdError as Error; -use alloc::string::String; -use alloc::vec::{self, Vec}; -use alloc::format; -use alloc::boxed::Box; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/src/named_source.rs b/src/named_source.rs index 415f088..0f2804e 100644 --- a/src/named_source.rs +++ b/src/named_source.rs @@ -12,9 +12,9 @@ pub struct NamedSource { extern crate alloc; +use alloc::boxed::Box; use alloc::string::String; use alloc::string::ToString; -use alloc::boxed::Box; impl core::fmt::Debug for NamedSource { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { diff --git a/src/panic.rs b/src/panic.rs index b1dfd4a..ef6f427 100644 --- a/src/panic.rs +++ b/src/panic.rs @@ -1,4 +1,11 @@ -use std::{error::Error, fmt::Display}; +use std::boxed::Box; +use std::{ + eprintln, + error::Error, + fmt::Display, + format, + string::{String, ToString}, +}; use backtrace::Backtrace; @@ -104,7 +111,7 @@ impl Panic { #[cfg(test)] mod tests { - use std::error::Error; + use std::{borrow::ToOwned, error::Error}; use super::*; diff --git a/src/protocol.rs b/src/protocol.rs index 10bb283..636aefa 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -5,13 +5,13 @@ full reporting and such features. */ extern crate alloc; -use core::fmt::{self, Display}; -#[cfg(feature = "std")] -use std::fs; -use core::panic::Location; -use core::ops; use alloc::boxed::Box; use alloc::string::String; +use core::fmt::{self, Display}; +use core::ops; +use core::panic::Location; +#[cfg(feature = "std")] +use std::fs; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -152,9 +152,6 @@ impl From for Box { fn from(s: String) -> Self { struct StringError(String); - #[cfg(feature = "std")] - impl std::error::Error for StringError {} - #[cfg(not(feature = "std"))] impl crate::StdError for StringError {} impl Diagnostic for StringError {} diff --git a/src/source_impls.rs b/src/source_impls.rs index 4fa60c3..efa415c 100644 --- a/src/source_impls.rs +++ b/src/source_impls.rs @@ -3,14 +3,14 @@ Default trait implementations for [`SourceCode`]. */ extern crate alloc; -use core::fmt::Debug; use alloc::borrow::Cow; use alloc::borrow::ToOwned; -use alloc::collections::VecDeque; -use alloc::sync::Arc; -use alloc::string::String; -use alloc::vec::Vec; use alloc::boxed::Box; +use alloc::collections::VecDeque; +use alloc::string::String; +use alloc::sync::Arc; +use alloc::vec::Vec; +use core::fmt::Debug; use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan, SpanContents}; diff --git a/tests/color_format.rs b/tests/color_format.rs index 4e95856..f0f1ad5 100644 --- a/tests/color_format.rs +++ b/tests/color_format.rs @@ -1,5 +1,7 @@ #![cfg(feature = "fancy-no-backtrace")] +extern crate alloc; + use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler, RgbColors}; use regex::Regex; use std::ffi::OsString; @@ -83,7 +85,9 @@ fn check_colors MietteHandlerOpts>( // // Since environment variables are shared for the entire process, we need // to ensure that only one test that modifies these env vars runs at a time. - let lock = COLOR_ENV_VARS.lock().unwrap(); + let lock = COLOR_ENV_VARS + .lock() + .unwrap_or_else(|poisoned| poisoned.into_inner()); let guards = ( EnvVarGuard::new("NO_COLOR"), diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 223810c..8c48253 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -10,5 +10,5 @@ pub fn bail_fmt() -> Result<()> { } pub fn bail_error() -> Result<()> { - bail!(io::Error::new(io::ErrorKind::Other, "oh no!")); + bail!(io::Error::other("oh no!")); } diff --git a/tests/derive.rs b/tests/derive.rs index aa631dc..3cc0e8b 100644 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use miette::{Diagnostic, Report, Severity, SourceSpan}; use thiserror::Error; diff --git a/tests/graphical.rs b/tests/graphical.rs index 93c9bce..e2548e1 100644 --- a/tests/graphical.rs +++ b/tests/graphical.rs @@ -1,5 +1,7 @@ #![cfg(feature = "fancy-no-backtrace")] +extern crate alloc; + use miette::{ Diagnostic, GraphicalReportHandler, GraphicalTheme, MietteError, NamedSource, NarratableReportHandler, Report, SourceSpan, diff --git a/tests/narrated.rs b/tests/narrated.rs index 52acd13..ced0b23 100644 --- a/tests/narrated.rs +++ b/tests/narrated.rs @@ -1,5 +1,7 @@ #![cfg(feature = "fancy-no-backtrace")] +extern crate alloc; + use miette::{Diagnostic, MietteError, NamedSource, NarratableReportHandler, Report, SourceSpan}; use miette::{GraphicalReportHandler, GraphicalTheme}; diff --git a/tests/test_boxed.rs b/tests/test_boxed.rs index 9f9f4e2..db8d214 100644 --- a/tests/test_boxed.rs +++ b/tests/test_boxed.rs @@ -42,13 +42,13 @@ fn test_boxed_str_stderr() { #[test] fn test_boxed_thiserror() { let error = MyError { - source: io::Error::new(io::ErrorKind::Other, "oh no!"), + source: io::Error::other("oh no!"), }; let report: Report = miette!(error); assert_eq!("oh no!", report.source().unwrap().to_string()); let error = MyError { - source: io::Error::new(io::ErrorKind::Other, "oh no!!!!"), + source: io::Error::other("oh no!!!!"), }; let error: Box = Box::new(error); let report = Report::new_boxed(error); @@ -203,7 +203,7 @@ fn test_boxed_custom_diagnostic() { let related = CustomDiagnostic::new(); let main_diagnostic = CustomDiagnostic::new() - .with_source(io::Error::new(io::ErrorKind::Other, "oh no!")) + .with_source(io::Error::other("oh no!")) .with_related(related); let report = Report::new_boxed(Box::new(main_diagnostic)); @@ -211,7 +211,7 @@ fn test_boxed_custom_diagnostic() { let related = CustomDiagnostic::new(); let main_diagnostic = CustomDiagnostic::new() - .with_source(io::Error::new(io::ErrorKind::Other, "oh no!")) + .with_source(io::Error::other("oh no!")) .with_related(related); let main_diagnostic = Box::new(main_diagnostic) as Box; let report = miette!(main_diagnostic); @@ -228,7 +228,7 @@ fn test_boxed_custom_diagnostic() { #[test] fn test_boxed_sources() { let error = MyError { - source: io::Error::new(io::ErrorKind::Other, "oh no!"), + source: io::Error::other("oh no!"), }; let error = Box::::from(error); let error: Report = miette!(error).wrap_err("it failed"); diff --git a/tests/test_derive_attr.rs b/tests/test_derive_attr.rs index f1b0f3d..8c13670 100644 --- a/tests/test_derive_attr.rs +++ b/tests/test_derive_attr.rs @@ -1,4 +1,6 @@ // Testing of the `diagnostic` attr used by derive(Diagnostic) +extern crate alloc; + use miette::{Diagnostic, LabeledSpan, NamedSource, SourceSpan}; use thiserror::Error; diff --git a/tests/test_derive_collection.rs b/tests/test_derive_collection.rs index 952b505..baf037c 100644 --- a/tests/test_derive_collection.rs +++ b/tests/test_derive_collection.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use std::{ collections::{LinkedList, VecDeque}, ops::Range, diff --git a/tests/test_diagnostic_source_macro.rs b/tests/test_diagnostic_source_macro.rs index 1349303..33aebb6 100644 --- a/tests/test_diagnostic_source_macro.rs +++ b/tests/test_diagnostic_source_macro.rs @@ -1,3 +1,5 @@ +extern crate alloc; + use miette::Diagnostic; #[derive(Debug, miette::Diagnostic, thiserror::Error)] diff --git a/tests/test_json.rs b/tests/test_json.rs index 664318a..b055fc7 100644 --- a/tests/test_json.rs +++ b/tests/test_json.rs @@ -1,4 +1,6 @@ mod json_report_handler { + extern crate alloc; + use miette::{Diagnostic, MietteError, NamedSource, Report, SourceSpan}; use miette::JSONReportHandler; diff --git a/tests/test_source.rs b/tests/test_source.rs index 9511bdb..303c2f9 100644 --- a/tests/test_source.rs +++ b/tests/test_source.rs @@ -50,7 +50,7 @@ fn test_fmt_source() { #[test] #[ignore = "Again with the io::Error source issue?"] fn test_io_source() { - let io = io::Error::new(io::ErrorKind::Other, "oh no!"); + let io = io::Error::other("oh no!"); let error: Report = miette!(TestError::Io(io)); assert_eq!("oh no!", error.source().unwrap().to_string()); } From 18c217f0dc00be07603f88f861a92df07f26d00c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Mon, 20 Oct 2025 04:46:36 -0400 Subject: [PATCH 03/10] Add CI job for no-std build verification and fix Infallible StdError implementation - Add dedicated no-std CI job that builds for wasm32-unknown-unknown target - Fix Infallible StdError implementation for no-std environments The CI job validates: 1. Core no-std functionality (no default features) 2. fancy-no-syscall feature set compatible with no-std environments --- .github/workflows/ci.yml | 15 +++++++++++++++ src/diagnostic_impls.rs | 3 +++ src/eyreish/wrapper.rs | 7 +++++-- src/handler.rs | 22 +++++++++++++++++----- src/handlers/graphical.rs | 6 +++--- src/handlers/theme.rs | 18 +++++++++++++----- src/highlighters/mod.rs | 37 ++++++++++++++++++++++--------------- src/protocol.rs | 6 +++++- 8 files changed, 83 insertions(+), 31 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d10924..6250716 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -68,6 +68,21 @@ jobs: - name: Check wasm target run: cargo check --target wasm32-unknown-unknown --features fancy-no-syscall + no-std: + name: Check no-std build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + targets: wasm32-unknown-unknown + - name: Check no-std core build + run: cargo check --target wasm32-unknown-unknown --no-default-features + - name: Check no-std with fancy-no-syscall + run: cargo check --target wasm32-unknown-unknown --no-default-features --features fancy-no-syscall + miri: name: Miri runs-on: ubuntu-latest diff --git a/src/diagnostic_impls.rs b/src/diagnostic_impls.rs index 07391a4..5a9a65d 100644 --- a/src/diagnostic_impls.rs +++ b/src/diagnostic_impls.rs @@ -8,6 +8,9 @@ use core::{convert::Infallible, fmt::Display}; use crate::{Diagnostic, LabeledSpan, Severity, SourceCode}; +#[cfg(not(feature = "std"))] +impl crate::StdError for Infallible {} + impl Diagnostic for Infallible { fn code<'a>(&'a self) -> Option> { match *self {} diff --git a/src/eyreish/wrapper.rs b/src/eyreish/wrapper.rs index 73c6a2e..0b0ac73 100644 --- a/src/eyreish/wrapper.rs +++ b/src/eyreish/wrapper.rs @@ -209,13 +209,16 @@ impl StdError for WithSourceCode { } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { - + #[cfg(feature = "fancy")] + use std::format; use std::{ boxed::Box, string::{String, ToString}, }; + #[cfg(feature = "fancy")] + use std::{vec, vec::Vec}; use thiserror::Error; use crate::{Diagnostic, LabeledSpan, Report, SourceCode, SourceSpan}; diff --git a/src/handler.rs b/src/handler.rs index 1efec17..6dc2df1 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -345,15 +345,27 @@ impl MietteHandlerOpts { } } + #[allow(clippy::manual_unwrap_or)] pub(crate) fn is_graphical(&self) -> bool { if let Some(force_narrated) = self.force_narrated { !force_narrated } else if let Some(force_graphical) = self.force_graphical { force_graphical - } else if let Ok(env) = std::env::var("NO_GRAPHICS") { - env == "0" } else { - true + #[cfg(feature = "fancy-no-syscall")] + { + // In no-std environment, assume graphics are available + true + } + #[cfg(not(feature = "fancy-no-syscall"))] + { + // In std environment, check NO_GRAPHICS env var + if let Ok(env) = std::env::var("NO_GRAPHICS") { + env == "0" + } else { + true + } + } } } @@ -501,7 +513,7 @@ mod syscall { // In no-std environment without color support, default to no color support false } else { - supports_color::on(supports_color::Stream::Stderr).is_some() + true // Fallback to assuming color support } } } @@ -515,7 +527,7 @@ mod syscall { // In no-std environment without color support, default to no RGB color support Some(false) } else { - supports_color::on(supports_color::Stream::Stderr).map(|color| color.has_16m) + Some(true) // Fallback to assuming RGB support } } } diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs index 3b66aa4..74fb261 100644 --- a/src/handlers/graphical.rs +++ b/src/handlers/graphical.rs @@ -588,7 +588,7 @@ impl GraphicalReportHandler { // The snippets will overlap, so we create one Big Chunky Boi let left_end = left.offset() + left.len(); let right_end = right.offset() + right.len(); - let new_end = std::cmp::max(left_end, right_end); + let new_end = core::cmp::max(left_end, right_end); let new_span = LabeledSpan::new( left.label().map(String::from), @@ -655,7 +655,7 @@ impl GraphicalReportHandler { num_highlights += 1; } } - max_gutter = std::cmp::max(max_gutter, num_highlights); + max_gutter = core::cmp::max(max_gutter, num_highlights); } // Oh and one more thing: We need to figure out how much room our line @@ -1179,7 +1179,7 @@ impl GraphicalReportHandler { .style(hl.style) .to_string(), ); - highest = std::cmp::max(highest, end); + highest = core::cmp::max(highest, end); (hl, vbar_offset) }) diff --git a/src/handlers/theme.rs b/src/handlers/theme.rs index 64980d8..168ff89 100644 --- a/src/handlers/theme.rs +++ b/src/handlers/theme.rs @@ -71,12 +71,20 @@ impl GraphicalTheme { impl Default for GraphicalTheme { fn default() -> Self { - match std::env::var("NO_COLOR") { - _ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => { - Self::none() + #[cfg(feature = "fancy-no-syscall")] + { + // In no-std environments, default to no-color mode + Self::unicode_nocolor() + } + #[cfg(not(feature = "fancy-no-syscall"))] + { + match std::env::var("NO_COLOR") { + _ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => { + Self::none() + } + Ok(string) if string != "0" => Self::unicode_nocolor(), + _ => Self::unicode(), } - Ok(string) if string != "0" => Self::unicode_nocolor(), - _ => Self::unicode(), } } } diff --git a/src/highlighters/mod.rs b/src/highlighters/mod.rs index 5f95836..398ad36 100644 --- a/src/highlighters/mod.rs +++ b/src/highlighters/mod.rs @@ -11,7 +11,7 @@ //! * `syntect-highlighter` - Enables [`syntect`](https://docs.rs/syntect/latest/syntect/) syntax highlighting support via the [`SyntectHighlighter`] //! -use std::{ops::Deref, sync::Arc}; +use core::ops::Deref; extern crate alloc; use alloc::boxed::Box; @@ -83,21 +83,28 @@ impl MietteHighlighter { } impl Default for MietteHighlighter { - #[cfg(feature = "syntect-highlighter")] fn default() -> Self { - use std::io::IsTerminal; - match std::env::var("NO_COLOR") { - _ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => { - //TODO: should use ANSI styling instead of 24-bit truecolor here - Self(Arc::new(SyntectHighlighter::default())) + #[cfg(all(feature = "syntect-highlighter", not(feature = "fancy-no-syscall")))] + { + use std::io::IsTerminal; + match std::env::var("NO_COLOR") { + _ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => { + //TODO: should use ANSI styling instead of 24-bit truecolor here + Self(Arc::new(SyntectHighlighter::default())) + } + Ok(string) if string != "0" => MietteHighlighter::nocolor(), + _ => Self(Arc::new(SyntectHighlighter::default())), } - Ok(string) if string != "0" => MietteHighlighter::nocolor(), - _ => Self(Arc::new(SyntectHighlighter::default())), } - } - #[cfg(not(feature = "syntect-highlighter"))] - fn default() -> Self { - MietteHighlighter::nocolor() + #[cfg(all(feature = "syntect-highlighter", feature = "fancy-no-syscall"))] + { + // In no-std environment, use syntect but without terminal detection + Self(Arc::new(SyntectHighlighter::default())) + } + #[cfg(not(feature = "syntect-highlighter"))] + { + MietteHighlighter::nocolor() + } } } @@ -107,8 +114,8 @@ impl From for MietteHighlighter { } } -impl std::fmt::Debug for MietteHighlighter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for MietteHighlighter { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "MietteHighlighter(...)") } } diff --git a/src/protocol.rs b/src/protocol.rs index 636aefa..ef6f7e0 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -9,6 +9,7 @@ use alloc::boxed::Box; use alloc::string::String; use core::fmt::{self, Display}; use core::ops; +#[cfg(feature = "std")] use core::panic::Location; #[cfg(feature = "std")] use std::fs; @@ -16,7 +17,7 @@ use std::fs; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use crate::{DiagnosticError, MietteError}; +use crate::MietteError; /// Adds rich metadata to your Error that can be used by /// [`Report`](crate::Report) to print really nice and human-friendly error @@ -172,6 +173,9 @@ impl From for Box { } } +#[cfg(feature = "std")] +use crate::DiagnosticError; + #[cfg(feature = "std")] impl From> for Box { fn from(s: Box) -> Self { From 359941fdef6a178a309c6e8c30e01e9a1d36c1b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Sat, 15 Nov 2025 16:09:22 -0500 Subject: [PATCH 04/10] fix: address review comments - Fixed compilation errors in no-std mode (TestError Debug derive, ToString imports) - Addressed consistency issues: Option qualification, StdError naming, Borrow impl - Fixed feature flag logic for supports_color function with proper priority ordering - Added conditional compilation for std-dependent features in panic.rs, handler.rs, theme.rs - Made syntect-highlighter feature explicitly require std - Consolidated duplicate Borrow implementations using core::borrow::Borrow --- Cargo.toml | 2 +- README.md | 17 ++++++++++++-- build.rs | 8 ++----- miette-derive/src/code.rs | 10 ++++---- miette-derive/src/diagnostic_source.rs | 6 ++--- src/error.rs | 4 ++++ src/eyreish/error.rs | 8 ------- src/eyreish/into_diagnostic.rs | 3 +-- src/handler.rs | 32 +++++++++++++++++++------- src/handlers/theme.rs | 7 +++++- src/highlighters/syntect.rs | 1 + src/lib.rs | 21 ++++++++++++----- src/panic.rs | 22 +++++++++++++++++- 13 files changed, 98 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bc3ead4..e7f6f07 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,7 @@ fancy-no-backtrace = [ "dep:supports-unicode", ] fancy = ["fancy-no-backtrace", "dep:backtrace", "dep:backtrace-ext"] -syntect-highlighter = ["fancy-no-backtrace", "dep:syntect"] +syntect-highlighter = ["fancy-no-backtrace", "dep:syntect", "std"] [workspace] members = ["miette-derive"] diff --git a/README.md b/README.md index 8ecc16d..084ce0d 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,8 @@ diagnostic error code: ruget::api::bad_json ### Features -- Generic [`Diagnostic`] protocol, compatible (and dependent on) - [`std::error::Error`]. +- Generic [`Diagnostic`] protocol, compatible with `std::error::Error`. + Works without the standard library: Just turn off the default `std` feature and you can use `miette` in places like embedded systems or web browsers that don't have the full standard library. You still need `alloc` for memory management. - Unique error codes on every [`Diagnostic`]. - Custom links to get more details on error codes. - Super handy derive macro for defining diagnostic metadata. @@ -93,6 +93,19 @@ If you want to use the fancy printer in all these screenshots: $ cargo add miette --features fancy ``` +For computers without the standard library (like microcontrollers or web browsers): + +```sh +$ cargo add miette --no-default-features --features derive +``` + +Available features you can turn on or off: +- `std` (on by default): Use the standard library +- `derive`: Lets you automatically create error types +- `fancy`: Shows pretty error messages with colors +- `fancy-no-syscall`: Pretty errors without using system calls +- `fancy-no-backtrace`: Pretty errors without showing the call stack + ### Example ```rust diff --git a/build.rs b/build.rs index 20e6ce0..296a4eb 100644 --- a/build.rs +++ b/build.rs @@ -5,12 +5,8 @@ fn main() { println!("cargo:rustc-cfg=nightly") } - // Configure track_caller based on Rust version - if let Ok(version) = rustc_version::version() { - if version >= rustc_version::Version::new(1, 46, 0) { - println!("cargo:rustc-cfg=track_caller"); - } - } + // track_caller is stable since Rust 1.46 (2020), so no version check needed + println!("cargo:rustc-cfg=track_caller"); // Add check-cfg for conditional configurations println!("cargo:rustc-check-cfg=cfg(doc_cfg)"); diff --git a/miette-derive/src/code.rs b/miette-derive/src/code.rs index 8ebe920..83cb053 100644 --- a/miette-derive/src/code.rs +++ b/miette-derive/src/code.rs @@ -56,13 +56,13 @@ impl Code { let code = &code.as_ref()?.0; Some(match fields { syn::Fields::Named(_) => { - quote! { Self::#ident { .. } => Option::Some(alloc::boxed::Box::new(#code)), } + quote! { Self::#ident { .. } => core::option::Option::Some(alloc::boxed::Box::new(#code)), } } syn::Fields::Unnamed(_) => { - quote! { Self::#ident(..) => Option::Some(alloc::boxed::Box::new(#code)), } + quote! { Self::#ident(..) => core::option::Option::Some(alloc::boxed::Box::new(#code)), } } syn::Fields::Unit => { - quote! { Self::#ident => Option::Some(alloc::boxed::Box::new(#code)), } + quote! { Self::#ident => core::option::Option::Some(alloc::boxed::Box::new(#code)), } } }) }, @@ -72,8 +72,8 @@ impl Code { pub(crate) fn gen_struct(&self) -> Option { let code = &self.0; Some(quote! { - fn code(&self) -> Option> { - Some(alloc::boxed::Box::new(#code)) + fn code(&self) -> core::option::Option> { + core::option::Option::Some(alloc::boxed::Box::new(#code)) } }) } diff --git a/miette-derive/src/diagnostic_source.rs b/miette-derive/src/diagnostic_source.rs index 7864e4a..35b5e35 100644 --- a/miette-derive/src/diagnostic_source.rs +++ b/miette-derive/src/diagnostic_source.rs @@ -59,7 +59,7 @@ impl DiagnosticSource { }; quote! { Self::#ident #display_pat => { - Some(alloc::borrow::Borrow::borrow(#rel)) + core::option::Option::Some(alloc::borrow::Borrow::borrow(#rel)) } } }) @@ -70,8 +70,8 @@ impl DiagnosticSource { pub(crate) fn gen_struct(&self) -> Option { let rel = &self.0; Some(quote! { - fn diagnostic_source<'a>(&'a self) -> Option<&'a dyn miette::Diagnostic> { - Some(alloc::borrow::Borrow::borrow(&self.#rel)) + fn diagnostic_source<'a>(&'a self) -> core::option::Option<&'a dyn miette::Diagnostic> { + core::option::Option::Some(alloc::borrow::Borrow::borrow(&self.#rel)) } }) } diff --git a/src/error.rs b/src/error.rs index 9e2faad..f54f4d8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -93,6 +93,9 @@ impl Diagnostic for MietteError { pub(crate) mod tests { #[cfg(not(feature = "std"))] use crate::StdError as Error; + #[cfg(not(feature = "std"))] + use alloc::string::ToString; + #[cfg(feature = "std")] use std::string::ToString; use super::*; @@ -101,6 +104,7 @@ pub(crate) mod tests { #[cfg(feature = "std")] pub(crate) struct TestError(pub(crate) io::Error); #[cfg(not(feature = "std"))] + #[derive(Debug)] pub(crate) struct TestError(pub(crate) &'static str); impl Display for TestError { diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs index 0add102..2dc0a3c 100644 --- a/src/eyreish/error.rs +++ b/src/eyreish/error.rs @@ -817,14 +817,6 @@ impl AsRef for Report { } } -#[cfg(feature = "std")] -impl std::borrow::Borrow for Report { - fn borrow(&self) -> &(dyn Diagnostic + 'static) { - self.as_ref() - } -} - -#[cfg(not(feature = "std"))] impl core::borrow::Borrow for Report { fn borrow(&self) -> &(dyn Diagnostic + 'static) { self.as_ref() diff --git a/src/eyreish/into_diagnostic.rs b/src/eyreish/into_diagnostic.rs index 2ed2aae..83262a5 100644 --- a/src/eyreish/into_diagnostic.rs +++ b/src/eyreish/into_diagnostic.rs @@ -60,13 +60,12 @@ impl IntoDiagnostic for Result #[cfg(test)] mod tests { + use super::IntoDiagnostic; #[cfg(feature = "std")] use std::io::{self}; #[cfg(feature = "std")] use std::string::ToString; - use super::*; - #[cfg(feature = "std")] use crate::error::tests::TestError; diff --git a/src/handler.rs b/src/handler.rs index 6dc2df1..32a2e5e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -357,7 +357,7 @@ impl MietteHandlerOpts { // In no-std environment, assume graphics are available true } - #[cfg(not(feature = "fancy-no-syscall"))] + #[cfg(all(not(feature = "fancy-no-syscall"), feature = "std"))] { // In std environment, check NO_GRAPHICS env var if let Ok(env) = std::env::var("NO_GRAPHICS") { @@ -366,6 +366,11 @@ impl MietteHandlerOpts { true } } + #[cfg(all(not(feature = "fancy-no-syscall"), not(feature = "std")))] + { + // In no-std environment without fancy-no-syscall, default to true + true + } } } @@ -487,8 +492,10 @@ mod syscall { cfg_if! { if #[cfg(any(feature = "fancy-no-syscall", miri))] { None - } else { + } else if #[cfg(feature = "fancy-no-backtrace")] { terminal_size::terminal_size().map(|size| size.0 .0 as usize) + } else { + None } } } @@ -498,8 +505,10 @@ mod syscall { cfg_if! { if #[cfg(feature = "fancy-no-syscall")] { false - } else { + } else if #[cfg(feature = "fancy-no-backtrace")] { supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr) + } else { + false } } } @@ -507,13 +516,18 @@ mod syscall { #[inline] pub(super) fn supports_color() -> bool { cfg_if! { - if #[cfg(feature = "fancy-no-backtrace")] { + if #[cfg(all(feature = "fancy", not(feature = "fancy-no-syscall")))] { + // Standard fancy mode with full std support supports_color::on(supports_color::Stream::Stderr).is_some() - } else if #[cfg(feature = "fancy-no-syscall")] { - // In no-std environment without color support, default to no color support + } else if #[cfg(all(feature = "fancy", feature = "fancy-no-backtrace"))] { + // Fancy mode without backtrace but with color support + supports_color::on(supports_color::Stream::Stderr).is_some() + } else if #[cfg(not(feature = "std"))] { + // No-std environment - no color support by default false } else { - true // Fallback to assuming color support + // All other cases - no color support + false } } } @@ -537,8 +551,10 @@ mod syscall { cfg_if! { if #[cfg(feature = "fancy-no-syscall")] { false - } else { + } else if #[cfg(feature = "fancy-no-backtrace")] { supports_unicode::on(supports_unicode::Stream::Stderr) + } else { + false } } } diff --git a/src/handlers/theme.rs b/src/handlers/theme.rs index 168ff89..f51b42f 100644 --- a/src/handlers/theme.rs +++ b/src/handlers/theme.rs @@ -76,7 +76,7 @@ impl Default for GraphicalTheme { // In no-std environments, default to no-color mode Self::unicode_nocolor() } - #[cfg(not(feature = "fancy-no-syscall"))] + #[cfg(all(not(feature = "fancy-no-syscall"), feature = "std"))] { match std::env::var("NO_COLOR") { _ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => { @@ -86,6 +86,11 @@ impl Default for GraphicalTheme { _ => Self::unicode(), } } + #[cfg(all(not(feature = "fancy-no-syscall"), not(feature = "std")))] + { + // In no-std environment, default to unicode theme + Self::unicode() + } } } diff --git a/src/highlighters/syntect.rs b/src/highlighters/syntect.rs index ca848f6..d5df64e 100644 --- a/src/highlighters/syntect.rs +++ b/src/highlighters/syntect.rs @@ -1,3 +1,4 @@ +use core::str; use std::path::Path; extern crate alloc; diff --git a/src/lib.rs b/src/lib.rs index 24cefff..79ed0fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,8 +60,8 @@ //! //! ## Features //! -//! - Generic [`Diagnostic`] protocol, compatible (and dependent on) -//! [`std::error::Error`]. +//! - Generic [`Diagnostic`] protocol, compatible with `std::error::Error`. +//! Works without the standard library: Just turn off the default `std` feature and you can use `miette` in places like embedded systems or web browsers that don't have the full standard library. You still need `alloc` for memory management. //! - Unique error codes on every [`Diagnostic`]. //! - Custom links to get more details on error codes. //! - Super handy derive macro for defining diagnostic metadata. @@ -95,6 +95,19 @@ //! $ cargo add miette --features fancy //! ``` //! +//! For computers without the standard library (like microcontrollers or web browsers): +//! +//! ```sh +//! $ cargo add miette --no-default-features --features derive +//! ``` +//! +//! Available features you can turn on or off: +//! - `std` (on by default): Use the standard library +//! - `derive`: Lets you automatically create error types +//! - `fancy`: Shows pretty error messages with colors +//! - `fancy-no-syscall`: Pretty errors without using system calls +//! - `fancy-no-backtrace`: Pretty errors without showing the call stack +//! //! ## Example //! //! ```rust @@ -829,10 +842,6 @@ //! under the Apache License. Some code is taken from //! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed. -// For doctests that use Diagnostic derive macro -#[cfg(test)] -extern crate alloc; - #[cfg(feature = "std")] extern crate std; diff --git a/src/panic.rs b/src/panic.rs index ef6f427..eb57601 100644 --- a/src/panic.rs +++ b/src/panic.rs @@ -1,4 +1,6 @@ +#[cfg(feature = "std")] use std::boxed::Box; +#[cfg(feature = "std")] use std::{ eprintln, error::Error, @@ -7,11 +9,14 @@ use std::{ string::{String, ToString}, }; +#[cfg(feature = "std")] use backtrace::Backtrace; +#[cfg(feature = "std")] use crate::{Context, Diagnostic, Result}; -/// Tells miette to render panics using its rendering engine. +/// Makes miette show pretty error messages when your program crashes. +#[cfg(feature = "std")] pub fn set_panic_hook() { std::panic::set_hook(Box::new(move |info| { let mut message = "Something went wrong".to_string(); @@ -33,9 +38,20 @@ pub fn set_panic_hook() { })); } +/// Makes miette show pretty error messages when your program crashes. +/// +/// On computers without the standard library, this function does nothing +/// because crash hooks need the standard library to work. +#[cfg(not(feature = "std"))] +pub fn set_panic_hook() { + // Does nothing on computers without the standard library +} + #[derive(Debug)] +#[cfg(feature = "std")] struct Panic(String); +#[cfg(feature = "std")] impl Display for Panic { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let msg = &self.0; @@ -44,8 +60,10 @@ impl Display for Panic { } } +#[cfg(feature = "std")] impl Error for Panic {} +#[cfg(feature = "std")] impl Diagnostic for Panic { fn help<'a>(&'a self) -> Option> { Some(Box::new( @@ -54,6 +72,7 @@ impl Diagnostic for Panic { } } +#[cfg(feature = "std")] impl Panic { fn backtrace() -> String { use std::fmt::Write; @@ -110,6 +129,7 @@ impl Panic { } #[cfg(test)] +#[cfg(feature = "std")] mod tests { use std::{borrow::ToOwned, error::Error}; From 9af0988e5212aac8967951a6a4313e32968fb96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Sun, 16 Nov 2025 09:08:10 -0500 Subject: [PATCH 05/10] fix: suppress unused assignment warnings in derive tests for Miri - Add #[allow(unused_assignments)] to test structs/enums with unused fields - Fix Miri test failures caused by derive macro feature interactions - Miri tests now pass: 52 passed, 0 failed, 6 ignored - Issue occurs when fancy feature changes how derive macros process fields - Suppression is appropriate as fields are only unused under specific feature combinations --- tests/derive.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/derive.rs b/tests/derive.rs index 3cc0e8b..087d184 100644 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -1,3 +1,4 @@ +#[allow(unused_assignments)] // some fields unused when feature="fancy" extern crate alloc; use miette::{Diagnostic, Report, Severity, SourceSpan}; @@ -246,6 +247,7 @@ fn help_field() { #[diagnostic()] struct Foo<'a> { #[help] + #[allow(unused_assignments)] do_this: Option<&'a str>, } @@ -295,6 +297,7 @@ fn test_snippet_named_struct() { #[error("welp")] #[diagnostic(code(foo::bar::baz))] #[allow(dead_code)] + #[allow(unused_assignments)] struct Foo<'a> { #[source_code] src: &'a str, @@ -403,6 +406,7 @@ const SNIPPET_TEXT: &str = "hello from miette"; help("help"), severity(Warning) )] +#[allow(unused_assignments)] struct ForwardsTo { #[source_code] src: String, @@ -503,6 +507,7 @@ fn test_forward_struct_named() { help("{help}"), forward(span) )] + #[allow(unused_assignments)] struct Struct<'a> { span: ForwardsTo, help: &'a str, @@ -536,6 +541,7 @@ fn test_forward_enum_named() { enum Enum<'a> { #[error("help: {help_text}")] #[diagnostic(code(foo::bar::overridden), help("{help_text}"), forward(span))] + #[allow(unused_assignments)] Variant { span: ForwardsTo, help_text: &'a str, @@ -597,6 +603,7 @@ fn test_unit_enum_display() { fn test_optional_source_code() { #[derive(Debug, Diagnostic, Error)] #[error("struct with optional source")] + #[allow(unused_assignments)] struct Struct { #[source_code] src: Option, @@ -609,6 +616,7 @@ fn test_optional_source_code() { .is_some()); #[derive(Debug, Diagnostic, Error)] + #[allow(unused_assignments)] enum Enum { #[error("variant1 with optional source")] Variant1 { From 9bdf23a659ee6a03f64b46f3d3acdfab8e4cdbef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Wed, 19 Nov 2025 09:12:01 -0500 Subject: [PATCH 06/10] fix: remove unused rustc_version dependency and simplify build.rs - Remove rustc_version build dependency that was no longer needed - Remove nightly detection since nightly cfg flag wasn't used anywhere - Simplify build.rs to only set track_caller cfg flag --- Cargo.toml | 2 -- build.rs | 7 ------- 2 files changed, 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e7f6f07..e830164 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,8 +42,6 @@ serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.113" strip-ansi-escapes = "0.2.0" -[build-dependencies] -rustc_version = "0.2" [features] default = ["derive", "std"] diff --git a/build.rs b/build.rs index 296a4eb..23e7655 100644 --- a/build.rs +++ b/build.rs @@ -1,15 +1,8 @@ -use rustc_version::{version_meta, Channel}; - fn main() { - if let Channel::Nightly = version_meta().unwrap().channel { - println!("cargo:rustc-cfg=nightly") - } - // track_caller is stable since Rust 1.46 (2020), so no version check needed println!("cargo:rustc-cfg=track_caller"); // Add check-cfg for conditional configurations println!("cargo:rustc-check-cfg=cfg(doc_cfg)"); println!("cargo:rustc-check-cfg=cfg(track_caller)"); - println!("cargo:rustc-check-cfg=cfg(nightly)"); } From 09e1090b2ed3605ea337dfc26466bbb0f2bd311b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Wed, 19 Nov 2025 19:17:50 -0500 Subject: [PATCH 07/10] refactor: use #[track_caller] directly instead of conditional cfg_attr --- build.rs | 4 +--- src/eyreish/context.rs | 22 +++++++++++----------- src/eyreish/error.rs | 12 ++++++------ src/eyreish/into_diagnostic.rs | 6 +++--- src/eyreish/kind.rs | 6 +++--- src/eyreish/mod.rs | 17 ++++++----------- 6 files changed, 30 insertions(+), 37 deletions(-) diff --git a/build.rs b/build.rs index 23e7655..fb54b74 100644 --- a/build.rs +++ b/build.rs @@ -1,8 +1,6 @@ fn main() { - // track_caller is stable since Rust 1.46 (2020), so no version check needed - println!("cargo:rustc-cfg=track_caller"); + // track_caller is always available with our MSRV (Rust ≥ 1.82) // Add check-cfg for conditional configurations println!("cargo:rustc-check-cfg=cfg(doc_cfg)"); - println!("cargo:rustc-check-cfg=cfg(track_caller)"); } diff --git a/src/eyreish/context.rs b/src/eyreish/context.rs index 86db506..c92d651 100644 --- a/src/eyreish/context.rs +++ b/src/eyreish/context.rs @@ -14,7 +14,7 @@ mod ext { use super::*; pub trait Diag { - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn ext_report(self, msg: D) -> Report where D: Display + Send + Sync + 'static; @@ -24,7 +24,7 @@ mod ext { where E: Diagnostic + Send + Sync + 'static, { - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn ext_report(self, msg: D) -> Report where D: Display + Send + Sync + 'static, @@ -34,7 +34,7 @@ mod ext { } impl Diag for Report { - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn ext_report(self, msg: D) -> Report where D: Display + Send + Sync + 'static, @@ -45,7 +45,7 @@ mod ext { } impl WrapErr for Option { - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn wrap_err(self, msg: D) -> Result where D: Display + Send + Sync + 'static, @@ -56,7 +56,7 @@ impl WrapErr for Option { } } - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn wrap_err_with(self, msg: F) -> Result where D: Display + Send + Sync + 'static, @@ -68,7 +68,7 @@ impl WrapErr for Option { } } - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static, @@ -76,7 +76,7 @@ impl WrapErr for Option { self.wrap_err(msg) } - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn with_context(self, msg: F) -> Result where D: Display + Send + Sync + 'static, @@ -90,7 +90,7 @@ impl WrapErr for Result where E: ext::Diag + Send + Sync + 'static, { - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn wrap_err(self, msg: D) -> Result where D: Display + Send + Sync + 'static, @@ -101,7 +101,7 @@ where } } - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn wrap_err_with(self, msg: F) -> Result where D: Display + Send + Sync + 'static, @@ -113,7 +113,7 @@ where } } - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static, @@ -121,7 +121,7 @@ where self.wrap_err(msg) } - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn with_context(self, msg: F) -> Result where D: Display + Send + Sync + 'static, diff --git a/src/eyreish/error.rs b/src/eyreish/error.rs index 2dc0a3c..ac84975 100644 --- a/src/eyreish/error.rs +++ b/src/eyreish/error.rs @@ -23,7 +23,7 @@ impl Report { /// /// If the error type does not provide a backtrace, a backtrace will be /// created here to ensure that a backtrace exists. - #[cfg_attr(track_caller, track_caller)] + #[track_caller] #[cold] pub fn new(error: E) -> Self where @@ -69,7 +69,7 @@ impl Report { /// .await /// } /// ``` - #[cfg_attr(track_caller, track_caller)] + #[track_caller] #[cold] pub fn msg(message: M) -> Self where @@ -85,7 +85,7 @@ impl Report { /// /// Boxed `Diagnostic`s don't implement `Diagnostic` themselves due to trait coherence issues. /// This method allows you to create a `Report` from a boxed `Diagnostic`. - #[cfg_attr(track_caller, track_caller)] + #[track_caller] pub fn new_boxed(error: Box) -> Self { Report::from_boxed(error) } @@ -111,7 +111,7 @@ impl Report { unsafe { Report::construct(error, vtable, handler) } } - #[cfg_attr(track_caller, track_caller)] + #[track_caller] #[cold] pub(crate) fn from_adhoc(message: M) -> Self where @@ -136,7 +136,7 @@ impl Report { unsafe { Report::construct(error, vtable, handler) } } - #[cfg_attr(track_caller, track_caller)] + #[track_caller] #[cold] pub(crate) fn from_msg(msg: D, error: E) -> Self where @@ -161,7 +161,7 @@ impl Report { unsafe { Report::construct(error, vtable, handler) } } - #[cfg_attr(track_caller, track_caller)] + #[track_caller] #[cold] pub(crate) fn from_boxed(error: Box) -> Self { use super::wrapper::BoxedError; diff --git a/src/eyreish/into_diagnostic.rs b/src/eyreish/into_diagnostic.rs index 83262a5..c9264ec 100644 --- a/src/eyreish/into_diagnostic.rs +++ b/src/eyreish/into_diagnostic.rs @@ -38,13 +38,13 @@ inaccessible. If you have a type implementing [`Diagnostic`] consider simply ret pub trait IntoDiagnostic { /// Converts [`Result`] types that return regular [`std::error::Error`]s /// into a [`Result`] that returns a [`Diagnostic`]. - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn into_diagnostic(self) -> Result; } #[cfg(feature = "std")] impl IntoDiagnostic for Result { - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn into_diagnostic(self) -> Result { self.map_err(|e| DiagnosticError(Box::new(e)).into()) } @@ -52,7 +52,7 @@ impl IntoDiagnostic for R #[cfg(not(feature = "std"))] impl IntoDiagnostic for Result { - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn into_diagnostic(self) -> Result { self.map_err(|e| DiagnosticError(Box::new(e)).into()) } diff --git a/src/eyreish/kind.rs b/src/eyreish/kind.rs index 2c1f49a..d3d1df1 100644 --- a/src/eyreish/kind.rs +++ b/src/eyreish/kind.rs @@ -71,7 +71,7 @@ pub trait AdhocKind: Sized { impl AdhocKind for &T where T: ?Sized + Display + Debug + Send + Sync + 'static {} impl Adhoc { - #[cfg_attr(track_caller, track_caller)] + #[track_caller] #[cold] pub fn new(self, message: M) -> Report where @@ -93,7 +93,7 @@ pub trait TraitKind: Sized { impl TraitKind for E where E: Into {} impl Trait { - #[cfg_attr(track_caller, track_caller)] + #[track_caller] #[cold] pub fn new(self, error: E) -> Report where @@ -115,7 +115,7 @@ pub trait BoxedKind: Sized { impl BoxedKind for Box {} impl Boxed { - #[cfg_attr(track_caller, track_caller)] + #[track_caller] #[cold] pub fn new(self, error: Box) -> Report { Report::from_boxed(error) diff --git a/src/eyreish/mod.rs b/src/eyreish/mod.rs index bf0076d..586fc61 100644 --- a/src/eyreish/mod.rs +++ b/src/eyreish/mod.rs @@ -105,7 +105,7 @@ pub(crate) fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box Box { @@ -115,16 +115,11 @@ pub(crate) fn capture_handler_with_location( DEFAULT.get().unwrap() }); - #[cfg(track_caller)] { let mut handler = hook(error); handler.track_caller(core::panic::Location::caller()); handler } - #[cfg(not(track_caller))] - { - hook(error) - } } fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box { @@ -454,27 +449,27 @@ pub type Result = core::result::Result; /// ``` pub trait WrapErr: context::private::Sealed { /// Wrap the error value with a new adhoc error - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn wrap_err(self, msg: D) -> Result where D: Display + Send + Sync + 'static; /// Wrap the error value with a new adhoc error that is evaluated lazily /// only once an error does occur. - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn wrap_err_with(self, f: F) -> Result where D: Display + Send + Sync + 'static, F: FnOnce() -> D; /// Compatibility re-export of `wrap_err()` for interop with `anyhow` - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static; /// Compatibility re-export of `wrap_err_with()` for interop with `anyhow` - #[cfg_attr(track_caller, track_caller)] + #[track_caller] fn with_context(self, f: F) -> Result where D: Display + Send + Sync + 'static, @@ -496,7 +491,7 @@ pub mod private { pub use super::super::kind::BoxedKind; } - #[cfg_attr(track_caller, track_caller)] + #[track_caller] #[cold] pub fn new_adhoc(message: M) -> Report where From c07cab4b60143d8e038c68aa0c9801ec71aad21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Wed, 14 Jan 2026 15:38:54 -0500 Subject: [PATCH 08/10] fix: address review feedback for no_std support - Use core::error::Error directly (MSRV 1.82 supports it) - Remove build.rs (track_caller is always available with MSRV 1.82) - Simplify feature detection in handler.rs syscall module - Add clippy lints to catch std/core/alloc misuse - Remove custom StdError trait, re-export core::error::Error - Add __alloc module for derive macro compatibility - Clean up verbose comments - Remove extern crate alloc from tests (not needed with std) - Fix derive macros to use miette::__alloc paths --- Cargo.toml | 10 +-- build.rs | 6 -- miette-derive/src/code.rs | 10 +-- miette-derive/src/diagnostic_source.rs | 4 +- miette-derive/src/forward.rs | 10 +-- miette-derive/src/help.rs | 12 ++-- miette-derive/src/label.rs | 6 +- miette-derive/src/related.rs | 8 +-- miette-derive/src/url.rs | 6 +- src/diagnostic_impls.rs | 4 +- src/error.rs | 8 +-- src/eyreish/into_diagnostic.rs | 19 ++---- src/eyreish/kind.rs | 10 +-- src/eyreish/mod.rs | 60 +++++++++------- src/eyreish/wrapper.rs | 14 ++-- src/handler.rs | 95 ++++++++++---------------- src/handlers/theme.rs | 13 ++-- src/highlighters/mod.rs | 8 +-- src/highlighters/syntect.rs | 2 +- src/lib.rs | 22 +++--- src/miette_diagnostic.rs | 7 +- src/panic.rs | 27 ++------ src/protocol.rs | 6 +- tests/color_format.rs | 2 - tests/derive.rs | 3 +- tests/graphical.rs | 2 - tests/narrated.rs | 2 - tests/test_derive_attr.rs | 1 - tests/test_derive_collection.rs | 2 - tests/test_diagnostic_source_macro.rs | 2 - tests/test_json.rs | 2 - 31 files changed, 158 insertions(+), 225 deletions(-) delete mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index e830164..5b2dece 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,11 +13,11 @@ rust-version = "1.82.0" exclude = ["images/", "tests/", "miette-derive/"] [dependencies] -thiserror = "2.0.11" miette-derive = { path = "miette-derive", version = "=7.6.0", optional = true } unicode-width = { version = "0.2.0", default-features = false } cfg-if = "1.0.0" -spin = { version = "0.9", default-features = false, features = ["mutex", "spin_mutex", "lazy"] } +# spin is needed for no_std environments (provides Once without std) +spin = { version = "0.9", default-features = false, features = ["once"] } owo-colors = { version = "4.0.0", optional = true } textwrap = { version = "0.16.0", default-features = false, features = ["unicode-linebreak", "unicode-width"], optional = true } @@ -31,6 +31,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" futures = { version = "0.3", default-features = false } indenter = "0.3.3" @@ -45,7 +46,7 @@ strip-ansi-escapes = "0.2.0" [features] default = ["derive", "std"] -std = ["thiserror/std", "fancy-no-syscall"] +std = [] derive = ["dep:miette-derive"] no-format-args-capture = [] fancy-base = [ @@ -57,13 +58,14 @@ fancy-no-syscall = [ ] fancy-no-backtrace = [ "fancy-base", + "std", "dep:terminal_size", "dep:supports-hyperlinks", "dep:supports-color", "dep:supports-unicode", ] fancy = ["fancy-no-backtrace", "dep:backtrace", "dep:backtrace-ext"] -syntect-highlighter = ["fancy-no-backtrace", "dep:syntect", "std"] +syntect-highlighter = ["fancy-no-backtrace", "dep:syntect"] [workspace] members = ["miette-derive"] diff --git a/build.rs b/build.rs deleted file mode 100644 index fb54b74..0000000 --- a/build.rs +++ /dev/null @@ -1,6 +0,0 @@ -fn main() { - // track_caller is always available with our MSRV (Rust ≥ 1.82) - - // Add check-cfg for conditional configurations - println!("cargo:rustc-check-cfg=cfg(doc_cfg)"); -} diff --git a/miette-derive/src/code.rs b/miette-derive/src/code.rs index 83cb053..04b655e 100644 --- a/miette-derive/src/code.rs +++ b/miette-derive/src/code.rs @@ -56,13 +56,13 @@ impl Code { let code = &code.as_ref()?.0; Some(match fields { syn::Fields::Named(_) => { - quote! { Self::#ident { .. } => core::option::Option::Some(alloc::boxed::Box::new(#code)), } + quote! { Self::#ident { .. } => core::option::Option::Some(miette::__alloc::Box::new(#code)), } } syn::Fields::Unnamed(_) => { - quote! { Self::#ident(..) => core::option::Option::Some(alloc::boxed::Box::new(#code)), } + quote! { Self::#ident(..) => core::option::Option::Some(miette::__alloc::Box::new(#code)), } } syn::Fields::Unit => { - quote! { Self::#ident => core::option::Option::Some(alloc::boxed::Box::new(#code)), } + quote! { Self::#ident => core::option::Option::Some(miette::__alloc::Box::new(#code)), } } }) }, @@ -72,8 +72,8 @@ impl Code { pub(crate) fn gen_struct(&self) -> Option { let code = &self.0; Some(quote! { - fn code(&self) -> core::option::Option> { - core::option::Option::Some(alloc::boxed::Box::new(#code)) + fn code(&self) -> core::option::Option> { + core::option::Option::Some(miette::__alloc::Box::new(#code)) } }) } diff --git a/miette-derive/src/diagnostic_source.rs b/miette-derive/src/diagnostic_source.rs index 35b5e35..fff97b1 100644 --- a/miette-derive/src/diagnostic_source.rs +++ b/miette-derive/src/diagnostic_source.rs @@ -59,7 +59,7 @@ impl DiagnosticSource { }; quote! { Self::#ident #display_pat => { - core::option::Option::Some(alloc::borrow::Borrow::borrow(#rel)) + core::option::Option::Some(miette::__alloc::borrow::Borrow::borrow(#rel)) } } }) @@ -71,7 +71,7 @@ impl DiagnosticSource { let rel = &self.0; Some(quote! { fn diagnostic_source<'a>(&'a self) -> core::option::Option<&'a dyn miette::Diagnostic> { - core::option::Option::Some(alloc::borrow::Borrow::borrow(&self.#rel)) + core::option::Option::Some(miette::__alloc::borrow::Borrow::borrow(&self.#rel)) } }) } diff --git a/miette-derive/src/forward.rs b/miette-derive/src/forward.rs index cdc658f..278d6ca 100644 --- a/miette-derive/src/forward.rs +++ b/miette-derive/src/forward.rs @@ -58,22 +58,22 @@ impl WhichFn { pub fn signature(&self) -> TokenStream { match self { Self::Code => quote! { - fn code(& self) -> Option> + fn code(& self) -> Option> }, Self::Help => quote! { - fn help(& self) -> Option> + fn help(& self) -> Option> }, Self::Url => quote! { - fn url(& self) -> Option> + fn url(& self) -> Option> }, Self::Severity => quote! { fn severity(&self) -> Option }, Self::Related => quote! { - fn related(&self) -> Option + '_>> + fn related(&self) -> Option + '_>> }, Self::Labels => quote! { - fn labels(&self) -> Option + '_>> + fn labels(&self) -> Option + '_>> }, Self::SourceCode => quote! { fn source_code(&self) -> Option<&dyn miette::SourceCode> diff --git a/miette-derive/src/help.rs b/miette-derive/src/help.rs index 0169027..685537c 100644 --- a/miette-derive/src/help.rs +++ b/miette-derive/src/help.rs @@ -94,7 +94,7 @@ impl Help { Help::Display(display) => { let (fmt, args) = display.expand_shorthand_cloned(&display_members); Some(quote! { - Self::#ident #display_pat => Option::Some(alloc::boxed::Box::new(format!(#fmt #args))), + Self::#ident #display_pat => Option::Some(miette::__alloc::Box::new(format!(#fmt #args))), }) } Help::Field(member, ty) => { @@ -108,7 +108,7 @@ impl Help { Some(quote! { Self::#ident #display_pat => { use miette::macro_helpers::ToOption; - miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&#help).as_ref().map(|#var| -> alloc::boxed::Box { alloc::boxed::Box::new(format!("{}", #var)) }) + miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&#help).as_ref().map(|#var| -> miette::__alloc::Box { miette::__alloc::Box::new(format!("{}", #var)) }) }, }) } @@ -123,21 +123,21 @@ impl Help { Help::Display(display) => { let (fmt, args) = display.expand_shorthand_cloned(&display_members); Some(quote! { - fn help(&self) -> Option> { + fn help(&self) -> Option> { #[allow(unused_variables, deprecated)] let Self #display_pat = self; - Option::Some(alloc::boxed::Box::new(format!(#fmt #args))) + Option::Some(miette::__alloc::Box::new(format!(#fmt #args))) } }) } Help::Field(member, ty) => { let var = quote! { __miette_internal_var }; Some(quote! { - fn help(&self) -> Option> { + fn help(&self) -> Option> { #[allow(unused_variables, deprecated)] let Self #display_pat = self; use miette::macro_helpers::ToOption; - miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#member).as_ref().map(|#var| -> alloc::boxed::Box { alloc::boxed::Box::new(format!("{}", #var)) }) + miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#member).as_ref().map(|#var| -> miette::__alloc::Box { miette::__alloc::Box::new(format!("{}", #var)) }) } }) } diff --git a/miette-derive/src/label.rs b/miette-derive/src/label.rs index 28db920..ff3738a 100644 --- a/miette-derive/src/label.rs +++ b/miette-derive/src/label.rs @@ -226,7 +226,7 @@ impl Labels { Some(quote! { #[allow(unused_variables)] - fn labels(&self) -> Option + '_>> { + fn labels(&self) -> Option + '_>> { use miette::macro_helpers::ToOption; let Self #display_pat = self; @@ -236,7 +236,7 @@ impl Labels { .into_iter() #(#collections_chain)*; - Option::Some(alloc::boxed::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap))) + Option::Some(miette::__alloc::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap))) } }) } @@ -322,7 +322,7 @@ impl Labels { ] .into_iter() #(#collections_chain)*; - Option::Some(alloc::boxed::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap))) + Option::Some(miette::__alloc::Box::new(labels_iter.filter(Option::is_some).map(Option::unwrap))) } }), } diff --git a/miette-derive/src/related.rs b/miette-derive/src/related.rs index 8158ea6..6c0a276 100644 --- a/miette-derive/src/related.rs +++ b/miette-derive/src/related.rs @@ -55,7 +55,7 @@ impl Related { }; quote! { Self::#ident #display_pat => { - Option::Some(alloc::boxed::Box::new( + Option::Some(miette::__alloc::Box::new( #rel.iter().map(|x| -> &(dyn miette::Diagnostic) { &*x }) )) } @@ -68,9 +68,9 @@ impl Related { pub(crate) fn gen_struct(&self) -> Option { let rel = &self.0; Some(quote! { - fn related<'a>(&'a self) -> Option + 'a>> { - use alloc::borrow::Borrow; - Option::Some(alloc::boxed::Box::new( + fn related<'a>(&'a self) -> Option + 'a>> { + use miette::__alloc::borrow::Borrow; + Option::Some(miette::__alloc::Box::new( self.#rel.iter().map(|x| -> &(dyn miette::Diagnostic) { &*x.borrow() }) )) } diff --git a/miette-derive/src/url.rs b/miette-derive/src/url.rs index 2583a1c..156315b 100644 --- a/miette-derive/src/url.rs +++ b/miette-derive/src/url.rs @@ -96,7 +96,7 @@ impl Url { } }; Some(quote! { - Self::#ident #pat => Option::Some(alloc::boxed::Box::new(format!(#fmt #args))), + Self::#ident #pat => Option::Some(miette::__alloc::Box::new(format!(#fmt #args))), }) }, ) @@ -129,10 +129,10 @@ impl Url { } }; Some(quote! { - fn url(&self) -> Option> { + fn url(&self) -> Option> { #[allow(unused_variables, deprecated)] let Self #pat = self; - Option::Some(alloc::boxed::Box::new(format!(#fmt #args))) + Option::Some(miette::__alloc::Box::new(format!(#fmt #args))) } }) } diff --git a/src/diagnostic_impls.rs b/src/diagnostic_impls.rs index 5a9a65d..fc635d4 100644 --- a/src/diagnostic_impls.rs +++ b/src/diagnostic_impls.rs @@ -8,8 +8,8 @@ use core::{convert::Infallible, fmt::Display}; use crate::{Diagnostic, LabeledSpan, Severity, SourceCode}; -#[cfg(not(feature = "std"))] -impl crate::StdError for Infallible {} +// Note: Infallible implements core::error::Error since Rust 1.81, +// so we don't need to provide our own StdError impl. impl Diagnostic for Infallible { fn code<'a>(&'a self) -> Option> { diff --git a/src/error.rs b/src/error.rs index f54f4d8..1f34865 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,9 @@ extern crate alloc; -#[cfg(not(feature = "std"))] -use crate::StdError as Error; use alloc::boxed::Box; +use core::error::Error; use core::fmt::{self, Display}; #[cfg(feature = "std")] -use std::error::Error; -#[cfg(feature = "std")] use std::io; use crate::Diagnostic; @@ -93,10 +90,7 @@ impl Diagnostic for MietteError { pub(crate) mod tests { #[cfg(not(feature = "std"))] use crate::StdError as Error; - #[cfg(not(feature = "std"))] use alloc::string::ToString; - #[cfg(feature = "std")] - use std::string::ToString; use super::*; diff --git a/src/eyreish/into_diagnostic.rs b/src/eyreish/into_diagnostic.rs index c9264ec..758b0e1 100644 --- a/src/eyreish/into_diagnostic.rs +++ b/src/eyreish/into_diagnostic.rs @@ -1,7 +1,7 @@ extern crate alloc; -use crate::StdError as Error; use alloc::boxed::Box; +use core::error::Error; use crate::{Diagnostic, Report}; @@ -42,15 +42,6 @@ pub trait IntoDiagnostic { fn into_diagnostic(self) -> Result; } -#[cfg(feature = "std")] -impl IntoDiagnostic for Result { - #[track_caller] - fn into_diagnostic(self) -> Result { - self.map_err(|e| DiagnosticError(Box::new(e)).into()) - } -} - -#[cfg(not(feature = "std"))] impl IntoDiagnostic for Result { #[track_caller] fn into_diagnostic(self) -> Result { @@ -60,11 +51,13 @@ impl IntoDiagnostic for Result #[cfg(test)] mod tests { + extern crate alloc; + + use alloc::string::ToString; + use super::IntoDiagnostic; #[cfg(feature = "std")] - use std::io::{self}; - #[cfg(feature = "std")] - use std::string::ToString; + use std::io; #[cfg(feature = "std")] use crate::error::tests::TestError; diff --git a/src/eyreish/kind.rs b/src/eyreish/kind.rs index d3d1df1..7f78df8 100644 --- a/src/eyreish/kind.rs +++ b/src/eyreish/kind.rs @@ -45,20 +45,14 @@ // let error = $msg; // (&error).miette_kind().new(error) -#[cfg(not(feature = "std"))] extern crate alloc; -use super::Report; +use alloc::boxed::Box; use core::fmt::{Debug, Display}; +use super::Report; use crate::Diagnostic; -#[cfg(not(feature = "std"))] -use alloc::boxed::Box; - -#[cfg(feature = "std")] -use std::boxed::Box; - pub struct Adhoc; pub trait AdhocKind: Sized { diff --git a/src/eyreish/mod.rs b/src/eyreish/mod.rs index 586fc61..3667b73 100644 --- a/src/eyreish/mod.rs +++ b/src/eyreish/mod.rs @@ -9,11 +9,9 @@ extern crate alloc; use alloc::boxed::Box; use core::fmt::Display; -#[cfg(not(feature = "std"))] -use crate::StdError; -use spin::Once; +use core::error::Error as StdError; #[cfg(feature = "std")] -use std::error::Error as StdError; +use std::sync::OnceLock; #[allow(unreachable_pub)] pub use into_diagnostic::*; @@ -67,7 +65,11 @@ unsafe impl Send for Report {} pub type ErrorHook = Box Box + Sync + Send + 'static>; -static HOOK: Once = Once::new(); +#[cfg(feature = "std")] +static HOOK: OnceLock = OnceLock::new(); + +#[cfg(not(feature = "std"))] +static HOOK: spin::Once = spin::Once::new(); fn default_hook() -> ErrorHook { Box::new(get_default_printer) @@ -87,39 +89,51 @@ impl core::fmt::Display for InstallError { impl StdError for InstallError {} impl Diagnostic for InstallError {} -/** -Set the error hook. -*/ +/// Set the error hook. +#[cfg(feature = "std")] +pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { + HOOK.set(hook).map_err(|_| InstallError) +} + +/// Set the error hook. +#[cfg(not(feature = "std"))] pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { HOOK.call_once(|| hook); Ok(()) } +#[cfg(feature = "std")] pub(crate) fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box { - static DEFAULT: Once = Once::new(); - let hook = HOOK.get().unwrap_or_else(|| { - DEFAULT.call_once(|| default_hook()); - DEFAULT.get().unwrap() - }); + let hook = HOOK.get_or_init(default_hook); + hook(error) +} +#[cfg(not(feature = "std"))] +pub(crate) fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box { + let hook = HOOK.call_once(default_hook); hook(error) } #[track_caller] +#[cfg(feature = "std")] pub(crate) fn capture_handler_with_location( error: &(dyn Diagnostic + 'static), ) -> Box { - static DEFAULT: Once = Once::new(); - let hook = HOOK.get().unwrap_or_else(|| { - DEFAULT.call_once(|| default_hook()); - DEFAULT.get().unwrap() - }); + let hook = HOOK.get_or_init(default_hook); + let mut handler = hook(error); + handler.track_caller(core::panic::Location::caller()); + handler +} - { - let mut handler = hook(error); - handler.track_caller(core::panic::Location::caller()); - handler - } +#[track_caller] +#[cfg(not(feature = "std"))] +pub(crate) fn capture_handler_with_location( + error: &(dyn Diagnostic + 'static), +) -> Box { + let hook = HOOK.call_once(default_hook); + let mut handler = hook(error); + handler.track_caller(core::panic::Location::caller()); + handler } fn get_default_printer(_err: &(dyn Diagnostic + 'static)) -> Box { diff --git a/src/eyreish/wrapper.rs b/src/eyreish/wrapper.rs index 0b0ac73..fd8af1b 100644 --- a/src/eyreish/wrapper.rs +++ b/src/eyreish/wrapper.rs @@ -211,14 +211,14 @@ impl StdError for WithSourceCode { #[cfg(all(test, feature = "std"))] mod tests { + extern crate alloc; + + use alloc::boxed::Box; #[cfg(feature = "fancy")] - use std::format; - use std::{ - boxed::Box, - string::{String, ToString}, - }; + use alloc::format; + use alloc::string::{String, ToString}; #[cfg(feature = "fancy")] - use std::{vec, vec::Vec}; + use alloc::{vec, vec::Vec}; use thiserror::Error; use crate::{Diagnostic, LabeledSpan, Report, SourceCode, SourceSpan}; @@ -232,7 +232,7 @@ mod tests { impl Diagnostic for Inner { fn labels(&self) -> Option + '_>> { - Some(Box::new(std::iter::once(LabeledSpan::underline(self.at)))) + Some(Box::new(core::iter::once(LabeledSpan::underline(self.at)))) } fn source_code(&self) -> Option<&dyn SourceCode> { diff --git a/src/handler.rs b/src/handler.rs index 32a2e5e..5ceaa78 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -352,25 +352,18 @@ impl MietteHandlerOpts { } else if let Some(force_graphical) = self.force_graphical { force_graphical } else { - #[cfg(feature = "fancy-no-syscall")] + #[cfg(all(feature = "fancy-no-syscall", not(feature = "fancy-no-backtrace")))] { - // In no-std environment, assume graphics are available true } - #[cfg(all(not(feature = "fancy-no-syscall"), feature = "std"))] + #[cfg(feature = "fancy-no-backtrace")] { - // In std environment, check NO_GRAPHICS env var if let Ok(env) = std::env::var("NO_GRAPHICS") { env == "0" } else { true } } - #[cfg(all(not(feature = "fancy-no-syscall"), not(feature = "std")))] - { - // In no-std environment without fancy-no-syscall, default to true - true - } } } @@ -485,77 +478,63 @@ impl From for MietteHighlighter { } mod syscall { - use cfg_if::cfg_if; - #[inline] pub(super) fn terminal_width() -> Option { - cfg_if! { - if #[cfg(any(feature = "fancy-no-syscall", miri))] { - None - } else if #[cfg(feature = "fancy-no-backtrace")] { - terminal_size::terminal_size().map(|size| size.0 .0 as usize) - } else { - None - } + #[cfg(all(feature = "fancy-no-backtrace", not(miri)))] + { + terminal_size::terminal_size().map(|size| size.0 .0 as usize) + } + #[cfg(any(not(feature = "fancy-no-backtrace"), miri))] + { + None } } #[inline] pub(super) fn supports_hyperlinks() -> bool { - cfg_if! { - if #[cfg(feature = "fancy-no-syscall")] { - false - } else if #[cfg(feature = "fancy-no-backtrace")] { - supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr) - } else { - false - } + #[cfg(feature = "fancy-no-backtrace")] + { + supports_hyperlinks::on(supports_hyperlinks::Stream::Stderr) + } + #[cfg(not(feature = "fancy-no-backtrace"))] + { + false } } #[inline] pub(super) fn supports_color() -> bool { - cfg_if! { - if #[cfg(all(feature = "fancy", not(feature = "fancy-no-syscall")))] { - // Standard fancy mode with full std support - supports_color::on(supports_color::Stream::Stderr).is_some() - } else if #[cfg(all(feature = "fancy", feature = "fancy-no-backtrace"))] { - // Fancy mode without backtrace but with color support - supports_color::on(supports_color::Stream::Stderr).is_some() - } else if #[cfg(not(feature = "std"))] { - // No-std environment - no color support by default - false - } else { - // All other cases - no color support - false - } + #[cfg(feature = "fancy-no-backtrace")] + { + supports_color::on(supports_color::Stream::Stderr).is_some() + } + #[cfg(not(feature = "fancy-no-backtrace"))] + { + false } } #[inline] pub(super) fn supports_color_has_16m() -> Option { - cfg_if! { - if #[cfg(feature = "fancy-no-backtrace")] { - supports_color::on(supports_color::Stream::Stderr).map(|color| color.has_16m) - } else if #[cfg(feature = "fancy-no-syscall")] { - // In no-std environment without color support, default to no RGB color support - Some(false) - } else { - Some(true) // Fallback to assuming RGB support - } + #[cfg(feature = "fancy-no-backtrace")] + { + supports_color::on(supports_color::Stream::Stderr).map(|color| color.has_16m) + } + #[cfg(not(feature = "fancy-no-backtrace"))] + { + None } } #[inline] pub(super) fn supports_unicode() -> bool { - cfg_if! { - if #[cfg(feature = "fancy-no-syscall")] { - false - } else if #[cfg(feature = "fancy-no-backtrace")] { - supports_unicode::on(supports_unicode::Stream::Stderr) - } else { - false - } + #[cfg(feature = "fancy-no-backtrace")] + { + supports_unicode::on(supports_unicode::Stream::Stderr) + } + #[cfg(not(feature = "fancy-no-backtrace"))] + { + false } } } diff --git a/src/handlers/theme.rs b/src/handlers/theme.rs index f51b42f..4f61c3b 100644 --- a/src/handlers/theme.rs +++ b/src/handlers/theme.rs @@ -71,13 +71,9 @@ impl GraphicalTheme { impl Default for GraphicalTheme { fn default() -> Self { - #[cfg(feature = "fancy-no-syscall")] - { - // In no-std environments, default to no-color mode - Self::unicode_nocolor() - } - #[cfg(all(not(feature = "fancy-no-syscall"), feature = "std"))] + #[cfg(feature = "fancy-no-backtrace")] { + use std::io::IsTerminal; match std::env::var("NO_COLOR") { _ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => { Self::none() @@ -86,10 +82,9 @@ impl Default for GraphicalTheme { _ => Self::unicode(), } } - #[cfg(all(not(feature = "fancy-no-syscall"), not(feature = "std")))] + #[cfg(not(feature = "fancy-no-backtrace"))] { - // In no-std environment, default to unicode theme - Self::unicode() + Self::unicode_nocolor() } } } diff --git a/src/highlighters/mod.rs b/src/highlighters/mod.rs index 398ad36..437aac9 100644 --- a/src/highlighters/mod.rs +++ b/src/highlighters/mod.rs @@ -84,23 +84,17 @@ impl MietteHighlighter { impl Default for MietteHighlighter { fn default() -> Self { - #[cfg(all(feature = "syntect-highlighter", not(feature = "fancy-no-syscall")))] + #[cfg(feature = "syntect-highlighter")] { use std::io::IsTerminal; match std::env::var("NO_COLOR") { _ if !std::io::stdout().is_terminal() || !std::io::stderr().is_terminal() => { - //TODO: should use ANSI styling instead of 24-bit truecolor here Self(Arc::new(SyntectHighlighter::default())) } Ok(string) if string != "0" => MietteHighlighter::nocolor(), _ => Self(Arc::new(SyntectHighlighter::default())), } } - #[cfg(all(feature = "syntect-highlighter", feature = "fancy-no-syscall"))] - { - // In no-std environment, use syntect but without terminal detection - Self(Arc::new(SyntectHighlighter::default())) - } #[cfg(not(feature = "syntect-highlighter"))] { MietteHighlighter::nocolor() diff --git a/src/highlighters/syntect.rs b/src/highlighters/syntect.rs index d5df64e..6d31eff 100644 --- a/src/highlighters/syntect.rs +++ b/src/highlighters/syntect.rs @@ -103,7 +103,7 @@ impl SyntectHighlighter { } // finally, attempt to guess syntax based on first line self.syntax_set.find_syntax_by_first_line( - std::str::from_utf8(contents.data()) + core::str::from_utf8(contents.data()) .ok()? .split('\n') .next()?, diff --git a/src/lib.rs b/src/lib.rs index 79ed0fc..dfed8c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,11 @@ #![no_std] #![deny(missing_docs, missing_debug_implementations, nonstandard_style)] #![warn(unreachable_pub, rust_2018_idioms)] +#![warn( + clippy::alloc_instead_of_core, + clippy::std_instead_of_core, + clippy::std_instead_of_alloc +)] #![allow(unexpected_cfgs)] //! You run miette? You run her code like the software? Oh. Oh! Error code for @@ -845,18 +850,13 @@ #[cfg(feature = "std")] extern crate std; -#[cfg(feature = "std")] -pub use std::error::Error as StdError; +pub use core::error::Error as StdError; -#[cfg(not(feature = "std"))] -/// Compatibility trait for error handling in no_std environments. -/// This trait provides a subset of `std::error::Error` functionality -/// suitable for no_std environments. -pub trait StdError: core::fmt::Debug + core::fmt::Display { - /// Returns the lower-level source of this error, if any. - fn source(&self) -> Option<&(dyn StdError + 'static)> { - None - } +#[doc(hidden)] +pub mod __alloc { + extern crate alloc; + pub use alloc::borrow; + pub use alloc::boxed::Box; } #[cfg(feature = "derive")] diff --git a/src/miette_diagnostic.rs b/src/miette_diagnostic.rs index dc0fa28..d56391a 100644 --- a/src/miette_diagnostic.rs +++ b/src/miette_diagnostic.rs @@ -1,13 +1,10 @@ extern crate alloc; -#[cfg(not(feature = "std"))] -use crate::StdError as Error; use alloc::boxed::Box; use alloc::string::String; use alloc::vec::Vec; +use core::error::Error; use core::fmt::{Debug, Display}; -#[cfg(feature = "std")] -use std::error::Error; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -267,6 +264,7 @@ impl MietteDiagnostic { #[cfg(feature = "serde")] #[test] fn test_serialize_miette_diagnostic() { + use alloc::format; use serde_json::json; use crate::diagnostic; @@ -317,6 +315,7 @@ fn test_serialize_miette_diagnostic() { #[cfg(feature = "serde")] #[test] fn test_deserialize_miette_diagnostic() { + use alloc::format; use serde_json::json; use crate::diagnostic; diff --git a/src/panic.rs b/src/panic.rs index eb57601..3437ef8 100644 --- a/src/panic.rs +++ b/src/panic.rs @@ -1,6 +1,11 @@ -#[cfg(feature = "std")] +// `fancy` feature requires std, so std imports are fine here. +#![allow( + clippy::std_instead_of_core, + clippy::std_instead_of_alloc, + clippy::alloc_instead_of_core +)] + use std::boxed::Box; -#[cfg(feature = "std")] use std::{ eprintln, error::Error, @@ -9,14 +14,11 @@ use std::{ string::{String, ToString}, }; -#[cfg(feature = "std")] use backtrace::Backtrace; -#[cfg(feature = "std")] use crate::{Context, Diagnostic, Result}; /// Makes miette show pretty error messages when your program crashes. -#[cfg(feature = "std")] pub fn set_panic_hook() { std::panic::set_hook(Box::new(move |info| { let mut message = "Something went wrong".to_string(); @@ -38,20 +40,9 @@ pub fn set_panic_hook() { })); } -/// Makes miette show pretty error messages when your program crashes. -/// -/// On computers without the standard library, this function does nothing -/// because crash hooks need the standard library to work. -#[cfg(not(feature = "std"))] -pub fn set_panic_hook() { - // Does nothing on computers without the standard library -} - #[derive(Debug)] -#[cfg(feature = "std")] struct Panic(String); -#[cfg(feature = "std")] impl Display for Panic { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let msg = &self.0; @@ -60,10 +51,8 @@ impl Display for Panic { } } -#[cfg(feature = "std")] impl Error for Panic {} -#[cfg(feature = "std")] impl Diagnostic for Panic { fn help<'a>(&'a self) -> Option> { Some(Box::new( @@ -72,7 +61,6 @@ impl Diagnostic for Panic { } } -#[cfg(feature = "std")] impl Panic { fn backtrace() -> String { use std::fmt::Write; @@ -129,7 +117,6 @@ impl Panic { } #[cfg(test)] -#[cfg(feature = "std")] mod tests { use std::{borrow::ToOwned, error::Error}; diff --git a/src/protocol.rs b/src/protocol.rs index ef6f7e0..f34aa7d 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -177,8 +177,8 @@ impl From for Box { use crate::DiagnosticError; #[cfg(feature = "std")] -impl From> for Box { - fn from(s: Box) -> Self { +impl From> for Box { + fn from(s: Box) -> Self { Box::new(DiagnosticError(s)) } } @@ -374,6 +374,7 @@ impl LabeledSpan { #[cfg(feature = "serde")] #[test] fn test_serialize_labeled_span() { + use alloc::string::ToString; use serde_json::json; assert_eq!( @@ -397,6 +398,7 @@ fn test_serialize_labeled_span() { #[cfg(feature = "serde")] #[test] fn test_deserialize_labeled_span() { + use alloc::string::ToString; use serde_json::json; let span: LabeledSpan = serde_json::from_value(json!({ diff --git a/tests/color_format.rs b/tests/color_format.rs index f0f1ad5..682f54d 100644 --- a/tests/color_format.rs +++ b/tests/color_format.rs @@ -1,7 +1,5 @@ #![cfg(feature = "fancy-no-backtrace")] -extern crate alloc; - use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler, RgbColors}; use regex::Regex; use std::ffi::OsString; diff --git a/tests/derive.rs b/tests/derive.rs index 087d184..88b46eb 100644 --- a/tests/derive.rs +++ b/tests/derive.rs @@ -1,5 +1,4 @@ -#[allow(unused_assignments)] // some fields unused when feature="fancy" -extern crate alloc; +#![allow(unused_assignments)] // some fields unused when feature="fancy" use miette::{Diagnostic, Report, Severity, SourceSpan}; use thiserror::Error; diff --git a/tests/graphical.rs b/tests/graphical.rs index e2548e1..93c9bce 100644 --- a/tests/graphical.rs +++ b/tests/graphical.rs @@ -1,7 +1,5 @@ #![cfg(feature = "fancy-no-backtrace")] -extern crate alloc; - use miette::{ Diagnostic, GraphicalReportHandler, GraphicalTheme, MietteError, NamedSource, NarratableReportHandler, Report, SourceSpan, diff --git a/tests/narrated.rs b/tests/narrated.rs index ced0b23..52acd13 100644 --- a/tests/narrated.rs +++ b/tests/narrated.rs @@ -1,7 +1,5 @@ #![cfg(feature = "fancy-no-backtrace")] -extern crate alloc; - use miette::{Diagnostic, MietteError, NamedSource, NarratableReportHandler, Report, SourceSpan}; use miette::{GraphicalReportHandler, GraphicalTheme}; diff --git a/tests/test_derive_attr.rs b/tests/test_derive_attr.rs index 8c13670..f2b9ddf 100644 --- a/tests/test_derive_attr.rs +++ b/tests/test_derive_attr.rs @@ -1,5 +1,4 @@ // Testing of the `diagnostic` attr used by derive(Diagnostic) -extern crate alloc; use miette::{Diagnostic, LabeledSpan, NamedSource, SourceSpan}; use thiserror::Error; diff --git a/tests/test_derive_collection.rs b/tests/test_derive_collection.rs index baf037c..952b505 100644 --- a/tests/test_derive_collection.rs +++ b/tests/test_derive_collection.rs @@ -1,5 +1,3 @@ -extern crate alloc; - use std::{ collections::{LinkedList, VecDeque}, ops::Range, diff --git a/tests/test_diagnostic_source_macro.rs b/tests/test_diagnostic_source_macro.rs index 33aebb6..1349303 100644 --- a/tests/test_diagnostic_source_macro.rs +++ b/tests/test_diagnostic_source_macro.rs @@ -1,5 +1,3 @@ -extern crate alloc; - use miette::Diagnostic; #[derive(Debug, miette::Diagnostic, thiserror::Error)] diff --git a/tests/test_json.rs b/tests/test_json.rs index b055fc7..664318a 100644 --- a/tests/test_json.rs +++ b/tests/test_json.rs @@ -1,6 +1,4 @@ mod json_report_handler { - extern crate alloc; - use miette::{Diagnostic, MietteError, NamedSource, Report, SourceSpan}; use miette::JSONReportHandler; From c5ddfdc0bed842d0eb11ca8db1d985dce8c74b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Wed, 14 Jan 2026 15:45:27 -0500 Subject: [PATCH 09/10] fix: make spin dependency optional for std builds - spin is now optional, only needed for no_std (provides Once) - std builds use OnceLock, avoiding unnecessary dependency - Restore unicode-width default features (keeps CJK support) --- Cargo.toml | 5 ++--- src/eyreish/mod.rs | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5b2dece..f6221dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,10 +14,9 @@ exclude = ["images/", "tests/", "miette-derive/"] [dependencies] miette-derive = { path = "miette-derive", version = "=7.6.0", optional = true } -unicode-width = { version = "0.2.0", default-features = false } +unicode-width = "0.2.0" cfg-if = "1.0.0" -# spin is needed for no_std environments (provides Once without std) -spin = { version = "0.9", default-features = false, features = ["once"] } +spin = { version = "0.9", default-features = false, features = ["once"], optional = true } owo-colors = { version = "4.0.0", optional = true } textwrap = { version = "0.16.0", default-features = false, features = ["unicode-linebreak", "unicode-width"], optional = true } diff --git a/src/eyreish/mod.rs b/src/eyreish/mod.rs index 3667b73..c7fb697 100644 --- a/src/eyreish/mod.rs +++ b/src/eyreish/mod.rs @@ -68,7 +68,7 @@ pub type ErrorHook = #[cfg(feature = "std")] static HOOK: OnceLock = OnceLock::new(); -#[cfg(not(feature = "std"))] +#[cfg(all(not(feature = "std"), feature = "spin"))] static HOOK: spin::Once = spin::Once::new(); fn default_hook() -> ErrorHook { @@ -96,7 +96,7 @@ pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { } /// Set the error hook. -#[cfg(not(feature = "std"))] +#[cfg(all(not(feature = "std"), feature = "spin"))] pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { HOOK.call_once(|| hook); Ok(()) @@ -108,7 +108,7 @@ pub(crate) fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box Box { let hook = HOOK.call_once(default_hook); hook(error) @@ -126,7 +126,7 @@ pub(crate) fn capture_handler_with_location( } #[track_caller] -#[cfg(not(feature = "std"))] +#[cfg(all(not(feature = "std"), feature = "spin"))] pub(crate) fn capture_handler_with_location( error: &(dyn Diagnostic + 'static), ) -> Box { From f346d78d21b91f4a552ec9cb453c2c2b0f955b98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Garillot?= Date: Wed, 14 Jan 2026 16:31:42 -0500 Subject: [PATCH 10/10] fix: address review feedback and add feature powerset CI - Make spin a required dependency for no_std hook support (OnceLock unavailable) - Simplify cfg guards: not(std) instead of not(std) && spin - Change clippy alloc/std lints from warn to deny - Remove no_std stub for from_current_location (compile-time vs runtime error) - Fix is_graphical cfg logic for fancy-base without fancy-no-backtrace - Add cargo-hack feature powerset CI job to verify all 56 combinations --- .github/workflows/ci.yml | 14 ++++++++++++++ Cargo.toml | 2 +- src/eyreish/mod.rs | 8 ++++---- src/handler.rs | 9 +++++---- src/lib.rs | 2 +- src/protocol.rs | 9 --------- 6 files changed, 25 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6250716..161aecc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,6 +99,20 @@ jobs: MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance run: cargo miri test --all --verbose --features fancy + feature-check: + name: Feature combinations check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - name: Check feature powerset + run: cargo hack check --feature-powerset --no-dev-deps --skip default + minimal_versions: name: Minimal versions check runs-on: ${{ matrix.os }} diff --git a/Cargo.toml b/Cargo.toml index f6221dc..ae6292e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = ["images/", "tests/", "miette-derive/"] miette-derive = { path = "miette-derive", version = "=7.6.0", optional = true } unicode-width = "0.2.0" cfg-if = "1.0.0" -spin = { version = "0.9", default-features = false, features = ["once"], optional = true } +spin = { version = "0.9", default-features = false, features = ["once"] } owo-colors = { version = "4.0.0", optional = true } textwrap = { version = "0.16.0", default-features = false, features = ["unicode-linebreak", "unicode-width"], optional = true } diff --git a/src/eyreish/mod.rs b/src/eyreish/mod.rs index c7fb697..3667b73 100644 --- a/src/eyreish/mod.rs +++ b/src/eyreish/mod.rs @@ -68,7 +68,7 @@ pub type ErrorHook = #[cfg(feature = "std")] static HOOK: OnceLock = OnceLock::new(); -#[cfg(all(not(feature = "std"), feature = "spin"))] +#[cfg(not(feature = "std"))] static HOOK: spin::Once = spin::Once::new(); fn default_hook() -> ErrorHook { @@ -96,7 +96,7 @@ pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { } /// Set the error hook. -#[cfg(all(not(feature = "std"), feature = "spin"))] +#[cfg(not(feature = "std"))] pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> { HOOK.call_once(|| hook); Ok(()) @@ -108,7 +108,7 @@ pub(crate) fn capture_handler(error: &(dyn Diagnostic + 'static)) -> Box Box { let hook = HOOK.call_once(default_hook); hook(error) @@ -126,7 +126,7 @@ pub(crate) fn capture_handler_with_location( } #[track_caller] -#[cfg(all(not(feature = "std"), feature = "spin"))] +#[cfg(not(feature = "std"))] pub(crate) fn capture_handler_with_location( error: &(dyn Diagnostic + 'static), ) -> Box { diff --git a/src/handler.rs b/src/handler.rs index 5ceaa78..21ab0e7 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -352,10 +352,6 @@ impl MietteHandlerOpts { } else if let Some(force_graphical) = self.force_graphical { force_graphical } else { - #[cfg(all(feature = "fancy-no-syscall", not(feature = "fancy-no-backtrace")))] - { - true - } #[cfg(feature = "fancy-no-backtrace")] { if let Ok(env) = std::env::var("NO_GRAPHICS") { @@ -364,6 +360,11 @@ impl MietteHandlerOpts { true } } + #[cfg(not(feature = "fancy-no-backtrace"))] + { + // fancy-base or fancy-no-syscall without std: default to graphical + true + } } } diff --git a/src/lib.rs b/src/lib.rs index dfed8c0..d646ebe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ #![no_std] #![deny(missing_docs, missing_debug_implementations, nonstandard_style)] #![warn(unreachable_pub, rust_2018_idioms)] -#![warn( +#![deny( clippy::alloc_instead_of_core, clippy::std_instead_of_core, clippy::std_instead_of_alloc diff --git a/src/protocol.rs b/src/protocol.rs index f34aa7d..d7e3855 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -731,15 +731,6 @@ impl SourceOffset { .map(|txt| Self::from_location(txt, loc.line() as usize, loc.column() as usize))?, )) } - - /// Returns both the filename that was given and the offset of the caller - /// as a [`SourceOffset`]. - /// - /// In no_std environments, this is not supported and will return an error. - #[cfg(not(feature = "std"))] - pub fn from_current_location() -> Result<(String, Self), MietteError> { - Err(MietteError::OutOfBounds) - } } impl From for SourceOffset {