From a03951740c34e14055fc75f1981dcbb6316fd3fb Mon Sep 17 00:00:00 2001 From: Freja Roberts Date: Wed, 24 Jan 2024 17:32:27 +0100 Subject: [PATCH 01/11] fix: remove ambiguity between options and results trait methods 1. Remove the conditional trait methods `context` and `with_context` from `WrapErr` 2. Remove ambiguity between "wrapping errors" with another, and converting a `None` value to an error. The anyhow compatibility methods (`context` and `with_context`) were introduced as conditional required methods for the trait `WrapErr`, as well as another separate trait `ContextCompat`, implemented for options *and* results. The latter trait also provided methods `wrap_err` and `wrap_err_with`. This led to confusion as the two distinct traits had the same methods, one was implemented on results, and the other on both options and results. Furthermore, wrapping options does not align with our error model where errors are wrapped around other errors; an error wrapping `None` should just be a single error and should not be wrapped. See: #149 --- eyre/src/context.rs | 33 +++++++++++++-------------------- eyre/src/lib.rs | 31 ++----------------------------- eyre/tests/test_location.rs | 8 ++++++-- 3 files changed, 21 insertions(+), 51 deletions(-) diff --git a/eyre/src/context.rs b/eyre/src/context.rs index aaba5bd..44ef6fe 100644 --- a/eyre/src/context.rs +++ b/eyre/src/context.rs @@ -61,42 +61,34 @@ where Err(e) => Err(e.ext_report(msg())), } } +} - #[cfg(feature = "anyhow")] - fn context(self, msg: D) -> Result +#[cfg(feature = "anyhow")] +impl crate::ContextCompat for Result +where + Self: WrapErr, +{ + #[track_caller] + fn context(self, msg: D) -> crate::Result where D: Display + Send + Sync + 'static, { self.wrap_err(msg) } - #[cfg(feature = "anyhow")] - fn with_context(self, msg: F) -> Result + #[track_caller] + fn with_context(self, f: F) -> crate::Result where D: Display + Send + Sync + 'static, F: FnOnce() -> D, { - self.wrap_err_with(msg) + self.wrap_err_with(f) } } #[cfg(feature = "anyhow")] impl crate::ContextCompat for Option { - fn wrap_err(self, msg: D) -> Result - where - D: Display + Send + Sync + 'static, - { - self.context(msg) - } - - fn wrap_err_with(self, msg: F) -> Result - where - D: Display + Send + Sync + 'static, - F: FnOnce() -> D, - { - self.with_context(msg) - } - + #[track_caller] fn context(self, msg: D) -> Result where D: Display + Send + Sync + 'static, @@ -107,6 +99,7 @@ impl crate::ContextCompat for Option { } } + #[track_caller] fn with_context(self, msg: F) -> Result where D: Display + Send + Sync + 'static, diff --git a/eyre/src/lib.rs b/eyre/src/lib.rs index 33c60d3..65b3980 100644 --- a/eyre/src/lib.rs +++ b/eyre/src/lib.rs @@ -1125,21 +1125,6 @@ pub trait WrapErr: context::private::Sealed { where D: Display + Send + Sync + 'static, F: FnOnce() -> D; - - /// Compatibility re-export of wrap_err for interop with `anyhow` - #[cfg(feature = "anyhow")] - #[cfg_attr(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(feature = "anyhow")] - #[cfg_attr(track_caller, track_caller)] - fn with_context(self, f: F) -> Result - where - D: Display + Send + Sync + 'static, - F: FnOnce() -> D; } /// Provides the [`ok_or_eyre`][OptionExt::ok_or_eyre] method for [`Option`]. @@ -1197,7 +1182,8 @@ pub trait OptionExt: context::private::Sealed { M: Debug + Display + Send + Sync + 'static; } -/// Provides the `context` method for `Option` when porting from `anyhow` +/// Provides the `context` and `with_context` methods for `Result` and `Option` to enhance +/// compatibility when porting from anyhow. /// /// This trait is sealed and cannot be implemented for types outside of /// `eyre`. @@ -1256,19 +1242,6 @@ pub trait ContextCompat: context::private::Sealed { where D: Display + Send + Sync + 'static, F: FnOnce() -> D; - - /// Compatibility re-export of `context` for porting from `anyhow` to `eyre` - #[cfg_attr(track_caller, track_caller)] - fn wrap_err(self, msg: D) -> Result - where - D: Display + Send + Sync + 'static; - - /// Compatibility re-export of `with_context` for porting from `anyhow` to `eyre` - #[cfg_attr(track_caller, track_caller)] - fn wrap_err_with(self, f: F) -> Result - where - D: Display + Send + Sync + 'static, - F: FnOnce() -> D; } /// Equivalent to Ok::<_, eyre::Error>(value). diff --git a/eyre/tests/test_location.rs b/eyre/tests/test_location.rs index 53c181f..6a6ccfd 100644 --- a/eyre/tests/test_location.rs +++ b/eyre/tests/test_location.rs @@ -101,6 +101,8 @@ fn test_option_ok_or_eyre() { #[cfg(feature = "anyhow")] #[test] fn test_context() { + use eyre::ContextCompat; + let _ = eyre::set_hook(Box::new(|_e| { let expected_location = file!(); Box::new(LocationHandler::new(expected_location)) @@ -117,6 +119,8 @@ fn test_context() { #[cfg(feature = "anyhow")] #[test] fn test_with_context() { + use eyre::ContextCompat; + let _ = eyre::set_hook(Box::new(|_e| { let expected_location = file!(); Box::new(LocationHandler::new(expected_location)) @@ -139,7 +143,7 @@ fn test_option_compat_wrap_err() { })); use eyre::ContextCompat; - let err = None::<()>.wrap_err("oopsie").unwrap_err(); + let err = None::<()>.context("oopsie").unwrap_err(); // should panic if the location isn't in our crate println!("{:?}", err); @@ -154,7 +158,7 @@ fn test_option_compat_wrap_err_with() { })); use eyre::ContextCompat; - let err = None::<()>.wrap_err_with(|| "oopsie").unwrap_err(); + let err = None::<()>.with_context(|| "oopsie").unwrap_err(); // should panic if the location isn't in our crate println!("{:?}", err); From 90645efddcefa475a37608184cb3a699685ffc65 Mon Sep 17 00:00:00 2001 From: Freja Roberts Date: Fri, 29 Mar 2024 10:51:15 +0100 Subject: [PATCH 02/11] fix: warnings from new clippy lints --- eyre/src/lib.rs | 6 +++--- eyre/tests/test_location.rs | 2 -- eyre/tests/test_repr.rs | 1 - 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/eyre/src/lib.rs b/eyre/src/lib.rs index 65b3980..1c5231f 100644 --- a/eyre/src/lib.rs +++ b/eyre/src/lib.rs @@ -624,7 +624,7 @@ fn capture_handler(error: &(dyn StdError + 'static)) -> Box { } impl dyn EyreHandler { - /// + /// Check if the handler is of type `T` pub fn is(&self) -> bool { // Get `TypeId` of the type this function is instantiated with. let t = core::any::TypeId::of::(); @@ -636,7 +636,7 @@ impl dyn EyreHandler { t == concrete } - /// + /// Downcast the handler to a contcrete type `T` pub fn downcast_ref(&self) -> Option<&T> { if self.is::() { unsafe { Some(&*(self as *const dyn EyreHandler as *const T)) } @@ -645,7 +645,7 @@ impl dyn EyreHandler { } } - /// + /// Downcast the handler to a contcrete type `T` pub fn downcast_mut(&mut self) -> Option<&mut T> { if self.is::() { unsafe { Some(&mut *(self as *mut dyn EyreHandler as *mut T)) } diff --git a/eyre/tests/test_location.rs b/eyre/tests/test_location.rs index 63f8709..2d199d9 100644 --- a/eyre/tests/test_location.rs +++ b/eyre/tests/test_location.rs @@ -107,7 +107,6 @@ fn test_context() { Box::new(LocationHandler::new(expected_location)) })); - use eyre::WrapErr; let err = read_path("totally_fake_path") .context("oopsie") .unwrap_err(); @@ -126,7 +125,6 @@ fn test_with_context() { Box::new(LocationHandler::new(expected_location)) })); - use eyre::WrapErr; let err = read_path("totally_fake_path") .with_context(|| "oopsie") .unwrap_err(); diff --git a/eyre/tests/test_repr.rs b/eyre/tests/test_repr.rs index 71a5663..5a1b5bf 100644 --- a/eyre/tests/test_repr.rs +++ b/eyre/tests/test_repr.rs @@ -4,7 +4,6 @@ mod drop; use self::common::maybe_install_handler; use self::drop::{DetectDrop, Flag}; use eyre::Report; -use std::marker::Unpin; use std::mem; #[test] From f80de3e49e65de3bcefec8efec041a524cab8022 Mon Sep 17 00:00:00 2001 From: Freja Roberts Date: Tue, 26 Mar 2024 13:13:45 +0100 Subject: [PATCH 03/11] fix: compile probe and nightly backtraces This addresses the invalid compile probe testing for a non-longer existing feature by updating it and eyre to use the `error_generic_member_access` features instead. The report and errors have all been updated to accomodate this and the new backtrace provide API Fixes: #84 and #97 --- Cargo.toml | 1 + README.md | 20 +++- eyre/Cargo.toml | 4 + eyre/build.rs | 167 ++++++++++++---------------- eyre/src/backtrace.rs | 22 +++- eyre/src/context.rs | 9 +- eyre/src/error.rs | 5 + eyre/src/lib.rs | 11 +- eyre/src/wrapper.rs | 6 +- eyre/tests/generic_member_access.rs | 73 ++++++++++++ eyre/tests/test_toolchain.rs | 34 ------ 11 files changed, 201 insertions(+), 151 deletions(-) create mode 100644 eyre/tests/generic_member_access.rs delete mode 100644 eyre/tests/test_toolchain.rs diff --git a/Cargo.toml b/Cargo.toml index b58a509..125e065 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ rust-version = "1.65.0" indenter = "0.3.0" once_cell = "1.18.0" owo-colors = "3.2.0" +autocfg = "1.0" [profile.dev.package.backtrace] opt-level = 3 diff --git a/README.md b/README.md index 31e25a9..a0a99fb 100644 --- a/README.md +++ b/README.md @@ -122,9 +122,12 @@ avoid using `eyre::Report` as your public error type. } ``` -- If using the nightly channel, a backtrace is captured and printed with the - error if the underlying error type does not already provide its own. In order - to see backtraces, they must be enabled through the environment variables +- If using rust >1.65, a backtrace is captured and printed with the + error. + + On nightly eyre will use the underlying error's backtrace if it has one. + + In order to see backtraces, they must be enabled through the environment variables described in [`std::backtrace`]: - If you want panics and errors to both have backtraces, set @@ -141,7 +144,7 @@ avoid using `eyre::Report` as your public error type. - Eyre works with any error type that has an impl of `std::error::Error`, including ones defined in your crate. We do not bundle a `derive(Error)` macro but you can write the impls yourself or use a standalone macro like - [thiserror]. + [thiserror](https://github.com/dtolnay/thiserror). ```rust use thiserror::Error; @@ -178,6 +181,15 @@ No-std support was removed in 2020 in [commit 608a16a] due to unaddressed upstre [commit 608a16a]: https://github.com/eyre-rs/eyre/pull/29/commits/608a16aa2c2c27eca6c88001cc94c6973c18f1d5 + +## Backtrace support + +The built in default handler has support for capturing backtrace using `rustc-1.65` or later. + +Backtraces are captured when an error is converted to an `eyre::Report` (such as using `?` or `eyre!`). + +If using the nightly toolchain, backtraces will also be captured and accessed from other errors using [error_generic_member_access](https://github.com/rust-lang/rfcs/pull/2895) if available. + ## Comparison to failure The `eyre::Report` type works something like `failure::Error`, but unlike diff --git a/eyre/Cargo.toml b/eyre/Cargo.toml index 6848126..390a271 100644 --- a/eyre/Cargo.toml +++ b/eyre/Cargo.toml @@ -23,10 +23,14 @@ indenter = { workspace = true } once_cell = { workspace = true } pyo3 = { version = "0.20", optional = true, default-features = false } +[build-dependencies] +autocfg = { workspace = true } + [dev-dependencies] futures = { version = "0.3", default-features = false } rustversion = "1.0" thiserror = "1.0" +# TODO: 1.0.90 uses rustc-1.70 trybuild = { version = "=1.0.83", features = ["diff"] } backtrace = "0.3.46" anyhow = "1.0.28" diff --git a/eyre/build.rs b/eyre/build.rs index e0b2d29..ab36c2a 100644 --- a/eyre/build.rs +++ b/eyre/build.rs @@ -1,23 +1,53 @@ -use std::env; -use std::ffi::OsString; -use std::fs; -use std::path::Path; -use std::process::{Command, ExitStatus}; -use std::str; - -// This code exercises the surface area that we expect of the std Backtrace -// type. If the current toolchain is able to compile it, we go ahead and use -// backtrace in eyre. -const BACKTRACE_PROBE: &str = r#" - #![feature(backtrace)] +use std::{ + env, fs, + path::Path, + process::{Command, ExitStatus}, +}; + +fn main() { + let ac = autocfg::new(); + + // https://github.com/rust-lang/rust/issues/99301 [nightly] + // + // Autocfg does currently not support custom probes, or `nightly` only features + match compile_probe(GENERIC_MEMBER_ACCESS_PROBE) { + Some(status) if status.success() => autocfg::emit("generic_member_access"), + _ => {} + } + + // https://github.com/rust-lang/rust/issues/47809 [rustc-1.46] + ac.emit_expression_cfg("std::panic::Location::caller", "track_caller"); + + if ac.probe_rustc_version(1, 52) { + autocfg::emit("eyre_no_fmt_arguments_as_str"); + } + + if ac.probe_rustc_version(1, 58) { + autocfg::emit("eyre_no_fmt_args_capture"); + } + + if ac.probe_rustc_version(1, 65) { + autocfg::emit("backtrace") + } +} + +// This code exercises the surface area or the generic member access feature for the `std::error::Error` trait. +// +// This is use to detect and supply backtrace information through different errors types. +const GENERIC_MEMBER_ACCESS_PROBE: &str = r#" + #![feature(error_generic_member_access)] #![allow(dead_code)] - use std::backtrace::{Backtrace, BacktraceStatus}; - use std::error::Error; + use std::error::{Error, Request}; use std::fmt::{self, Display}; #[derive(Debug)] - struct E; + struct E { + backtrace: MyBacktrace, + } + + #[derive(Debug)] + struct MyBacktrace; impl Display for E { fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { @@ -26,59 +56,44 @@ const BACKTRACE_PROBE: &str = r#" } impl Error for E { - fn backtrace(&self) -> Option<&Backtrace> { - let backtrace = Backtrace::capture(); - match backtrace.status() { - BacktraceStatus::Captured | BacktraceStatus::Disabled | _ => {} - } - unimplemented!() + fn provide<'a>(&'a self, request: &mut Request<'a>) { + request + .provide_ref::(&self.backtrace); } } "#; -const TRACK_CALLER_PROBE: &str = r#" - #![allow(dead_code)] - - #[track_caller] - fn foo() { - let _location = std::panic::Location::caller(); - } -"#; +fn compile_probe(probe: &str) -> Option { + let rustc = env::var_os("RUSTC")?; + let out_dir = env::var_os("OUT_DIR")?; + let probefile = Path::new(&out_dir).join("probe.rs"); + fs::write(&probefile, probe).ok()?; -fn main() { - match compile_probe(BACKTRACE_PROBE) { - Some(status) if status.success() => println!("cargo:rustc-cfg=backtrace"), - _ => {} - } + // Supports invoking rustc thrugh a wrapper + let mut cmd = if let Some(wrapper) = env::var_os("RUSTC_WRAPPER") { + let mut cmd = Command::new(wrapper); - match compile_probe(TRACK_CALLER_PROBE) { - Some(status) if status.success() => println!("cargo:rustc-cfg=track_caller"), - _ => {} - } + cmd.arg(rustc); - let version = match rustc_version_info() { - Some(version) => version, - None => return, + cmd + } else { + Command::new(rustc) }; - version.toolchain.set_feature(); - - if version.minor < 52 { - println!("cargo:rustc-cfg=eyre_no_fmt_arguments_as_str"); + if let Some(target) = env::var_os("TARGET") { + cmd.arg("--target").arg(target); } - if version.minor < 58 { - println!("cargo:rustc-cfg=eyre_no_fmt_args_capture"); + // If Cargo wants to set RUSTFLAGS, use that. + if let Ok(rustflags) = env::var("CARGO_ENCODED_RUSTFLAGS") { + if !rustflags.is_empty() { + for arg in rustflags.split('\x1f') { + cmd.arg(arg); + } + } } -} -fn compile_probe(probe: &str) -> Option { - let rustc = env::var_os("RUSTC")?; - let out_dir = env::var_os("OUT_DIR")?; - let probefile = Path::new(&out_dir).join("probe.rs"); - fs::write(&probefile, probe).ok()?; - Command::new(rustc) - .arg("--edition=2018") + cmd.arg("--edition=2018") .arg("--crate-name=eyre_build") .arg("--crate-type=lib") .arg("--emit=metadata") @@ -88,45 +103,3 @@ fn compile_probe(probe: &str) -> Option { .status() .ok() } - -// TODO factor this toolchain parsing and related tests into its own file -#[derive(PartialEq)] -enum Toolchain { - Stable, - Beta, - Nightly, -} -impl Toolchain { - fn set_feature(self) { - match self { - Toolchain::Nightly => println!("cargo:rustc-cfg=nightly"), - Toolchain::Beta => println!("cargo:rustc-cfg=beta"), - Toolchain::Stable => println!("cargo:rustc-cfg=stable"), - } - } -} - -struct VersionInfo { - minor: u32, - toolchain: Toolchain, -} - -fn rustc_version_info() -> Option { - let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); - let output = Command::new(rustc).arg("--version").output().ok()?; - let version = str::from_utf8(&output.stdout).ok()?; - let mut pieces = version.split(['.', ' ', '-']); - if pieces.next() != Some("rustc") { - return None; - } - let _major: u32 = pieces.next()?.parse().ok()?; - let minor = pieces.next()?.parse().ok()?; - let _patch: u32 = pieces.next()?.parse().ok()?; - let toolchain = match pieces.next() { - Some("beta") => Toolchain::Beta, - Some("nightly") => Toolchain::Nightly, - _ => Toolchain::Stable, - }; - let version = VersionInfo { minor, toolchain }; - Some(version) -} diff --git a/eyre/src/backtrace.rs b/eyre/src/backtrace.rs index 6c00d7f..b1b378b 100644 --- a/eyre/src/backtrace.rs +++ b/eyre/src/backtrace.rs @@ -5,18 +5,32 @@ pub(crate) use std::backtrace::Backtrace; pub(crate) enum Backtrace {} #[cfg(backtrace)] +macro_rules! capture_backtrace { + () => { + Some(Backtrace::capture()) + }; +} + +#[cfg(not(backtrace))] +macro_rules! capture_backtrace { + () => { + None + }; +} +/// Capture a backtrace iff there is not already a backtrace in the error chain +#[cfg(generic_member_access)] macro_rules! backtrace_if_absent { ($err:expr) => { - match $err.backtrace() { + match std::error::request_ref::($err as &dyn std::error::Error) { Some(_) => None, - None => Some(Backtrace::capture()), + None => capture_backtrace!(), } }; } -#[cfg(not(backtrace))] +#[cfg(not(generic_member_access))] macro_rules! backtrace_if_absent { ($err:expr) => { - None + capture_backtrace!() }; } diff --git a/eyre/src/context.rs b/eyre/src/context.rs index aaba5bd..d3b6820 100644 --- a/eyre/src/context.rs +++ b/eyre/src/context.rs @@ -2,9 +2,6 @@ use crate::error::{ContextError, ErrorImpl}; use crate::{Report, StdError, WrapErr}; use core::fmt::{self, Debug, Display, Write}; -#[cfg(backtrace)] -use std::backtrace::Backtrace; - mod ext { use super::*; @@ -146,9 +143,9 @@ where D: Display, E: StdError + 'static, { - #[cfg(backtrace)] - fn backtrace(&self) -> Option<&Backtrace> { - self.error.backtrace() + #[cfg(generic_member_access)] + fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) { + self.error.provide(request); } fn source(&self) -> Option<&(dyn StdError + 'static)> { diff --git a/eyre/src/error.rs b/eyre/src/error.rs index d6e3d5d..eed6cfe 100644 --- a/eyre/src/error.rs +++ b/eyre/src/error.rs @@ -855,6 +855,11 @@ impl StdError for ErrorImpl where E: StdError, { + #[cfg(generic_member_access)] + fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) { + self._object.provide(request) + } + fn source(&self) -> Option<&(dyn StdError + 'static)> { ErrorImpl::<()>::error(self.erase()).source() } diff --git a/eyre/src/lib.rs b/eyre/src/lib.rs index 33c60d3..5f8bc94 100644 --- a/eyre/src/lib.rs +++ b/eyre/src/lib.rs @@ -355,7 +355,7 @@ unused_parens, while_true )] -#![cfg_attr(backtrace, feature(backtrace))] +#![cfg_attr(generic_member_access, feature(error_generic_member_access))] #![cfg_attr(doc_cfg, feature(doc_cfg))] #![allow( clippy::needless_doctest_main, @@ -778,6 +778,7 @@ impl DefaultHandler { #[allow(unused_variables)] #[cfg_attr(not(feature = "auto-install"), allow(dead_code))] pub fn default_with(error: &(dyn StdError + 'static)) -> Box { + // Capture the backtrace if the source error did not already capture one let backtrace = backtrace_if_absent!(error); Box::new(Self { @@ -837,15 +838,19 @@ impl EyreHandler for DefaultHandler { } } - #[cfg(backtrace)] + #[cfg(generic_member_access)] { use std::backtrace::BacktraceStatus; + // The backtrace can be stored either in the handler instance, or the error itself. + // + // If the source error has a backtrace, the handler should not capture one let backtrace = self .backtrace .as_ref() - .or_else(|| error.backtrace()) + .or_else(|| std::error::request_ref::(error)) .expect("backtrace capture failed"); + if let BacktraceStatus::Captured = backtrace.status() { write!(f, "\n\nStack backtrace:\n{}", backtrace)?; } diff --git a/eyre/src/wrapper.rs b/eyre/src/wrapper.rs index b4da68a..40dc01b 100644 --- a/eyre/src/wrapper.rs +++ b/eyre/src/wrapper.rs @@ -82,9 +82,9 @@ impl Display for BoxedError { } impl StdError for BoxedError { - #[cfg(backtrace)] - fn backtrace(&self) -> Option<&crate::backtrace::Backtrace> { - self.0.backtrace() + #[cfg(generic_member_access)] + fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) { + self.0.provide(request); } fn source(&self) -> Option<&(dyn StdError + 'static)> { diff --git a/eyre/tests/generic_member_access.rs b/eyre/tests/generic_member_access.rs new file mode 100644 index 0000000..c838e1e --- /dev/null +++ b/eyre/tests/generic_member_access.rs @@ -0,0 +1,73 @@ +#![cfg_attr(generic_member_access, feature(error_generic_member_access))] + +mod common; + +#[cfg(all(generic_member_access, not(miri)))] +#[test] +/// Tests that generic member access works through an `eyre::Report` +fn generic_member_access() { + use crate::common::maybe_install_handler; + + use eyre::WrapErr; + use std::backtrace::Backtrace; + use std::fmt::Display; + + fn fail() -> Result<(), MyError> { + Err(MyError { + cupcake: MyCupcake("Blueberry".into()), + backtrace: std::backtrace::Backtrace::capture(), + }) + } + + maybe_install_handler().unwrap(); + + std::env::set_var("RUST_BACKTRACE", "1"); + + #[derive(Debug, PartialEq)] + struct MyCupcake(String); + + #[derive(Debug)] + struct MyError { + cupcake: MyCupcake, + backtrace: std::backtrace::Backtrace, + } + + impl Display for MyError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Error: {}", self.cupcake.0) + } + } + + impl std::error::Error for MyError { + fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) { + request + .provide_ref(&self.cupcake) + .provide_ref(&self.backtrace); + } + } + + let err = fail() + .wrap_err("Failed to bake my favorite cupcake") + .unwrap_err(); + + let err: Box = err.into(); + + assert!( + format!("{:?}", err).contains("generic_member_access::generic_member_access::fail"), + "should contain the source error backtrace" + ); + + assert_eq!( + std::error::request_ref::(&*err), + Some(&MyCupcake("Blueberry".into())) + ); + + let bt = std::error::request_ref::(&*err).unwrap(); + + assert!( + bt.to_string() + .contains("generic_member_access::generic_member_access::fail"), + "should contain the fail method as it was captured by the original error\n\n{}", + bt + ); +} diff --git a/eyre/tests/test_toolchain.rs b/eyre/tests/test_toolchain.rs deleted file mode 100644 index 0309398..0000000 --- a/eyre/tests/test_toolchain.rs +++ /dev/null @@ -1,34 +0,0 @@ -// These tests check our build script against rustversion. - -#[rustversion::attr(not(nightly), ignore)] -#[test] -fn nightlytest() { - if !cfg!(nightly) { - panic!("nightly feature isn't set when the toolchain is nightly."); - } - if cfg!(any(beta, stable)) { - panic!("beta, stable, and nightly are mutually exclusive features.") - } -} - -#[rustversion::attr(not(beta), ignore)] -#[test] -fn betatest() { - if !cfg!(beta) { - panic!("beta feature is not set when the toolchain is beta."); - } - if cfg!(any(nightly, stable)) { - panic!("beta, stable, and nightly are mutually exclusive features.") - } -} - -#[rustversion::attr(not(stable), ignore)] -#[test] -fn stabletest() { - if !cfg!(stable) { - panic!("stable feature is not set when the toolchain is stable."); - } - if cfg!(any(nightly, beta)) { - panic!("beta, stable, and nightly are mutually exclusive features.") - } -} From 92b6a5701d60a453138aecfb48fd5f56d107ff49 Mon Sep 17 00:00:00 2001 From: Freja Roberts Date: Thu, 25 Apr 2024 23:21:47 +0200 Subject: [PATCH 04/11] fix: use RUST_WORKSPACE_WRAPPER in compile probe --- eyre/build.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/eyre/build.rs b/eyre/build.rs index ab36c2a..d083edd 100644 --- a/eyre/build.rs +++ b/eyre/build.rs @@ -69,16 +69,16 @@ fn compile_probe(probe: &str) -> Option { let probefile = Path::new(&out_dir).join("probe.rs"); fs::write(&probefile, probe).ok()?; - // Supports invoking rustc thrugh a wrapper - let mut cmd = if let Some(wrapper) = env::var_os("RUSTC_WRAPPER") { - let mut cmd = Command::new(wrapper); - - cmd.arg(rustc); - - cmd - } else { - Command::new(rustc) - }; + let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|wrapper| !wrapper.is_empty()); + let rustc_workspace_wrapper = + env::var_os("RUSTC_WORKSPACE_WRAPPER").filter(|wrapper| !wrapper.is_empty()); + let mut rustc = rustc_wrapper + .into_iter() + .chain(rustc_workspace_wrapper) + .chain(std::iter::once(rustc)); + + let mut cmd = Command::new(rustc.next().unwrap()); + cmd.args(rustc); if let Some(target) = env::var_os("TARGET") { cmd.arg("--target").arg(target); From 3d7b20de9718192da52fdb5b4902449b19396e86 Mon Sep 17 00:00:00 2001 From: Freja Roberts Date: Thu, 25 Apr 2024 23:42:12 +0200 Subject: [PATCH 05/11] fix: remove duplicate thiserror reference --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index a0a99fb..37586f1 100644 --- a/README.md +++ b/README.md @@ -207,8 +207,6 @@ you need an error type that can be handled via match or reported. This is common in library crates where you don't know how your users will handle your errors. -[thiserror]: https://github.com/dtolnay/thiserror - ## Compatibility with `anyhow` This crate does its best to be usable as a drop in replacement of `anyhow` and From dd05aa3557b3dd4217560ac0c769d1b0f42db22f Mon Sep 17 00:00:00 2001 From: Freja Roberts Date: Mon, 13 May 2024 18:51:38 +0200 Subject: [PATCH 06/11] fix: empty doc comments disallowed by clippy --- eyre/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/eyre/src/lib.rs b/eyre/src/lib.rs index 21eaeb0..3116230 100644 --- a/eyre/src/lib.rs +++ b/eyre/src/lib.rs @@ -624,7 +624,7 @@ fn capture_handler(error: &(dyn StdError + 'static)) -> Box { } impl dyn EyreHandler { - /// + /// Returns true if the handler is of the specified type pub fn is(&self) -> bool { // Get `TypeId` of the type this function is instantiated with. let t = core::any::TypeId::of::(); @@ -636,7 +636,7 @@ impl dyn EyreHandler { t == concrete } - /// + /// Downcast the handler to a concrete type pub fn downcast_ref(&self) -> Option<&T> { if self.is::() { unsafe { Some(&*(self as *const dyn EyreHandler as *const T)) } @@ -645,7 +645,7 @@ impl dyn EyreHandler { } } - /// + /// Downcast the handler to a concrete type pub fn downcast_mut(&mut self) -> Option<&mut T> { if self.is::() { unsafe { Some(&mut *(self as *mut dyn EyreHandler as *mut T)) } From 4d6e73e5a9de72c79dd9c2d20fda6e0a1a32106c Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Fri, 21 Jun 2024 20:05:30 +0200 Subject: [PATCH 07/11] fix: different call machinery between linux and windows This filters out diverging backtraces caused by different before-main and test calling machinery on windows and linux --- color-eyre/tests/theme.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/color-eyre/tests/theme.rs b/color-eyre/tests/theme.rs index 716aaef..e0666e1 100644 --- a/color-eyre/tests/theme.rs +++ b/color-eyre/tests/theme.rs @@ -170,7 +170,12 @@ fn test_backwards_compatibility(target: String, file_name: &str) { fn normalize_backtrace(input: &str) -> String { input .lines() - .take_while(|v| !v.contains("core::panic") && !v.contains("theme_test_helper::main")) + .take_while(|v| { + !v.contains("core::panic") + && !v.contains("theme_test_helper::main") + && !v.contains("theme::test_error_backwards_compatibility::closure") + && !v.contains("theme::test_error_backwards_compatibility::{{closure}}") + }) .collect::>() .join("\n") } From 231090d5f664d73da0691a4f154a24b75bb1d74e Mon Sep 17 00:00:00 2001 From: Freja Roberts Date: Fri, 28 Jun 2024 10:58:09 +0200 Subject: [PATCH 08/11] fix: typo in doc comment --- eyre/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eyre/src/lib.rs b/eyre/src/lib.rs index b3167f8..93ec747 100644 --- a/eyre/src/lib.rs +++ b/eyre/src/lib.rs @@ -636,7 +636,7 @@ impl dyn EyreHandler { t == concrete } - /// Downcast the handler to a contcrete type `T` + /// Downcast the handler to a concrete type `T` pub fn downcast_ref(&self) -> Option<&T> { if self.is::() { unsafe { Some(&*(self as *const dyn EyreHandler as *const T)) } @@ -645,7 +645,7 @@ impl dyn EyreHandler { } } - /// Downcast the handler to a contcrete type `T` + /// Downcast the handler to a concrete type `T` pub fn downcast_mut(&mut self) -> Option<&mut T> { if self.is::() { unsafe { Some(&mut *(self as *mut dyn EyreHandler as *mut T)) } From 0eabf81eef73e665bb48a08b383f6d13446f600d Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Fri, 28 Jun 2024 17:31:40 +0200 Subject: [PATCH 09/11] fix: import consistency --- eyre/tests/test_location.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/eyre/tests/test_location.rs b/eyre/tests/test_location.rs index 2d199d9..21f4716 100644 --- a/eyre/tests/test_location.rs +++ b/eyre/tests/test_location.rs @@ -100,13 +100,12 @@ fn test_option_ok_or_eyre() { #[cfg(feature = "anyhow")] #[test] fn test_context() { - use eyre::ContextCompat; - let _ = eyre::set_hook(Box::new(|_e| { let expected_location = file!(); Box::new(LocationHandler::new(expected_location)) })); + use eyre::ContextCompat; let err = read_path("totally_fake_path") .context("oopsie") .unwrap_err(); @@ -118,13 +117,12 @@ fn test_context() { #[cfg(feature = "anyhow")] #[test] fn test_with_context() { - use eyre::ContextCompat; - let _ = eyre::set_hook(Box::new(|_e| { let expected_location = file!(); Box::new(LocationHandler::new(expected_location)) })); + use eyre::ContextCompat; let err = read_path("totally_fake_path") .with_context(|| "oopsie") .unwrap_err(); From c98bc8cc1cef5eef28badbd8d19d6ab1a4b5a324 Mon Sep 17 00:00:00 2001 From: Tei Leelo Roberts Date: Fri, 28 Jun 2024 17:39:22 +0200 Subject: [PATCH 10/11] fix: consistent naming and duplicated tests --- eyre/tests/test_location.rs | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/eyre/tests/test_location.rs b/eyre/tests/test_location.rs index 21f4716..ad1941c 100644 --- a/eyre/tests/test_location.rs +++ b/eyre/tests/test_location.rs @@ -131,36 +131,6 @@ fn test_with_context() { println!("{:?}", err); } -#[cfg(feature = "anyhow")] -#[test] -fn test_option_compat_wrap_err() { - let _ = eyre::set_hook(Box::new(|_e| { - let expected_location = file!(); - Box::new(LocationHandler::new(expected_location)) - })); - - use eyre::ContextCompat; - let err = None::<()>.context("oopsie").unwrap_err(); - - // should panic if the location isn't in our crate - println!("{:?}", err); -} - -#[cfg(feature = "anyhow")] -#[test] -fn test_option_compat_wrap_err_with() { - let _ = eyre::set_hook(Box::new(|_e| { - let expected_location = file!(); - Box::new(LocationHandler::new(expected_location)) - })); - - use eyre::ContextCompat; - let err = None::<()>.with_context(|| "oopsie").unwrap_err(); - - // should panic if the location isn't in our crate - println!("{:?}", err); -} - #[cfg(feature = "anyhow")] #[test] fn test_option_compat_context() { From d717cf9cac6f7ca6cad3e28885ab54ad2888a5fc Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Sat, 29 Jun 2024 07:42:39 -0700 Subject: [PATCH 11/11] fix(color_eyre): build warnings Remove structs that are unused and have been migrated to use the eyre versions of the same: - color_eyre::config::InstallError -> eyre::InstallError - color_eyre::config::InstallThemeError -> eyre::InstallThemeError - color_eyre::config::InstallColorSpantraceThemeError -> no equivalent Add cfg guards to the DisplayExt, FooterWriter, Footer, and Header to prevent unused warnings when the issue-url feature is not enabled. --- color-eyre/src/config.rs | 33 --------------------------------- color-eyre/src/writers.rs | 8 ++++++++ 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/color-eyre/src/config.rs b/color-eyre/src/config.rs index 22b4e60..f8f19b4 100644 --- a/color-eyre/src/config.rs +++ b/color-eyre/src/config.rs @@ -11,39 +11,6 @@ use std::env; use std::fmt::Write as _; use std::{fmt, path::PathBuf, sync::Arc}; -#[derive(Debug)] -struct InstallError; - -impl fmt::Display for InstallError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("could not install the BacktracePrinter as another was already installed") - } -} - -impl std::error::Error for InstallError {} - -#[derive(Debug)] -struct InstallThemeError; - -impl fmt::Display for InstallThemeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("could not set the provided `Theme` globally as another was already set") - } -} - -impl std::error::Error for InstallThemeError {} - -#[derive(Debug)] -struct InstallColorSpantraceThemeError; - -impl fmt::Display for InstallColorSpantraceThemeError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("could not set the provided `Theme` via `color_spantrace::set_theme` globally as another was already set") - } -} - -impl std::error::Error for InstallColorSpantraceThemeError {} - /// A struct that represents a theme that is used by `color_eyre` #[derive(Debug, Copy, Clone, Default)] pub struct Theme { diff --git a/color-eyre/src/writers.rs b/color-eyre/src/writers.rs index b5bb344..ce828af 100644 --- a/color-eyre/src/writers.rs +++ b/color-eyre/src/writers.rs @@ -28,11 +28,13 @@ impl WriterExt for W { } } +#[cfg(feature = "issue-url")] pub(crate) trait DisplayExt: Sized + Display { fn with_header(self, header: H) -> Header; fn with_footer(self, footer: F) -> Footer; } +#[cfg(feature = "issue-url")] impl DisplayExt for T where T: Display, @@ -80,11 +82,13 @@ where } } +#[cfg(feature = "issue-url")] pub(crate) struct FooterWriter { inner: W, had_output: bool, } +#[cfg(feature = "issue-url")] impl fmt::Write for FooterWriter where W: fmt::Write, @@ -98,6 +102,7 @@ where } } +#[cfg(feature = "issue-url")] #[allow(explicit_outlives_requirements)] pub(crate) struct Footer where @@ -108,6 +113,7 @@ where footer: H, } +#[cfg(feature = "issue-url")] impl fmt::Display for Footer where B: Display, @@ -129,6 +135,7 @@ where } } +#[cfg(feature = "issue-url")] #[allow(explicit_outlives_requirements)] pub(crate) struct Header where @@ -139,6 +146,7 @@ where h: H, } +#[cfg(feature = "issue-url")] impl fmt::Display for Header where B: Display,