Skip to content
10 changes: 4 additions & 6 deletions gix-error/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ impl Error {
/// Note that if there is nothing but this error, i.e. no source or children, this error is returned.
pub fn probable_cause(&self) -> &(dyn std::error::Error + 'static) {
let root = self.inner.as_frame();
root.probable_cause().unwrap_or(root).as_error()
root.probable_cause().unwrap_or(root).error()
}

/// Return an iterator over all errors in the tree in breadth-first order, starting with this one.
Expand Down Expand Up @@ -42,7 +42,7 @@ impl Error {
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.inner {
Inner::ExnAsError(err) => std::fmt::Display::fmt(err.as_error(), f),
Inner::ExnAsError(err) => std::fmt::Display::fmt(err.error(), f),
Inner::Exn(frame) => std::fmt::Display::fmt(frame, f),
}
}
Expand All @@ -51,7 +51,7 @@ impl std::fmt::Display for Error {
impl std::fmt::Debug for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.inner {
Inner::ExnAsError(err) => std::fmt::Debug::fmt(err.as_error(), f),
Inner::ExnAsError(err) => std::fmt::Debug::fmt(err.error(), f),
Inner::Exn(frame) => std::fmt::Debug::fmt(frame, f),
}
}
Expand All @@ -61,9 +61,7 @@ impl std::error::Error for Error {
/// Return the first source of an [Exn] error, or the source of a boxed error.
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match &self.inner {
Inner::ExnAsError(frame) | Inner::Exn(frame) => {
frame.children().first().map(exn::Frame::as_error_no_send_sync)
}
Inner::ExnAsError(frame) | Inner::Exn(frame) => frame.children().first().map(|f| f.error() as _),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion gix-error/src/exn/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub trait ErrorExt: std::error::Error + Send + Sync + 'static {
I: IntoIterator,
I::Item: Into<Exn<T>>,
{
Exn::from_iter(sources, self)
Exn::raise_all(sources, self)
}
}

Expand Down
79 changes: 35 additions & 44 deletions gix-error/src/exn/impls.rs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now I know gix-error has to wrap once more the inner error into Untyped because downcast must have the type param matched. In this case, it seems to be inevitable.

Copy link
Member

@Byron Byron Jan 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In theory, the into_inner() and into_box() can be removed as there is always gix_error::Error to hold these types.

Let's leave it as is for now, and I will consider removing them to avoid that double-box.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@

use std::error::Error;
use std::fmt;
use std::fmt::Formatter;
use std::marker::PhantomData;
use std::ops::Deref;
use std::panic::Location;
Expand Down Expand Up @@ -69,10 +68,8 @@ impl<E: Error + Send + Sync + 'static> Exn<E> {
}

/// Create a new exception with the given error and children.
///
/// It's no error if `children` is empty.
#[track_caller]
pub fn from_iter<T, I>(children: I, err: E) -> Self
pub fn raise_all<T, I>(children: I, err: E) -> Self
where
T: Error + Send + Sync + 'static,
I: IntoIterator,
Expand All @@ -94,9 +91,17 @@ impl<E: Error + Send + Sync + 'static> Exn<E> {
new_exn
}

/// Use the current exception as the head of a chain, adding `err` to its children.
#[track_caller]
pub fn chain<T: Error + Send + Sync + 'static>(mut self, err: impl Into<Exn<T>>) -> Exn<E> {
let err = err.into();
self.frame.children.push(*err.frame);
self
}

/// Use the current exception the head of a chain, adding `errors` to its children.
#[track_caller]
pub fn chain_iter<T, I>(mut self, errors: I) -> Exn<E>
pub fn chain_all<T, I>(mut self, errors: I) -> Exn<E>
where
T: Error + Send + Sync + 'static,
I: IntoIterator,
Expand All @@ -116,14 +121,6 @@ impl<E: Error + Send + Sync + 'static> Exn<E> {
self.frame.children.drain(..).map(Exn::from)
}

/// Use the current exception the head of a chain, adding all `err` to its children.
#[track_caller]
pub fn chain<T: Error + Send + Sync + 'static>(mut self, err: impl Into<Exn<T>>) -> Exn<E> {
let err = err.into();
self.frame.children.push(*err.frame);
self
}

/// Erase the type of this instance and turn it into a bare `Exn`.
pub fn erased(self) -> Exn {
let untyped_frame = {
Expand All @@ -148,7 +145,7 @@ impl<E: Error + Send + Sync + 'static> Exn<E> {
}

/// Return the current exception.
pub fn as_error(&self) -> &E {
pub fn error(&self) -> &E {
self.frame
.error
.downcast_ref()
Expand All @@ -175,19 +172,21 @@ impl<E: Error + Send + Sync + 'static> Exn<E> {
}

/// Turn ourselves into a top-level [Error] that implements [`std::error::Error`].
///
/// [Error]: crate::Error
pub fn into_error(self) -> crate::Error {
self.into()
}

/// Return the underlying exception frame.
pub fn as_frame(&self) -> &Frame {
pub fn frame(&self) -> &Frame {
&self.frame
}

/// Iterate over all frames in breadth-first order. The first frame is this instance,
/// followed by all of its children.
pub fn iter(&self) -> impl Iterator<Item = &Frame> {
self.as_frame().iter()
self.frame().iter()
}

/// Iterate over all frames and find one that downcasts into error of type `T`.
Expand All @@ -204,18 +203,18 @@ where
type Target = E;

fn deref(&self) -> &Self::Target {
self.as_error()
self.error()
}
}

impl<E: Error + Send + Sync + 'static> fmt::Debug for Exn<E> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write_frame_recursive(f, self.as_frame(), "", ErrorMode::Display, TreeMode::Linearize)
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_frame_recursive(f, self.frame(), "", ErrorMode::Display, TreeMode::Linearize)
}
}

impl fmt::Debug for Frame {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write_frame_recursive(f, self, "", ErrorMode::Display, TreeMode::Linearize)
}
}
Expand All @@ -233,16 +232,16 @@ enum TreeMode {
}

fn write_frame_recursive(
f: &mut Formatter<'_>,
f: &mut fmt::Formatter<'_>,
frame: &Frame,
prefix: &str,
err_mode: ErrorMode,
tree_mode: TreeMode,
) -> fmt::Result {
match err_mode {
ErrorMode::Display => fmt::Display::fmt(frame.as_error(), f),
ErrorMode::Display => fmt::Display::fmt(frame.error(), f),
ErrorMode::Debug => {
write!(f, "{:?}", frame.as_error())
write!(f, "{:?}", frame.error())
}
}?;
if !f.alternate() {
Expand All @@ -257,9 +256,8 @@ fn write_frame_recursive(
write!(f, "\n{prefix}└─ ")?;

let child_child_len = child.children().len();
let may_linerarize_chain =
matches!(tree_mode, TreeMode::Linearize) && children_len == 1 && child_child_len == 1;
if may_linerarize_chain {
let may_linearize_chain = matches!(tree_mode, TreeMode::Linearize) && children_len == 1 && child_child_len == 1;
if may_linearize_chain {
write_frame_recursive(f, child, prefix, err_mode, tree_mode)?;
} else if cidx < children_len - 1 {
write_frame_recursive(f, child, &format!("{prefix}| "), err_mode, tree_mode)?;
Expand All @@ -271,14 +269,14 @@ fn write_frame_recursive(
Ok(())
}

fn write_location(f: &mut Formatter<'_>, exn: &Frame) -> fmt::Result {
fn write_location(f: &mut fmt::Formatter<'_>, exn: &Frame) -> fmt::Result {
let location = exn.location();
write!(f, ", at {}:{}:{}", location.file(), location.line(), location.column())
}

impl<E: Error + Send + Sync + 'static> fmt::Display for Exn<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
std::fmt::Display::fmt(&self.frame, f)
fmt::Display::fmt(&self.frame, f)
}
}

Expand All @@ -288,7 +286,7 @@ impl fmt::Display for Frame {
// Avoid printing alternate versions of the debug info, keep it in one line, also print the tree.
write_frame_recursive(f, self, "", ErrorMode::Debug, TreeMode::Verbatim)
} else {
fmt::Display::fmt(self.as_error(), f)
fmt::Display::fmt(self.error(), f)
}
}
}
Expand All @@ -305,7 +303,7 @@ pub struct Frame {

impl Frame {
/// Return the error as a reference to [`Error`].
pub fn as_error(&self) -> &(dyn Error + Send + Sync + 'static) {
pub fn error(&self) -> &(dyn Error + Send + Sync + 'static) {
&*self.error
}

Expand All @@ -325,13 +323,6 @@ impl Frame {
}
}

impl Frame {
/// Return the error as a reference to [`Error`].
pub(crate) fn as_error_no_send_sync(&self) -> &(dyn Error + 'static) {
&*self.error
}
}

/// Navigation
impl Frame {
/// Find the best possible cause:
Expand Down Expand Up @@ -451,14 +442,14 @@ impl From<Frame> for Exn {
pub struct Untyped(Box<dyn Error + Send + Sync + 'static>);

impl fmt::Display for Untyped {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
std::fmt::Display::fmt(&self.0, f)
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}

impl fmt::Debug for Untyped {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
std::fmt::Debug::fmt(&self.0, f)
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}

Expand All @@ -468,14 +459,14 @@ impl Error for Untyped {}
pub struct Something;

impl fmt::Display for Something {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Something went wrong")
}
}

impl fmt::Debug for Something {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
std::fmt::Display::fmt(&self, f)
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self, f)
}
}

Expand Down
4 changes: 2 additions & 2 deletions gix-error/src/exn/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ pub use impls::{Frame, Something, Untyped};

mod macros;

/// An exception type that can hold an [error tree](Exn::from_iter) and the call site.
/// An exception type that can hold an [error tree](Exn::raise_all) and the call site.
///
/// While an error chain, a list, is automatically created when [raise](Exn::raise)
/// and friends are invoked, one can also use [`Exn::from_iter`] to create an error
/// and friends are invoked, one can also use [`Exn::raise_all`] to create an error
/// that has multiple causes.
///
/// # Warning: `source()` information is stringified and type-erased
Expand Down
4 changes: 2 additions & 2 deletions gix-error/tests/error/exn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ fn raise_iter() {
└─ E4-3, at gix-error/tests/error/exn.rs:123:47
");

let e = e.chain_iter((1..3).map(|idx| message!("SE{}", idx)));
let e = e.chain_all((1..3).map(|idx| message!("SE{}", idx)));
insta::assert_debug_snapshot!(e, @r"
Top
|
Expand Down Expand Up @@ -364,7 +364,7 @@ fn error_tree() {
|
└─ E7, at gix-error/tests/error/main.rs:22:30
");
insta::assert_debug_snapshot!(err.as_frame().iter().map(ToString::to_string).collect::<Vec<_>>(), @r#"
insta::assert_debug_snapshot!(err.frame().iter().map(ToString::to_string).collect::<Vec<_>>(), @r#"
[
"E6",
"E5",
Expand Down
4 changes: 2 additions & 2 deletions gix-error/tests/error/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ mod utils {
let e11 = Error("E11").raise();
let e12 = e11.raise(Error("E12"));

let e5 = Exn::from_iter([e3, e10, e12], Error("E5"));
let e5 = Exn::raise_all([e3, e10, e12], Error("E5"));

let e2 = Error("E2").raise();
let e4 = e2.raise(Error("E4"));

let e7 = Error("E7").raise();
let e8 = e7.raise(Error("E8"));

Exn::from_iter([e5, e4, e8], Error("E6"))
Exn::raise_all([e5, e4, e8], Error("E6"))
}

#[derive(Debug)]
Expand Down
4 changes: 2 additions & 2 deletions gix/src/revision/spec/parse/delegate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ impl<'repo> Delegate<'repo> {
match (ambiguous_errors.pop(), ambiguous_errors.pop()) {
(Some(one), None) => Some(one),
(Some(one), Some(two)) => {
Some(Exn::from_iter([one, two], message!("Both objects were ambiguous")).erased())
Some(Exn::raise_all([one, two], message!("Both objects were ambiguous")).erased())
}
_ => (!delayed_errors.is_empty()).then(|| {
if delayed_errors.len() == 1 {
delayed_errors.pop().expect("it's exactly one")
} else {
Exn::from_iter(delayed_errors, message!("one or more delayed errors")).erased()
Exn::raise_all(delayed_errors, message!("one or more delayed errors")).erased()
}
}),
}
Expand Down
2 changes: 1 addition & 1 deletion gix/src/revision/spec/parse/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl<'repo> Spec<'repo> {
Err(mut err) => {
if let Some(delegate_err) = delegate.into_delayed_errors() {
let sources: Vec<_> = err.drain_children().collect();
Err(err.chain(delegate_err.chain_iter(sources)).into_error())
Err(err.chain(delegate_err.chain_all(sources)).into_error())
} else {
Err(err.into_error())
}
Expand Down
Loading