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 error::Report type #90174

Closed
wants to merge 9 commits into from
Closed
245 changes: 244 additions & 1 deletion 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 @@ -807,3 +807,246 @@ 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,
yaahc marked this conversation as resolved.
Show resolved Hide resolved
/// or in multi-line format with each cause in the error chain on a new line.
///
/// # Examples
///
/// ```
/// #![feature(error_reporter)]
///
/// use std::error::{Error, Report};
/// use std::fmt;
///
/// #[derive(Debug)]
/// struct SuperError {
/// side: SuperErrorSideKick,
/// }
///
/// impl fmt::Display for SuperError {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "SuperError is here!")
/// }
/// }
///
/// impl Error for SuperError {
/// fn source(&self) -> Option<&(dyn Error + 'static)> {
/// Some(&self.side)
/// }
/// }
///
/// #[derive(Debug)]
/// struct SuperErrorSideKick;
///
/// impl fmt::Display for SuperErrorSideKick {
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// write!(f, "SuperErrorSideKick is here!")
/// }
/// }
///
/// impl Error for SuperErrorSideKick {}
///
/// fn main() {
/// let error = SuperError { side: SuperErrorSideKick };
/// let report = Report::new(&error).pretty();
///
/// println!("{}", report);
/// }
/// ```
#[unstable(feature = "error_reporter", issue = "90172")]
pub struct Report<E> {
source: E,
seanchen1991 marked this conversation as resolved.
Show resolved Hide resolved
show_backtrace: bool,
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(source: E) -> Report<E> {
Report { source, show_backtrace: false, pretty: false }
}

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

Choose a reason for hiding this comment

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

I don't think we have any 'builder pattern' functions in std right now that take and return self by value. Instead, they take &mut self and return &mut Self. That way, you can also use them without chaining them, as they modify the original object, but also allow chaining.

Copy link
Member

@yaahc yaahc Oct 22, 2021

Choose a reason for hiding this comment

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

it's definitely preferable to have this one done by value, because otherwise you can end up with common usages breaking. Our original version used &mut Self but broke in the following trivial example:

let report = Report::new(error).pretty(); // error: reference to temporary
println!("{}", report); 

Report is designed so that you're always going to create it and then use it immediately, so being able to mutate a pre-defined report is never going to be useful.

Copy link
Member

Choose a reason for hiding this comment

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

Report is designed so that you're always going to create it and then use it immediately, so being able to mutate a pre-defined report is never going to be useful.

Then why is it a problem that your example breaks, if you're not supposed to store it for later use?

Your example makes me think that it would be useful to be able to do things like:

let mut report = Report::new(error);

if opts.verbose_errors {
    report.pretty(true);
}

println!("{}", report); 

Copy link
Member

@yaahc yaahc Oct 22, 2021

Choose a reason for hiding this comment

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

I'm not sure I agree, I'd write that like this:

let report = Report::new(error).pretty(opts.verbose_errors);
println!("{}", report);

Then why is it a problem that your example breaks, if you're not supposed to store it for later use?

The issue to me is that I don't want to be forced to add a bunch of extra lines or inline the entire thing in a println in order to have it compile, if it it was returned by reference my example could only be written like this:

let mut report = Report::new(error);
report.pretty(opts.verbose_errors);
println!("{}", report);

or

println!("{}", report.pretty(opts.verbose_errors));

In my experience using Report so far I have found that it tends to be more annoying to use when it returned &mut Self and when I inlined the method calls they often ended up having to be spread over multiple lines and made the code look much harder to read for me.

Copy link
Member Author

Choose a reason for hiding this comment

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

At the very least, it seems like show_backtrace and pretty should be reverted to accept a boolean (we had this in a previous iteration, but ended up removing it).

Copy link
Member

Choose a reason for hiding this comment

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

(This is why in the other cases the builder type is different type than the type you end up building. Then the builder type doesn't need to contain any references or resources yet, such that (&mut self) -> &mut Self can be used, and only an .open(), .spawn(..), or w/e at the end will make the object you wanted to make, which might end up borrowing things into the newly created object.

But having two types in this case might get a bit annoying.)

Copy link
Member

Choose a reason for hiding this comment

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

Yea, I think its particularly important that fallible_fn().map_err(Report::new).unwrap() be usable, so forcing a .build() call at the end feels a little awkward to me.

self.pretty = true;
self
}

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

/// 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.source)?;

let sources = self.source.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.source;

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

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

let multiple = cause.source().is_some();
let format = if multiple {
Format::Numbered { ind: 0 }
} else {
Format::Uniform { indentation: " " }
};

for error in cause.chain() {
writeln!(f)?;

let mut indented = Indented { inner: f, needs_indent: true, format };

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

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

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

write!(f, "\n\n")?;
writeln!(f, "Stack backtrace:")?;

backtrace.truncate(backtrace.trim_end().len());

write!(f, "{}", backtrace)?;
seanchen1991 marked this conversation as resolved.
Show resolved Hide resolved
}
}

Ok(())
}
}

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

#[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
yaahc marked this conversation as resolved.
Show resolved Hide resolved
// 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)
}
}

/// Encapsulates how error sources are indented and formatted.
struct Indented<'a, D: ?Sized> {
inner: &'a mut D,
needs_indent: bool,
format: Format,
}

/// The possible variants that error sources can be formatted as.
#[derive(Clone, Copy)]
enum Format {
/// Insert uniform indentation before every line.
///
/// This format takes a static string as input and inserts it after every newline.
Uniform {
/// The string to insert as indentation.
indentation: &'static str,
},
seanchen1991 marked this conversation as resolved.
Show resolved Hide resolved
/// Inserts a number before the first line.
///
/// This format hard codes the indentation level to match the indentation from
/// `std::backtrace::Backtrace`.
Numbered {
/// The index to insert before the first line of output.
ind: usize,
},
}

impl<D> Write for Indented<'_, D>
where
D: Write + ?Sized,
{
fn write_str(&mut self, s: &str) -> fmt::Result {
for (ind, line) in s.split('\n').enumerate() {
if ind > 0 {
self.inner.write_char('\n')?;
self.needs_indent = true;
}

if self.needs_indent {
if line.is_empty() {
continue;
}

self.format.insert_indentation(ind, &mut self.inner)?;
self.needs_indent = false;
}

self.inner.write_fmt(format_args!("{}", line))?;
seanchen1991 marked this conversation as resolved.
Show resolved Hide resolved
}

Ok(())
}
}

impl Format {
/// Write the specified formatting to the write buffer.
fn insert_indentation(&mut self, line: usize, f: &mut dyn Write) -> fmt::Result {
match self {
Format::Uniform { indentation } => {
write!(f, "{}", indentation)
}
Format::Numbered { ind } => {
if line == 0 {
yaahc marked this conversation as resolved.
Show resolved Hide resolved
write!(f, "{: >4}: ", ind)?;
*ind += 1;
yaahc marked this conversation as resolved.
Show resolved Hide resolved
Ok(())
} else {
write!(f, " ")
}
}
}
}
}