-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Add error::Report type #90174
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
Changes from 1 commit
59df6c8
6a59d0e
c6de413
c0f14cb
aa853bd
d2f49ee
32bcb81
840cb00
edd65f7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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, | ||
/// 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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); There was a problem hiding this comment. Choose a reason for hiding this commentThe 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);
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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At the very least, it seems like There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 But having two types in this case might get a bit annoying.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea, I think its particularly important that |
||
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, " ") | ||
} | ||
} | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.