Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Structural conversion from anyhow errors #127

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -15,3 +15,5 @@ rust-version = "1.65.0"
[workspace.dependencies]
indenter = "0.3.0"
once_cell = "1.18.0"
# For use with anyhow-compat
anyhow = { version = "1.0", features = ["backtrace"] }
3 changes: 3 additions & 0 deletions eyre/Cargo.toml
Original file line number Diff line number Diff line change
@@ -16,11 +16,14 @@ rust-version = { workspace = true }
default = ["auto-install", "track-caller"]
auto-install = []
track-caller = []
anyhow-compat = ["anyhow"]
anyhow-compat-backtrace = ["anyhow-compat", "anyhow/backtrace"]

[dependencies]
indenter = { workspace = true }
once_cell = { workspace = true }
pyo3 = { version = "0.20", optional = true, default-features = false }
anyhow = { workspace = true, optional = true }

[dev-dependencies]
futures = { version = "0.3", default-features = false }
4 changes: 3 additions & 1 deletion eyre/examples/custom_handler.rs
Original file line number Diff line number Diff line change
@@ -27,7 +27,9 @@ fn install() -> Result<(), impl Error> {

let hook = Hook { capture_backtrace };

eyre::set_hook(Box::new(move |e| Box::new(hook.make_handler(e))))
eyre::set_hook(Box::new(move |e: &(dyn Error + 'static)| {
Box::new(hook.make_handler(e)) as Box<dyn EyreHandler>
}))
}

struct Hook {
7 changes: 4 additions & 3 deletions eyre/src/backtrace.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
#[cfg(backtrace)]
pub(crate) use std::backtrace::Backtrace;
pub use std::backtrace::Backtrace;

#[cfg(not(backtrace))]
pub(crate) enum Backtrace {}
#[derive(Debug)]
pub enum Backtrace {}

#[cfg(backtrace)]
macro_rules! backtrace_if_absent {
($err:expr) => {
match $err.backtrace() {
Some(_) => None,
None => Some(Backtrace::capture()),
None => Some(Backtrace::capture().into()),
}
};
}
146 changes: 146 additions & 0 deletions eyre/src/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
use crate::{
error::{context_downcast, context_downcast_mut, context_drop_rest, ContextError},
vtable::{
object_boxed, object_downcast, object_downcast_mut, object_drop, object_drop_front,
object_mut, object_ref, ErrorVTable,
},
HandlerBacktraceCompat, HookParams, Report, StdError,
};
use std::fmt::Display;

#[derive(Debug, Default)]
/// Used for incrementally constructing reports
pub struct ReportBuilder {
params: HookParams,
}

impl ReportBuilder {
/// Creates a new report builder with default parameters
pub fn new() -> Self {
Self::default()
}

/// Use the given backtrace for the error
pub fn with_backtrace(mut self, backtrace: impl Into<HandlerBacktraceCompat>) -> Self {
self.params.backtrace = Some(backtrace.into());
self
}

#[cfg_attr(track_caller, track_caller)]
/// Creates a report from the given error message
pub fn from_msg<M>(self, message: M) -> Report
where
M: Display + std::fmt::Debug + Send + Sync + 'static,
{
use crate::wrapper::MessageError;
let error: MessageError<M> = MessageError(message);
let vtable = &ErrorVTable {
object_drop: object_drop::<MessageError<M>>,
object_ref: object_ref::<MessageError<M>>,
object_mut: object_mut::<MessageError<M>>,
object_boxed: object_boxed::<MessageError<M>>,
object_downcast: object_downcast::<M>,
object_downcast_mut: object_downcast_mut::<M>,
object_drop_rest: object_drop_front::<M>,
};

// Safety: MessageError is repr(transparent) so it is okay for the
// vtable to allow casting the MessageError<M> to M.
let handler = Some(crate::capture_handler(&error, self.params));

unsafe { Report::construct(error, vtable, handler) }
}

#[cfg_attr(track_caller, track_caller)]
/// Creates a report from the following error
pub fn from_stderr<E>(self, error: E) -> Report
where
E: StdError + Send + Sync + 'static,
{
let vtable = &ErrorVTable {
object_drop: object_drop::<E>,
object_ref: object_ref::<E>,
object_mut: object_mut::<E>,
object_boxed: object_boxed::<E>,
object_downcast: object_downcast::<E>,
object_downcast_mut: object_downcast_mut::<E>,
object_drop_rest: object_drop_front::<E>,
};

// Safety: passing vtable that operates on the right type E.
let handler = Some(crate::capture_handler(&error, self.params));

unsafe { Report::construct(error, vtable, handler) }
}

#[cfg_attr(track_caller, track_caller)]
/// Creates a report from the following boxed error
pub fn from_boxed(self, error: Box<dyn StdError + Send + Sync>) -> Report {
use crate::wrapper::BoxedError;
let error = BoxedError(error);
let handler = Some(crate::capture_handler(&error, self.params));

let vtable = &ErrorVTable {
object_drop: object_drop::<BoxedError>,
object_ref: object_ref::<BoxedError>,
object_mut: object_mut::<BoxedError>,
object_boxed: object_boxed::<BoxedError>,
object_downcast: object_downcast::<Box<dyn StdError + Send + Sync>>,
object_downcast_mut: object_downcast_mut::<Box<dyn StdError + Send + Sync>>,
object_drop_rest: object_drop_front::<Box<dyn StdError + Send + Sync>>,
};

// Safety: BoxedError is repr(transparent) so it is okay for the vtable
// to allow casting to Box<dyn StdError + Send + Sync>.
unsafe { Report::construct(error, vtable, handler) }
}

#[cfg_attr(track_caller, track_caller)]
/// Wraps a source error with a message
pub fn wrap_with_msg<D, E>(self, msg: D, error: E) -> Report
where
D: Display + Send + Sync + 'static,
E: StdError + Send + Sync + 'static,
{
let error: ContextError<D, E> = ContextError { msg, error };

let vtable = &ErrorVTable {
object_drop: object_drop::<ContextError<D, E>>,
object_ref: object_ref::<ContextError<D, E>>,
object_mut: object_mut::<ContextError<D, E>>,
object_boxed: object_boxed::<ContextError<D, E>>,
object_downcast: context_downcast::<D, E>,
object_downcast_mut: context_downcast_mut::<D, E>,
object_drop_rest: context_drop_rest::<D, E>,
};

// Safety: passing vtable that operates on the right type.
let handler = Some(crate::capture_handler(&error, self.params));

unsafe { Report::construct(error, vtable, handler) }
}

#[cfg_attr(track_caller, track_caller)]
pub(crate) fn from_display<M>(self, message: M) -> Report
where
M: Display + Send + Sync + 'static,
{
use crate::wrapper::{DisplayError, NoneError};
let error: DisplayError<M> = DisplayError(message);
let vtable = &ErrorVTable {
object_drop: object_drop::<DisplayError<M>>,
object_ref: object_ref::<DisplayError<M>>,
object_mut: object_mut::<DisplayError<M>>,
object_boxed: object_boxed::<DisplayError<M>>,
object_downcast: object_downcast::<M>,
object_downcast_mut: object_downcast_mut::<M>,
object_drop_rest: object_drop_front::<M>,
};

// Safety: DisplayError is repr(transparent) so it is okay for the
// vtable to allow casting the DisplayError<M> to M.
let handler = Some(crate::capture_handler(&NoneError, Default::default()));

unsafe { Report::construct(error, vtable, handler) }
}
}
48 changes: 48 additions & 0 deletions eyre/src/compat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use crate::{builder::ReportBuilder, Report};

/// Convert this result into an eyre [`Report`](crate::Report) result
///
/// This trait can also be used to provide conversions to eyre in `no-std` environments where
/// [`Error`](std::error::Error) is not yet available.
pub trait IntoEyre<T> {
/// Convert this result into an eyre [`Report`](crate::Report) result
fn into_eyre(self) -> crate::Result<T>;
}

/// See: [`IntoEyre`]
/// This is for crate authors to implement on their custom error types. Implementing this for your
/// Error type automatically implements `into_eyre` for `Result<T, E>`
pub trait IntoEyreReport {
/// Convert this error into an eyre [`Report`](crate::Report)
#[track_caller]
fn into_eyre_report(self) -> Report;
}

impl<T, E> IntoEyre<T> for Result<T, E>
where
E: IntoEyreReport,
{
#[track_caller]
fn into_eyre(self) -> crate::Result<T> {
// Use a manual match to keep backtrace
match self {
Ok(v) => Ok(v),
Err(err) => Err(err.into_eyre_report()),
}
}
}

#[cfg(feature = "anyhow-compat")]
impl IntoEyreReport for anyhow::Error {
#[track_caller]
fn into_eyre_report(self) -> Report
where
Self: Sized,
{
let report = ReportBuilder::default()
.with_backtrace(self.backtrace())
.from_boxed(self.into());

report
}
}
3 changes: 2 additions & 1 deletion eyre/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::error::{ContextError, ErrorImpl};
use crate::error::ContextError;
use crate::vtable::ErrorImpl;
use crate::{ContextCompat, Report, StdError, WrapErr};
use core::fmt::{self, Debug, Display, Write};

329 changes: 19 additions & 310 deletions eyre/src/error.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion eyre/src/fmt.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{error::ErrorImpl, ptr::RefPtr};
use crate::{ptr::RefPtr, vtable::ErrorImpl};
use core::fmt;

impl ErrorImpl<()> {
4 changes: 2 additions & 2 deletions eyre/src/kind.rs
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@
// let error = $msg;
// (&error).eyre_kind().new(error)

use crate::Report;
use crate::{builder::ReportBuilder, Report};
use core::fmt::{Debug, Display};

use crate::StdError;
@@ -106,6 +106,6 @@ impl BoxedKind for Box<dyn StdError + Send + Sync> {}
impl Boxed {
#[cfg_attr(track_caller, track_caller)]
pub fn new(self, error: Box<dyn StdError + Send + Sync>) -> Report {
Report::from_boxed(error)
ReportBuilder::default().from_boxed(error)
}
}
82 changes: 78 additions & 4 deletions eyre/src/lib.rs
Original file line number Diff line number Diff line change
@@ -367,8 +367,13 @@ mod option;
mod ptr;
mod wrapper;

use crate::backtrace::Backtrace;
use crate::error::ErrorImpl;
/// Incrementally construct reports
pub mod builder;
/// Compatibility traits for conversion between different error providers in a structural
/// manner.
pub mod compat;
mod vtable;

use core::fmt::{Debug, Display};

use std::error::Error as StdError;
@@ -378,6 +383,7 @@ pub use eyre as format_err;
pub use eyre as anyhow;
use once_cell::sync::OnceCell;
use ptr::OwnedPtr;
use vtable::ErrorImpl;
#[doc(hidden)]
pub use DefaultHandler as DefaultContext;
#[doc(hidden)]
@@ -467,6 +473,61 @@ pub struct Report {
inner: OwnedPtr<ErrorImpl<()>>,
}

/// Provide an explicit backtrace for an error
pub enum HandlerBacktraceCompat {
/// std::backtrace::Backtrace
StdBacktrace(std::backtrace::Backtrace),
/// stable [`backtrace`](::backtrace)
Backtrace(backtrace::Backtrace),
/// An opaque backtrack
Display(Box<dyn Display + Send + Sync + 'static>),
}

impl From<Box<dyn Display + Send + Sync + 'static>> for HandlerBacktraceCompat {
fn from(v: Box<dyn Display + Send + Sync + 'static>) -> Self {
Self::Display(v)
}
}

impl<T> From<&T> for HandlerBacktraceCompat
where
T: Display,
{
fn from(v: &T) -> Self {
Self::Display(Box::new(v.to_string()))
}
}

impl From<backtrace::Backtrace> for HandlerBacktraceCompat {
fn from(v: backtrace::Backtrace) -> Self {
Self::Backtrace(v)
}
}

impl From<std::backtrace::Backtrace> for HandlerBacktraceCompat {
fn from(v: std::backtrace::Backtrace) -> Self {
Self::StdBacktrace(v)
}
}

impl std::fmt::Debug for HandlerBacktraceCompat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HandlerBacktraceCompat::StdBacktrace(_) => f.debug_tuple("StdBacktrace").finish(),
HandlerBacktraceCompat::Backtrace(_) => f.debug_tuple("Backtrace").finish(),
HandlerBacktraceCompat::Display(_) => f.debug_tuple("Display").finish(),
}
}
}

#[derive(Debug, Default)]
#[non_exhaustive]
/// Used for configuring error generation
pub struct HookParams {
/// An explicit backtrace to attach to the error, if any
pub backtrace: Option<HandlerBacktraceCompat>,
}

type ErrorHook =
Box<dyn Fn(&(dyn StdError + 'static)) -> Box<dyn EyreHandler> + Sync + Send + 'static>;

@@ -586,7 +647,7 @@ pub fn set_hook(hook: ErrorHook) -> Result<(), InstallError> {

#[cfg_attr(track_caller, track_caller)]
#[cfg_attr(not(track_caller), allow(unused_mut))]
fn capture_handler(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {
fn capture_handler(error: &(dyn StdError + 'static), params: HookParams) -> Box<dyn EyreHandler> {
#[cfg(not(feature = "auto-install"))]
let hook = HOOK
.get()
@@ -600,6 +661,10 @@ fn capture_handler(error: &(dyn StdError + 'static)) -> Box<dyn EyreHandler> {

let mut handler = hook(error);

if let Some(backtrace) = params.backtrace {
handler.set_backtrace(backtrace);
}

#[cfg(track_caller)]
{
handler.track_caller(std::panic::Location::caller())
@@ -720,6 +785,10 @@ pub trait EyreHandler: 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>) {}

/// Provide an explicit backtrace for this error
#[allow(unused_variables)]
fn set_backtrace(&mut self, backtrace: HandlerBacktraceCompat) {}
}

/// The default provided error report handler for `eyre::Report`.
@@ -728,7 +797,7 @@ pub trait EyreHandler: core::any::Any + Send + Sync {
/// error did not already capture one.
#[allow(dead_code)]
pub struct DefaultHandler {
backtrace: Option<Backtrace>,
backtrace: Option<HandlerBacktraceCompat>,
#[cfg(track_caller)]
location: Option<&'static std::panic::Location<'static>>,
}
@@ -831,6 +900,7 @@ impl EyreHandler for DefaultHandler {
.as_ref()
.or_else(|| error.backtrace())
.expect("backtrace capture failed");

if let BacktraceStatus::Captured = backtrace.status() {
write!(f, "\n\nStack backtrace:\n{}", backtrace)?;
}
@@ -843,6 +913,10 @@ impl EyreHandler for DefaultHandler {
fn track_caller(&mut self, location: &'static std::panic::Location<'static>) {
self.location = Some(location);
}

fn set_backtrace(&mut self, backtrace: HandlerBacktraceCompat) {
self.backtrace = Some(backtrace);
}
}

/// Iterator of a chain of source errors.
230 changes: 230 additions & 0 deletions eyre/src/vtable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
use std::{
any::TypeId,
fmt::{self, Debug, Display},
mem::{self, ManuallyDrop},
ptr::NonNull,
};

use crate::{
ptr::{MutPtr, OwnedPtr, RefPtr},
Chain, EyreHandler, Report, StdError,
};

pub(crate) struct ErrorVTable {
pub(crate) object_drop: unsafe fn(OwnedPtr<ErrorImpl<()>>),
pub(crate) object_ref:
unsafe fn(RefPtr<'_, ErrorImpl<()>>) -> &(dyn StdError + Send + Sync + 'static),
pub(crate) object_mut:
unsafe fn(MutPtr<'_, ErrorImpl<()>>) -> &mut (dyn StdError + Send + Sync + 'static),
#[allow(clippy::type_complexity)]
pub(crate) object_boxed:
unsafe fn(OwnedPtr<ErrorImpl<()>>) -> Box<dyn StdError + Send + Sync + 'static>,
pub(crate) object_downcast: unsafe fn(RefPtr<'_, ErrorImpl<()>>, TypeId) -> Option<NonNull<()>>,
pub(crate) object_downcast_mut:
unsafe fn(MutPtr<'_, ErrorImpl<()>>, TypeId) -> Option<NonNull<()>>,
pub(crate) object_drop_rest: unsafe fn(OwnedPtr<ErrorImpl<()>>, TypeId),
}

// repr C to ensure that E remains in the final position.
#[repr(C)]
pub(crate) struct ErrorImpl<E = ()> {
pub(crate) header: ErrorHeader,
// NOTE: Don't use directly. Use only through vtable. Erased type may have
// different alignment.
pub(crate) _object: E,
}

#[repr(C)]
pub(crate) struct ErrorHeader {
pub(crate) vtable: &'static ErrorVTable,
pub(crate) handler: Option<Box<dyn EyreHandler>>,
}
// Reads the header out of `p`. This is the same as `p.as_ref().header`, but
// avoids converting `p` into a reference of a shrunk provenance with a type different than the
// allocation.
pub(crate) fn header(p: RefPtr<'_, ErrorImpl<()>>) -> &'_ ErrorHeader {
// Safety: `ErrorHeader` is the first field of repr(C) `ErrorImpl`
unsafe { p.cast().as_ref() }
}

pub(crate) fn header_mut(p: MutPtr<'_, ErrorImpl<()>>) -> &mut ErrorHeader {
// Safety: `ErrorHeader` is the first field of repr(C) `ErrorImpl`
unsafe { p.cast().into_mut() }
}

impl<E> ErrorImpl<E> {
/// Returns a type erased Error
fn erase(&self) -> RefPtr<'_, ErrorImpl<()>> {
// Erase the concrete type of E but preserve the vtable in self.vtable
// for manipulating the resulting thin pointer. This is analogous to an
// unsize coersion.
RefPtr::new(self).cast()
}
}

impl ErrorImpl<()> {
pub(crate) fn error(this: RefPtr<'_, Self>) -> &(dyn StdError + Send + Sync + 'static) {
// Use vtable to attach E's native StdError vtable for the right
// original type E.
unsafe { (header(this).vtable.object_ref)(this) }
}

pub(crate) fn error_mut(this: MutPtr<'_, Self>) -> &mut (dyn StdError + Send + Sync + 'static) {
// Use vtable to attach E's native StdError vtable for the right
// original type E.
unsafe { (header_mut(this).vtable.object_mut)(this) }
}

pub(crate) fn chain(this: RefPtr<'_, Self>) -> Chain<'_> {
Chain::new(Self::error(this))
}

pub(crate) fn header(this: RefPtr<'_, ErrorImpl>) -> &ErrorHeader {
header(this)
}
}

impl<E> StdError for ErrorImpl<E>
where
E: StdError,
{
fn source(&self) -> Option<&(dyn StdError + 'static)> {
ErrorImpl::<()>::error(self.erase()).source()
}
}

impl<E> Debug for ErrorImpl<E>
where
E: Debug,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
ErrorImpl::debug(self.erase(), formatter)
}
}

impl<E> Display for ErrorImpl<E>
where
E: Display,
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(ErrorImpl::error(self.erase()), formatter)
}
}

impl From<Report> for Box<dyn StdError + Send + Sync + 'static> {
fn from(error: Report) -> Self {
let outer = ManuallyDrop::new(error);
unsafe {
// Read Box<ErrorImpl<()>> from error. Can't move it out because
// Report has a Drop impl which we want to not run.
// Use vtable to attach ErrorImpl<E>'s native StdError vtable for
// the right original type E.
(header(outer.inner.as_ref()).vtable.object_boxed)(outer.inner)
}
}
}

/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_drop<E>(e: OwnedPtr<ErrorImpl<()>>) {
// Cast to a context type and drop the Box allocation.
let unerased = unsafe { e.cast::<ErrorImpl<E>>().into_box() };
drop(unerased);
}

/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_drop_front<E>(e: OwnedPtr<ErrorImpl<()>>, target: TypeId) {
// Drop the fields of ErrorImpl other than E as well as the Box allocation,
// without dropping E itself. This is used by downcast after doing a
// ptr::read to take ownership of the E.
let _ = target;
// Note: This must not use `mem::transmute` because it tries to reborrow the `Unique`
// contained in `Box`, which must not be done. In practice this probably won't make any
// difference by now, but technically it's unsound.
// see: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.m
let unerased = unsafe { e.cast::<ErrorImpl<E>>().into_box() };

mem::forget(unerased._object)
}

/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_ref<E>(
e: RefPtr<'_, ErrorImpl<()>>,
) -> &(dyn StdError + Send + Sync + 'static)
where
E: StdError + Send + Sync + 'static,
{
// Attach E's native StdError vtable onto a pointer to self._object.
&unsafe { e.cast::<ErrorImpl<E>>().as_ref() }._object
}

/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_mut<E>(
e: MutPtr<'_, ErrorImpl<()>>,
) -> &mut (dyn StdError + Send + Sync + 'static)
where
E: StdError + Send + Sync + 'static,
{
// Attach E's native StdError vtable onto a pointer to self._object.
&mut unsafe { e.cast::<ErrorImpl<E>>().into_mut() }._object
}

/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_boxed<E>(
e: OwnedPtr<ErrorImpl<()>>,
) -> Box<dyn StdError + Send + Sync + 'static>
where
E: StdError + Send + Sync + 'static,
{
// Attach ErrorImpl<E>'s native StdError vtable. The StdError impl is below.
unsafe { e.cast::<ErrorImpl<E>>().into_box() }
}

/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_downcast<E>(
e: RefPtr<'_, ErrorImpl<()>>,
target: TypeId,
) -> Option<NonNull<()>>
where
E: 'static,
{
if TypeId::of::<E>() == target {
// Caller is looking for an E pointer and e is ErrorImpl<E>, take a
// pointer to its E field.
let unerased = unsafe { e.cast::<ErrorImpl<E>>().as_ref() };
Some(NonNull::from(&(unerased._object)).cast::<()>())
} else {
None
}
}

/// # Safety
///
/// Requires layout of *e to match ErrorImpl<E>.
pub(crate) unsafe fn object_downcast_mut<E>(
e: MutPtr<'_, ErrorImpl<()>>,
target: TypeId,
) -> Option<NonNull<()>>
where
E: 'static,
{
if TypeId::of::<E>() == target {
// Caller is looking for an E pointer and e is ErrorImpl<E>, take a
// pointer to its E field.
let unerased = unsafe { e.cast::<ErrorImpl<E>>().into_mut() };
Some(NonNull::from(&mut (unerased._object)).cast::<()>())
} else {
None
}
}
63 changes: 63 additions & 0 deletions eyre/tests/test_anyhow_compat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::fmt::Display;

use eyre::{compat::IntoEyre, Report};

#[derive(Debug)]
struct RootError;

impl Display for RootError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "RootError")
}
}

impl std::error::Error for RootError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}

#[cfg(feature = "anyhow-compat")]
fn this_function_fails() -> anyhow::Result<()> {
use anyhow::Context;

Err(RootError).context("Ouch!").context("Anyhow context A")
}

#[cfg(feature = "anyhow-compat")]
fn bubble() -> eyre::Result<()> {
use anyhow::Context;
use eyre::WrapErr;
let err = this_function_fails().context("Anyhow context B");

err.into_eyre().wrap_err("Eyre context A")?;

Ok(())
}

#[cfg(feature = "anyhow-compat")]
#[test]
fn anyhow_conversion() {
use eyre::WrapErr;
let error: Report = bubble().wrap_err("Eyre context B").unwrap_err();

eprintln!("Error: {:?}", error);

let chain = error.chain().map(ToString::to_string).collect::<Vec<_>>();
assert_eq!(
chain,
[
"Eyre context B",
"Eyre context A",
// Anyhow context
"Anyhow context B",
"Anyhow context A",
// Anyhow error
"Ouch!",
// Original concrete error, shows up in chain too
"RootError"
]
);

// let error = Report::msg("A").wrap_err("B").wrap_err("C");
}
22 changes: 11 additions & 11 deletions eyre/tests/test_location.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::panic::Location;
use std::{error::Error, panic::Location};

use eyre::WrapErr;

@@ -43,7 +43,7 @@ impl eyre::EyreHandler for LocationHandler {
fn test_wrap_err() {
let _ = eyre::set_hook(Box::new(|_e| {
let expected_location = file!();
Box::new(LocationHandler::new(expected_location))
Box::new(LocationHandler::new(expected_location)) as _
}));

let err = read_path("totally_fake_path")
@@ -70,9 +70,9 @@ fn read_path(_path: &str) -> Result<String, std::io::Error> {

#[test]
fn test_wrap_err_with() {
let _ = eyre::set_hook(Box::new(|_e| {
let _ = eyre::set_hook(Box::new(|_e: &(dyn Error + 'static)| {
let expected_location = file!();
Box::new(LocationHandler::new(expected_location))
Box::new(LocationHandler::new(expected_location)) as _
}));

let err = read_path("totally_fake_path")
@@ -85,9 +85,9 @@ fn test_wrap_err_with() {

#[test]
fn test_context() {
let _ = eyre::set_hook(Box::new(|_e| {
let _ = eyre::set_hook(Box::new(|_e: &(dyn Error + 'static)| {
let expected_location = file!();
Box::new(LocationHandler::new(expected_location))
Box::new(LocationHandler::new(expected_location)) as _
}));

let err = read_path("totally_fake_path")
@@ -102,7 +102,7 @@ fn test_context() {
fn test_with_context() {
let _ = eyre::set_hook(Box::new(|_e| {
let expected_location = file!();
Box::new(LocationHandler::new(expected_location))
Box::new(LocationHandler::new(expected_location)) as _
}));

let err = read_path("totally_fake_path")
@@ -117,7 +117,7 @@ fn test_with_context() {
fn test_option_compat_wrap_err() {
let _ = eyre::set_hook(Box::new(|_e| {
let expected_location = file!();
Box::new(LocationHandler::new(expected_location))
Box::new(LocationHandler::new(expected_location)) as _
}));

use eyre::ContextCompat;
@@ -131,7 +131,7 @@ fn test_option_compat_wrap_err() {
fn test_option_compat_wrap_err_with() {
let _ = eyre::set_hook(Box::new(|_e| {
let expected_location = file!();
Box::new(LocationHandler::new(expected_location))
Box::new(LocationHandler::new(expected_location)) as _
}));

use eyre::ContextCompat;
@@ -145,7 +145,7 @@ fn test_option_compat_wrap_err_with() {
fn test_option_compat_context() {
let _ = eyre::set_hook(Box::new(|_e| {
let expected_location = file!();
Box::new(LocationHandler::new(expected_location))
Box::new(LocationHandler::new(expected_location)) as _
}));

use eyre::ContextCompat;
@@ -159,7 +159,7 @@ fn test_option_compat_context() {
fn test_option_compat_with_context() {
let _ = eyre::set_hook(Box::new(|_e| {
let expected_location = file!();
Box::new(LocationHandler::new(expected_location))
Box::new(LocationHandler::new(expected_location)) as _
}));

use eyre::ContextCompat;