Merge branch 'master' into bump-backtrace-rs

This commit is contained in:
Freja Roberts 2024-07-20 18:47:24 +02:00 committed by GitHub
commit a6c8a61628
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 241 additions and 276 deletions

View File

@ -17,6 +17,7 @@ rust-version = "1.65.0"
indenter = "0.3.0" indenter = "0.3.0"
once_cell = "1.18.0" once_cell = "1.18.0"
owo-colors = "4.0" owo-colors = "4.0"
autocfg = "1.0"
[profile.dev.package.backtrace] [profile.dev.package.backtrace]
opt-level = 3 opt-level = 3

View File

@ -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 - If using rust >1.65, a backtrace is captured and printed with the
error if the underlying error type does not already provide its own. In order error.
to see backtraces, they must be enabled through the environment variables
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`]: described in [`std::backtrace`]:
- If you want panics and errors to both have backtraces, set - 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`, - 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 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 but you can write the impls yourself or use a standalone macro like
[thiserror]. [thiserror](https://github.com/dtolnay/thiserror).
```rust ```rust
use thiserror::Error; use thiserror::Error;
@ -178,6 +181,15 @@ No-std support was removed in 2020 in [commit 608a16a] due to unaddressed upstre
[commit 608a16a]: [commit 608a16a]:
https://github.com/eyre-rs/eyre/pull/29/commits/608a16aa2c2c27eca6c88001cc94c6973c18f1d5 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 ## Comparison to failure
The `eyre::Report` type works something like `failure::Error`, but unlike The `eyre::Report` type works something like `failure::Error`, but unlike
@ -195,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 common in library crates where you don't know how your users will handle
your errors. your errors.
[thiserror]: https://github.com/dtolnay/thiserror
## Compatibility with `anyhow` ## Compatibility with `anyhow`
This crate does its best to be usable as a drop in replacement of `anyhow` and This crate does its best to be usable as a drop in replacement of `anyhow` and

View File

@ -11,39 +11,6 @@ use std::env;
use std::fmt::Write as _; use std::fmt::Write as _;
use std::{fmt, path::PathBuf, sync::Arc}; 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` /// A struct that represents a theme that is used by `color_eyre`
#[derive(Debug, Copy, Clone, Default)] #[derive(Debug, Copy, Clone, Default)]
pub struct Theme { pub struct Theme {

View File

@ -28,11 +28,13 @@ impl<W> WriterExt for W {
} }
} }
#[cfg(feature = "issue-url")]
pub(crate) trait DisplayExt: Sized + Display { pub(crate) trait DisplayExt: Sized + Display {
fn with_header<H: Display>(self, header: H) -> Header<Self, H>; fn with_header<H: Display>(self, header: H) -> Header<Self, H>;
fn with_footer<F: Display>(self, footer: F) -> Footer<Self, F>; fn with_footer<F: Display>(self, footer: F) -> Footer<Self, F>;
} }
#[cfg(feature = "issue-url")]
impl<T> DisplayExt for T impl<T> DisplayExt for T
where where
T: Display, T: Display,
@ -80,11 +82,13 @@ where
} }
} }
#[cfg(feature = "issue-url")]
pub(crate) struct FooterWriter<W> { pub(crate) struct FooterWriter<W> {
inner: W, inner: W,
had_output: bool, had_output: bool,
} }
#[cfg(feature = "issue-url")]
impl<W> fmt::Write for FooterWriter<W> impl<W> fmt::Write for FooterWriter<W>
where where
W: fmt::Write, W: fmt::Write,
@ -98,6 +102,7 @@ where
} }
} }
#[cfg(feature = "issue-url")]
#[allow(explicit_outlives_requirements)] #[allow(explicit_outlives_requirements)]
pub(crate) struct Footer<B, H> pub(crate) struct Footer<B, H>
where where
@ -108,6 +113,7 @@ where
footer: H, footer: H,
} }
#[cfg(feature = "issue-url")]
impl<B, H> fmt::Display for Footer<B, H> impl<B, H> fmt::Display for Footer<B, H>
where where
B: Display, B: Display,
@ -129,6 +135,7 @@ where
} }
} }
#[cfg(feature = "issue-url")]
#[allow(explicit_outlives_requirements)] #[allow(explicit_outlives_requirements)]
pub(crate) struct Header<B, H> pub(crate) struct Header<B, H>
where where
@ -139,6 +146,7 @@ where
h: H, h: H,
} }
#[cfg(feature = "issue-url")]
impl<B, H> fmt::Display for Header<B, H> impl<B, H> fmt::Display for Header<B, H>
where where
B: Display, B: Display,

View File

@ -170,7 +170,12 @@ fn test_backwards_compatibility(target: String, file_name: &str) {
fn normalize_backtrace(input: &str) -> String { fn normalize_backtrace(input: &str) -> String {
input input
.lines() .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::<Vec<_>>() .collect::<Vec<_>>()
.join("\n") .join("\n")
} }

View File

@ -23,6 +23,9 @@ indenter = { workspace = true }
once_cell = { workspace = true } once_cell = { workspace = true }
pyo3 = { version = "0.20", optional = true, default-features = false } pyo3 = { version = "0.20", optional = true, default-features = false }
[build-dependencies]
autocfg = { workspace = true }
[dev-dependencies] [dev-dependencies]
futures = { version = "0.3", default-features = false } futures = { version = "0.3", default-features = false }
rustversion = "1.0" rustversion = "1.0"

View File

@ -1,23 +1,53 @@
use std::env; use std::{
use std::ffi::OsString; env, fs,
use std::fs; path::Path,
use std::path::Path; process::{Command, ExitStatus},
use std::process::{Command, ExitStatus}; };
use std::str;
// This code exercises the surface area that we expect of the std Backtrace fn main() {
// type. If the current toolchain is able to compile it, we go ahead and use let ac = autocfg::new();
// backtrace in eyre.
const BACKTRACE_PROBE: &str = r#" // https://github.com/rust-lang/rust/issues/99301 [nightly]
#![feature(backtrace)] //
// 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)] #![allow(dead_code)]
use std::backtrace::{Backtrace, BacktraceStatus}; use std::error::{Error, Request};
use std::error::Error;
use std::fmt::{self, Display}; use std::fmt::{self, Display};
#[derive(Debug)] #[derive(Debug)]
struct E; struct E {
backtrace: MyBacktrace,
}
#[derive(Debug)]
struct MyBacktrace;
impl Display for E { impl Display for E {
fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, _formatter: &mut fmt::Formatter) -> fmt::Result {
@ -26,59 +56,44 @@ const BACKTRACE_PROBE: &str = r#"
} }
impl Error for E { impl Error for E {
fn backtrace(&self) -> Option<&Backtrace> { fn provide<'a>(&'a self, request: &mut Request<'a>) {
let backtrace = Backtrace::capture(); request
match backtrace.status() { .provide_ref::<MyBacktrace>(&self.backtrace);
BacktraceStatus::Captured | BacktraceStatus::Disabled | _ => {}
}
unimplemented!()
} }
} }
"#; "#;
const TRACK_CALLER_PROBE: &str = r#"
#![allow(dead_code)]
#[track_caller]
fn foo() {
let _location = std::panic::Location::caller();
}
"#;
fn main() {
match compile_probe(BACKTRACE_PROBE) {
Some(status) if status.success() => println!("cargo:rustc-cfg=backtrace"),
_ => {}
}
match compile_probe(TRACK_CALLER_PROBE) {
Some(status) if status.success() => println!("cargo:rustc-cfg=track_caller"),
_ => {}
}
let version = match rustc_version_info() {
Some(version) => version,
None => return,
};
version.toolchain.set_feature();
if version.minor < 52 {
println!("cargo:rustc-cfg=eyre_no_fmt_arguments_as_str");
}
if version.minor < 58 {
println!("cargo:rustc-cfg=eyre_no_fmt_args_capture");
}
}
fn compile_probe(probe: &str) -> Option<ExitStatus> { fn compile_probe(probe: &str) -> Option<ExitStatus> {
let rustc = env::var_os("RUSTC")?; let rustc = env::var_os("RUSTC")?;
let out_dir = env::var_os("OUT_DIR")?; let out_dir = env::var_os("OUT_DIR")?;
let probefile = Path::new(&out_dir).join("probe.rs"); let probefile = Path::new(&out_dir).join("probe.rs");
fs::write(&probefile, probe).ok()?; fs::write(&probefile, probe).ok()?;
Command::new(rustc)
.arg("--edition=2018") 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);
}
// 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);
}
}
}
cmd.arg("--edition=2018")
.arg("--crate-name=eyre_build") .arg("--crate-name=eyre_build")
.arg("--crate-type=lib") .arg("--crate-type=lib")
.arg("--emit=metadata") .arg("--emit=metadata")
@ -88,45 +103,3 @@ fn compile_probe(probe: &str) -> Option<ExitStatus> {
.status() .status()
.ok() .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<VersionInfo> {
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)
}

View File

@ -5,18 +5,32 @@ pub(crate) use std::backtrace::Backtrace;
pub(crate) enum Backtrace {} pub(crate) enum Backtrace {}
#[cfg(backtrace)] #[cfg(backtrace)]
macro_rules! backtrace_if_absent { macro_rules! capture_backtrace {
($err:expr) => { () => {
match $err.backtrace() { Some(Backtrace::capture())
Some(_) => None,
None => Some(Backtrace::capture()),
}
}; };
} }
#[cfg(not(backtrace))] #[cfg(not(backtrace))]
macro_rules! backtrace_if_absent { macro_rules! capture_backtrace {
($err:expr) => { () => {
None 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 std::error::request_ref::<std::backtrace::Backtrace>($err as &dyn std::error::Error) {
Some(_) => None,
None => capture_backtrace!(),
}
};
}
#[cfg(not(generic_member_access))]
macro_rules! backtrace_if_absent {
($err:expr) => {
capture_backtrace!()
};
}

View File

@ -2,9 +2,6 @@ use crate::error::{ContextError, ErrorImpl};
use crate::{Report, StdError, WrapErr}; use crate::{Report, StdError, WrapErr};
use core::fmt::{self, Debug, Display, Write}; use core::fmt::{self, Debug, Display, Write};
#[cfg(backtrace)]
use std::backtrace::Backtrace;
mod ext { mod ext {
use super::*; use super::*;
@ -61,42 +58,34 @@ where
Err(e) => Err(e.ext_report(msg())), Err(e) => Err(e.ext_report(msg())),
} }
} }
}
#[cfg(feature = "anyhow")] #[cfg(feature = "anyhow")]
fn context<D>(self, msg: D) -> Result<T, Report> impl<T, E> crate::ContextCompat<T> for Result<T, E>
where
Self: WrapErr<T, E>,
{
#[track_caller]
fn context<D>(self, msg: D) -> crate::Result<T, Report>
where where
D: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
{ {
self.wrap_err(msg) self.wrap_err(msg)
} }
#[cfg(feature = "anyhow")] #[track_caller]
fn with_context<D, F>(self, msg: F) -> Result<T, Report> fn with_context<D, F>(self, f: F) -> crate::Result<T, Report>
where where
D: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
F: FnOnce() -> D, F: FnOnce() -> D,
{ {
self.wrap_err_with(msg) self.wrap_err_with(f)
} }
} }
#[cfg(feature = "anyhow")] #[cfg(feature = "anyhow")]
impl<T> crate::ContextCompat<T> for Option<T> { impl<T> crate::ContextCompat<T> for Option<T> {
fn wrap_err<D>(self, msg: D) -> Result<T, Report> #[track_caller]
where
D: Display + Send + Sync + 'static,
{
self.context(msg)
}
fn wrap_err_with<D, F>(self, msg: F) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D,
{
self.with_context(msg)
}
fn context<D>(self, msg: D) -> Result<T, Report> fn context<D>(self, msg: D) -> Result<T, Report>
where where
D: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
@ -107,6 +96,7 @@ impl<T> crate::ContextCompat<T> for Option<T> {
} }
} }
#[track_caller]
fn with_context<D, F>(self, msg: F) -> Result<T, Report> fn with_context<D, F>(self, msg: F) -> Result<T, Report>
where where
D: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
@ -146,9 +136,9 @@ where
D: Display, D: Display,
E: StdError + 'static, E: StdError + 'static,
{ {
#[cfg(backtrace)] #[cfg(generic_member_access)]
fn backtrace(&self) -> Option<&Backtrace> { fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
self.error.backtrace() self.error.provide(request);
} }
fn source(&self) -> Option<&(dyn StdError + 'static)> { fn source(&self) -> Option<&(dyn StdError + 'static)> {

View File

@ -855,6 +855,11 @@ impl<E> StdError for ErrorImpl<E>
where where
E: StdError, 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)> { fn source(&self) -> Option<&(dyn StdError + 'static)> {
ErrorImpl::<()>::error(self.erase()).source() ErrorImpl::<()>::error(self.erase()).source()
} }

View File

@ -355,7 +355,7 @@
unused_parens, unused_parens,
while_true while_true
)] )]
#![cfg_attr(backtrace, feature(backtrace))] #![cfg_attr(generic_member_access, feature(error_generic_member_access))]
#![cfg_attr(doc_cfg, feature(doc_cfg))] #![cfg_attr(doc_cfg, feature(doc_cfg))]
#![allow( #![allow(
clippy::needless_doctest_main, clippy::needless_doctest_main,
@ -624,6 +624,7 @@ fn capture_handler(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
} }
impl dyn EyreHandler { impl dyn EyreHandler {
/// Check if the handler is of type `T`
pub fn is<T: EyreHandler>(&self) -> bool { pub fn is<T: EyreHandler>(&self) -> bool {
// Get `TypeId` of the type this function is instantiated with. // Get `TypeId` of the type this function is instantiated with.
let t = core::any::TypeId::of::<T>(); let t = core::any::TypeId::of::<T>();
@ -635,6 +636,7 @@ impl dyn EyreHandler {
t == concrete t == concrete
} }
/// Downcast the handler to a concrete type
pub fn downcast_ref<T: EyreHandler>(&self) -> Option<&T> { pub fn downcast_ref<T: EyreHandler>(&self) -> Option<&T> {
if self.is::<T>() { if self.is::<T>() {
unsafe { Some(&*(self as *const dyn EyreHandler as *const T)) } unsafe { Some(&*(self as *const dyn EyreHandler as *const T)) }
@ -643,6 +645,7 @@ impl dyn EyreHandler {
} }
} }
/// Downcast the handler to a concrete type
pub fn downcast_mut<T: EyreHandler>(&mut self) -> Option<&mut T> { pub fn downcast_mut<T: EyreHandler>(&mut self) -> Option<&mut T> {
if self.is::<T>() { if self.is::<T>() {
unsafe { Some(&mut *(self as *mut dyn EyreHandler as *mut T)) } unsafe { Some(&mut *(self as *mut dyn EyreHandler as *mut T)) }
@ -775,6 +778,7 @@ impl DefaultHandler {
#[allow(unused_variables)] #[allow(unused_variables)]
#[cfg_attr(not(feature = "auto-install"), allow(dead_code))] #[cfg_attr(not(feature = "auto-install"), allow(dead_code))]
pub fn default_with(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> { pub fn default_with(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
// Capture the backtrace if the source error did not already capture one
let backtrace = backtrace_if_absent!(error); let backtrace = backtrace_if_absent!(error);
Box::new(Self { Box::new(Self {
@ -834,15 +838,19 @@ impl EyreHandler for DefaultHandler {
} }
} }
#[cfg(backtrace)] #[cfg(generic_member_access)]
{ {
use std::backtrace::BacktraceStatus; 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 let backtrace = self
.backtrace .backtrace
.as_ref() .as_ref()
.or_else(|| error.backtrace()) .or_else(|| std::error::request_ref::<Backtrace>(error))
.expect("backtrace capture failed"); .expect("backtrace capture failed");
if let BacktraceStatus::Captured = backtrace.status() { if let BacktraceStatus::Captured = backtrace.status() {
write!(f, "\n\nStack backtrace:\n{}", backtrace)?; write!(f, "\n\nStack backtrace:\n{}", backtrace)?;
} }
@ -1122,21 +1130,6 @@ pub trait WrapErr<T, E>: context::private::Sealed {
where where
D: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
F: FnOnce() -> D; F: FnOnce() -> D;
/// Compatibility re-export of wrap_err for interop with `anyhow`
#[cfg(feature = "anyhow")]
#[cfg_attr(track_caller, track_caller)]
fn context<D>(self, msg: D) -> Result<T, Report>
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<D, F>(self, f: F) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D;
} }
/// Provides the [`ok_or_eyre`][OptionExt::ok_or_eyre] method for [`Option`]. /// Provides the [`ok_or_eyre`][OptionExt::ok_or_eyre] method for [`Option`].
@ -1194,7 +1187,8 @@ pub trait OptionExt<T>: context::private::Sealed {
M: Debug + Display + Send + Sync + 'static; 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 /// This trait is sealed and cannot be implemented for types outside of
/// `eyre`. /// `eyre`.
@ -1253,19 +1247,6 @@ pub trait ContextCompat<T>: context::private::Sealed {
where where
D: Display + Send + Sync + 'static, D: Display + Send + Sync + 'static,
F: FnOnce() -> D; F: FnOnce() -> D;
/// Compatibility re-export of `context` for porting from `anyhow` to `eyre`
#[cfg_attr(track_caller, track_caller)]
fn wrap_err<D>(self, msg: D) -> Result<T, Report>
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<D, F>(self, f: F) -> Result<T, Report>
where
D: Display + Send + Sync + 'static,
F: FnOnce() -> D;
} }
/// Equivalent to `Ok::<_, eyre::Error>(value)`. /// Equivalent to `Ok::<_, eyre::Error>(value)`.

View File

@ -82,9 +82,9 @@ impl Display for BoxedError {
} }
impl StdError for BoxedError { impl StdError for BoxedError {
#[cfg(backtrace)] #[cfg(generic_member_access)]
fn backtrace(&self) -> Option<&crate::backtrace::Backtrace> { fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
self.0.backtrace() self.0.provide(request);
} }
fn source(&self) -> Option<&(dyn StdError + 'static)> { fn source(&self) -> Option<&(dyn StdError + 'static)> {

View File

@ -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<dyn std::error::Error> = 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::<MyCupcake>(&*err),
Some(&MyCupcake("Blueberry".into()))
);
let bt = std::error::request_ref::<Backtrace>(&*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
);
}

View File

@ -105,7 +105,7 @@ fn test_context() {
Box::new(LocationHandler::new(expected_location)) Box::new(LocationHandler::new(expected_location))
})); }));
use eyre::WrapErr; use eyre::ContextCompat;
let err = read_path("totally_fake_path") let err = read_path("totally_fake_path")
.context("oopsie") .context("oopsie")
.unwrap_err(); .unwrap_err();
@ -122,7 +122,7 @@ fn test_with_context() {
Box::new(LocationHandler::new(expected_location)) Box::new(LocationHandler::new(expected_location))
})); }));
use eyre::WrapErr; use eyre::ContextCompat;
let err = read_path("totally_fake_path") let err = read_path("totally_fake_path")
.with_context(|| "oopsie") .with_context(|| "oopsie")
.unwrap_err(); .unwrap_err();
@ -131,36 +131,6 @@ fn test_with_context() {
println!("{:?}", err); 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::<()>.wrap_err("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::<()>.wrap_err_with(|| "oopsie").unwrap_err();
// should panic if the location isn't in our crate
println!("{:?}", err);
}
#[cfg(feature = "anyhow")] #[cfg(feature = "anyhow")]
#[test] #[test]
fn test_option_compat_context() { fn test_option_compat_context() {

View File

@ -4,7 +4,6 @@ mod drop;
use self::common::maybe_install_handler; use self::common::maybe_install_handler;
use self::drop::{DetectDrop, Flag}; use self::drop::{DetectDrop, Flag};
use eyre::Report; use eyre::Report;
use std::marker::Unpin;
use std::mem; use std::mem;
#[test] #[test]

View File

@ -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.")
}
}