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

Add std::error::Report type #91938

Merged
merged 13 commits into from
Jan 14, 2022
211 changes: 207 additions & 4 deletions library/std/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use crate::backtrace::Backtrace;
use crate::borrow::Cow;
use crate::cell;
use crate::char;
use crate::fmt::{self, Debug, Display};
use crate::fmt::{self, Debug, Display, Write};
use crate::mem::transmute;
use crate::num;
use crate::str;
Expand Down Expand Up @@ -63,7 +63,7 @@ pub trait Error: Debug + Display {
///
/// #[derive(Debug)]
/// struct SuperError {
/// side: SuperErrorSideKick,
/// source: SuperErrorSideKick,
/// }
///
/// impl fmt::Display for SuperError {
Expand All @@ -74,7 +74,7 @@ pub trait Error: Debug + Display {
///
/// impl Error for SuperError {
/// fn source(&self) -> Option<&(dyn Error + 'static)> {
/// Some(&self.side)
/// Some(&self.source)
/// }
/// }
///
Expand All @@ -90,7 +90,7 @@ pub trait Error: Debug + Display {
/// impl Error for SuperErrorSideKick {}
///
/// fn get_super_error() -> Result<(), SuperError> {
/// Err(SuperError { side: SuperErrorSideKick })
/// Err(SuperError { source: SuperErrorSideKick })
/// }
///
/// fn main() {
Expand Down Expand Up @@ -807,3 +807,206 @@ impl dyn Error + Send + Sync {
})
}
}

/// An error reporter that exposes the entire error chain for printing.
/// It also exposes options for formatting the error chain, either entirely on a single line,
/// or in multi-line format with each cause in the error chain on a new line.
/// `Report` only requires that the wrapped error implements `Error`. It doesn't require that the
/// wrapped error be `Send`, `Sync`, or `'static`.
///
/// # Examples
///
/// ```rust
/// #![feature(error_reporter)]
/// #![feature(negative_impls)]
yaahc marked this conversation as resolved.
Show resolved Hide resolved
///
/// use std::error::{Error, Report};
/// use std::fmt;
///
/// #[derive(Debug)]
/// struct SuperError<'a> {
/// side: &'a str,
yaahc marked this conversation as resolved.
Show resolved Hide resolved
/// }
///
/// impl<'a> fmt::Display for SuperError<'a> {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "SuperError is here: {}", self.side)
/// }
/// }
///
/// impl<'a> Error for SuperError<'a> {}
///
/// fn main() {
/// let msg = String::from("Huzzah!");
/// let error = SuperError { side: &msg };
/// let report = Report::new(&error).pretty(true);
///
/// println!("{}", report);
/// }
/// ```

#[unstable(feature = "error_reporter", issue = "90172")]
pub struct Report<E> {
/// The error being reported.
error: E,
/// Whether a backtrace should be included as part of the report.
show_backtrace: bool,
/// Whether the report should be pretty-printed.
pretty: bool,
}

impl<E> Report<E>
where
E: Error,
{
/// Create a new `Report` from an input error.
#[unstable(feature = "error_reporter", issue = "90172")]
pub fn new(error: E) -> Report<E> {
Report { error, show_backtrace: false, pretty: false }
}

/// Enable pretty-printing the report.
#[unstable(feature = "error_reporter", issue = "90172")]
pub fn pretty(mut self, pretty: bool) -> Self {
self.pretty = pretty;
self
}

/// Enable showing a backtrace for the report.
#[unstable(feature = "error_reporter", issue = "90172")]
pub fn show_backtrace(mut self, show_backtrace: bool) -> Self {
self.show_backtrace = show_backtrace;
self
}

fn backtrace(&self) -> Option<&Backtrace> {
// have to grab the backtrace on the first error directly since that error may not be
// 'static
let backtrace = self.error.backtrace();
let backtrace = backtrace.or_else(|| {
self.error
.source()
.map(|source| source.chain().find_map(|source| source.backtrace()))
.flatten()
});
backtrace
}

/// Format the report as a single line.
#[unstable(feature = "error_reporter", issue = "90172")]
fn fmt_singleline(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.error)?;

let sources = self.error.source().into_iter().flat_map(<dyn Error>::chain);

for cause in sources {
write!(f, ": {}", cause)?;
}

Ok(())
}

/// Format the report as multiple lines, with each error cause on its own line.
#[unstable(feature = "error_reporter", issue = "90172")]
fn fmt_multiline(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let error = &self.error;

write!(f, "{}", error)?;

if let Some(cause) = error.source() {
write!(f, "\n\nCaused by:")?;

let multiple = cause.source().is_some();

for (ind, error) in cause.chain().enumerate() {
writeln!(f)?;
let mut indented = Indented {
inner: f,
number: if multiple { Some(ind) } else { None },
started: false,
};
write!(indented, "{}", error)?;
}
}

if self.show_backtrace {
let backtrace = self.backtrace();

if let Some(backtrace) = backtrace {
let backtrace = backtrace.to_string();

f.write_str("\n\nStack backtrace:\n")?;
f.write_str(backtrace.trim_end())?;
}
}

Ok(())
}
}

#[unstable(feature = "error_reporter", issue = "90172")]
impl<E> From<E> for Report<E>
where
E: Error,
{
fn from(error: E) -> Self {
Report::new(error)
}
}

#[unstable(feature = "error_reporter", issue = "90172")]
impl<E> fmt::Display for Report<E>
where
E: Error,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.pretty { self.fmt_multiline(f) } else { self.fmt_singleline(f) }
}
}

// This type intentionally outputs the same format for `Display` and `Debug`for
// situations where you unwrap a `Report` or return it from main.
#[unstable(feature = "error_reporter", issue = "90172")]
impl<E> fmt::Debug for Report<E>
where
E: Error,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

/// Wrapper type for indenting the inner source.
struct Indented<'a, D> {
inner: &'a mut D,
number: Option<usize>,
yaahc marked this conversation as resolved.
Show resolved Hide resolved
started: bool,
}

impl<T> Write for Indented<'_, T>
where
T: Write,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
for (i, line) in s.split('\n').enumerate() {
if !self.started {
self.started = true;
match self.number {
Some(number) => write!(self.inner, "{: >5}: ", number)?,
None => self.inner.write_str(" ")?,
}
} else if i > 0 {
self.inner.write_char('\n')?;
if self.number.is_some() {
self.inner.write_str(" ")?;
} else {
self.inner.write_str(" ")?;
}
}

self.inner.write_str(line)?;
}

Ok(())
}
}
Loading