fix(graphical): fix nested error wrapping (#358)

Fixes: https://github.com/zkat/miette/issues/333
This commit is contained in:
Brooks Rady 2024-03-27 16:20:06 -07:00 committed by GitHub
parent ca646f3119
commit 3eabbcebf1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 200 additions and 72 deletions

View File

@ -6,35 +6,9 @@ use crate::{Diagnostic, LabeledSpan, Report, SourceCode};
use crate as miette;
#[repr(transparent)]
pub(crate) struct DisplayError<M>(pub(crate) M);
#[repr(transparent)]
pub(crate) struct MessageError<M>(pub(crate) M);
pub(crate) struct NoneError;
impl<M> Debug for DisplayError<M>
where
M: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<M> Display for DisplayError<M>
where
M: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl<M> StdError for DisplayError<M> where M: Display + 'static {}
impl<M> Diagnostic for DisplayError<M> where M: Display + 'static {}
impl<M> Debug for MessageError<M>
where
M: Display + Debug,
@ -56,21 +30,6 @@ where
impl<M> StdError for MessageError<M> where M: Display + Debug + 'static {}
impl<M> Diagnostic for MessageError<M> where M: Display + Debug + 'static {}
impl Debug for NoneError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Debug::fmt("Option was None", f)
}
}
impl Display for NoneError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt("Option was None", f)
}
}
impl StdError for NoneError {}
impl Diagnostic for NoneError {}
#[repr(transparent)]
pub(crate) struct BoxedError(pub(crate) Box<dyn Diagnostic + Send + Sync>);

View File

@ -1,7 +1,7 @@
use std::fmt::{self, Write};
use owo_colors::{OwoColorize, Style, StyledList};
use unicode_width::UnicodeWidthChar;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
use crate::handlers::theme::*;
@ -225,7 +225,7 @@ impl GraphicalReportHandler {
self.render_related(f, diagnostic, src)?;
if let Some(footer) = &self.footer {
writeln!(f)?;
let width = self.termwidth.saturating_sub(4);
let width = self.termwidth.saturating_sub(2);
let mut opts = textwrap::Options::new(width)
.initial_indent(" ")
.subsequent_indent(" ")
@ -265,7 +265,6 @@ impl GraphicalReportHandler {
);
write!(header, "{}", link)?;
writeln!(f, "{}", header)?;
writeln!(f)?;
} else if let Some(code) = diagnostic.code() {
write!(header, "{}", code.style(severity_style),)?;
if self.links == LinkStyle::Text && diagnostic.url().is_some() {
@ -273,8 +272,8 @@ impl GraphicalReportHandler {
write!(header, " ({})", url.style(self.theme.styles.link))?;
}
writeln!(f, "{}", header)?;
writeln!(f)?;
}
writeln!(f)?;
Ok(())
}
@ -354,8 +353,12 @@ impl GraphicalReportHandler {
inner_renderer.footer = None;
// Cause chains are already flattened, so don't double-print the nested error
inner_renderer.with_cause_chain = false;
// Since everything from here on is indented, shrink the virtual terminal
inner_renderer.termwidth -= rest_indent.width();
inner_renderer.render_report(&mut inner, diag)?;
// If there was no header, remove the leading newline
let inner = inner.trim_start_matches('\n');
writeln!(f, "{}", self.wrap(&inner, opts))?;
}
ErrorKind::StdError(err) => {
@ -370,7 +373,7 @@ impl GraphicalReportHandler {
fn render_footer(&self, f: &mut impl fmt::Write, diagnostic: &(dyn Diagnostic)) -> fmt::Result {
if let Some(help) = diagnostic.help() {
let width = self.termwidth.saturating_sub(4);
let width = self.termwidth.saturating_sub(2);
let initial_indent = " help: ".style(self.theme.styles.help).to_string();
let mut opts = textwrap::Options::new(width)
.initial_indent(&initial_indent)
@ -398,8 +401,8 @@ impl GraphicalReportHandler {
let mut inner_renderer = self.clone();
// Re-enable the printing of nested cause chains for related errors
inner_renderer.with_cause_chain = true;
writeln!(f)?;
for rel in related {
writeln!(f)?;
match rel.severity() {
Some(Severity::Error) | None => write!(f, "Error: ")?,
Some(Severity::Warning) => write!(f, "Warning: ")?,

View File

@ -6,12 +6,14 @@ fn related() {
#[derive(Error, Debug, Diagnostic)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
struct Foo {
#[related]
related: Vec<Baz>,
}
#[derive(Error, Debug, Diagnostic)]
#[allow(dead_code)]
enum Bar {
#[error("variant1")]
#[diagnostic(code(foo::bar::baz))]
@ -29,6 +31,7 @@ fn related() {
#[derive(Error, Debug, Diagnostic)]
#[error("welp2")]
#[allow(dead_code)]
struct Baz;
}
@ -37,6 +40,7 @@ fn related_report() {
#[derive(Error, Debug, Diagnostic)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
struct Foo {
#[related]
related: Vec<Report>,
@ -288,6 +292,7 @@ fn test_snippet_named_struct() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
struct Foo<'a> {
#[source_code]
src: &'a str,
@ -310,6 +315,7 @@ fn test_snippet_unnamed_struct() {
#[derive(Debug, Diagnostic, Error)]
#[error("welp")]
#[diagnostic(code(foo::bar::baz))]
#[allow(dead_code)]
struct Foo<'a>(
#[source_code] &'a str,
#[label("{0}")] SourceSpan,

View File

@ -59,14 +59,15 @@ fn word_wrap_options() -> Result<(), MietteError> {
let out =
fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| handler);
let expected = " × abcdefghijklmnopqrstuvwxyz\n".to_string();
let expected = "\n × abcdefghijklmnopqrstuvwxyz\n".to_string();
assert_eq!(expected, out);
// A long word can break with a smaller width
let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| {
handler.with_width(10)
});
let expected = r#" × abcd
let expected = r#"
× abcd
efgh
ijkl
mnop
@ -81,7 +82,7 @@ fn word_wrap_options() -> Result<(), MietteError> {
let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| {
handler.with_width(10).with_break_words(false)
});
let expected = " × abcdefghijklmnopqrstuvwxyz\n".to_string();
let expected = "\n × abcdefghijklmnopqrstuvwxyz\n".to_string();
assert_eq!(expected, out);
// Breaks should start at the boundary of each word if possible
@ -89,7 +90,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
Report::msg("12 123 1234 12345 123456 1234567 1234567890"),
|handler| handler.with_width(10),
);
let expected = r#" × 12
let expected = r#"
× 12
123
1234
1234
@ -110,7 +112,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
Report::msg("12 123 1234 12345 123456 1234567 1234567890"),
|handler| handler.with_width(10).with_break_words(false),
);
let expected = r#" × 12
let expected = r#"
× 12
123
1234
12345
@ -126,7 +129,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
Report::msg("a-b a-b-c a-b-c-d a-b-c-d-e a-b-c-d-e-f a-b-c-d-e-f-g a-b-c-d-e-f-g-h"),
|handler| handler.with_width(10).with_break_words(false),
);
let expected = r#" × a-b
let expected = r#"
× a-b
a-b-
c a-
b-c-
@ -158,7 +162,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
.with_word_splitter(textwrap::WordSplitter::NoHyphenation)
},
);
let expected = r#" × a-b
let expected = r#"
× a-b
a-b-c
a-b-c-d
a-b-c-d-e
@ -174,7 +179,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
Report::msg("a/b a/b/c a/b/c/d a/b/c/d/e a/b/c/d/e/f a/b/c/d/e/f/g a/b/c/d/e/f/g/h"),
|handler| handler.with_width(10).with_break_words(false),
);
let expected = r#" × a/b
let expected = r#"
× a/b
a/b/
c a/
b/c/
@ -206,7 +212,8 @@ fn word_wrap_options() -> Result<(), MietteError> {
.with_word_separator(textwrap::WordSeparator::AsciiSpace)
},
);
let expected = r#" × a/b
let expected = r#"
× a/b
a/b/c
a/b/c/d
a/b/c/d/e
@ -227,7 +234,8 @@ fn wrap_option() -> Result<(), MietteError> {
Report::msg("abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz"),
|handler| handler.with_width(15),
);
let expected = r#" × abc def
let expected = r#"
× abc def
ghi jkl
mno pqr
stu vwx
@ -246,7 +254,7 @@ fn wrap_option() -> Result<(), MietteError> {
|handler| handler.with_width(15).with_wrap_lines(false),
);
let expected =
" × abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz\n".to_string();
"\n × abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz\n".to_string();
assert_eq!(expected, out);
// Then, user-defined new lines should be preserved wrapping is disabled
@ -254,7 +262,8 @@ fn wrap_option() -> Result<(), MietteError> {
Report::msg("abc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz"),
|handler| handler.with_width(15).with_wrap_lines(false),
);
let expected = r#" × abc def ghi jkl mno pqr stu vwx yz
let expected = r#"
× abc def ghi jkl mno pqr stu vwx yz
abc def ghi jkl mno pqr stu vwx yz
abc def ghi jkl mno pqr stu vwx yz
"#
@ -264,6 +273,143 @@ fn wrap_option() -> Result<(), MietteError> {
Ok(())
}
#[test]
fn wrapping_nested_errors() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("This is the parent error, the error withhhhh the children, kiddos, pups, as it were, and so on...")]
#[diagnostic(
code(mama::error),
help(
"try doing it better next time? I mean, you could have also done better thisssss time, but no?"
)
)]
struct MamaError {
#[diagnostic_source]
baby: BabyError,
}
#[derive(Debug, Diagnostic, Error)]
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
#[diagnostic(
code(baby::error),
help(
"it cannot be helped... woulddddddd you really want to get rid of an error that's so cute?"
)
)]
struct BabyError;
let err = MamaError { baby: BabyError };
let out = fmt_report_with_settings(err.into(), |handler| handler.with_width(50));
let expected = r#"mama::error
× This is the parent error, the error withhhhh
the children, kiddos, pups, as it were, and
so on...
baby::error
× Wah wah: I may be small, but I'll
cause a proper bout of trouble justt
try wrapping this mess of a line,
buddo!
help: it cannot be helped... woulddddddd
you really want to get rid of an
error that's so cute?
help: try doing it better next time? I mean,
you could have also done better thisssss
time, but no?
"#;
assert_eq!(expected, out);
Ok(())
}
#[test]
fn wrapping_related_errors() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
#[error("This is the parent error, the error withhhhh the children, kiddos, pups, as it were, and so on...")]
#[diagnostic(
code(mama::error),
help(
"try doing it better next time? I mean, you could have also done better thisssss time, but no?"
)
)]
struct MamaError {
#[diagnostic_source]
baby: BrotherError,
}
#[derive(Debug, Diagnostic, Error)]
#[error("Welcome to the brother-error brotherhood — where all of the wee baby errors join into a formidable force")]
#[diagnostic(code(brother::error))]
struct BrotherError {
#[related]
brethren: Vec<Box<dyn Diagnostic + Send + Sync>>,
}
#[derive(Debug, Diagnostic, Error)]
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
#[diagnostic(help(
"it cannot be helped... woulddddddd you really want to get rid of an error that's so cute?"
))]
struct BabyError;
#[derive(Debug, Diagnostic, Error)]
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
#[diagnostic(severity(Warning))]
struct BabyWarning;
#[derive(Debug, Diagnostic, Error)]
#[error("Wah wah: I may be small, but I'll cause a proper bout of trouble — justt try wrapping this mess of a line, buddo!")]
#[diagnostic(severity(Advice))]
struct BabyAdvice;
let err = MamaError {
baby: BrotherError {
brethren: vec![BabyError.into(), BabyWarning.into(), BabyAdvice.into()],
},
};
let out = fmt_report_with_settings(err.into(), |handler| handler.with_width(50));
let expected = r#"mama::error
× This is the parent error, the error withhhhh
the children, kiddos, pups, as it were, and
so on...
brother::error
× Welcome to the brother-error
brotherhood where all of the wee
baby errors join into a formidable
force
Error:
× Wah wah: I may be small, but I'll
cause a proper bout of trouble justt
try wrapping this mess of a line,
buddo!
help: it cannot be helped... woulddddddd
you really want to get rid of an
error that's so cute?
Warning:
Wah wah: I may be small, but I'll
cause a proper bout of trouble justt
try wrapping this mess of a line,
buddo!
Advice:
Wah wah: I may be small, but I'll
cause a proper bout of trouble justt
try wrapping this mess of a line,
buddo!
help: try doing it better next time? I mean,
you could have also done better thisssss
time, but no?
"#;
assert_eq!(expected, out);
Ok(())
}
#[test]
fn empty_source() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
@ -326,7 +472,8 @@ if true {
let out = fmt_report(err.into());
println!("Error: {}", out);
let expected = r#" × oops!
let expected = r#"
× oops!
[issue:1:1]
1 if true {
2 a
@ -361,7 +508,8 @@ fn single_line_highlight_span_full_line() {
let out = fmt_report(err.into());
println!("Error: {}", out);
let expected = r#" × oops!
let expected = r#"
× oops!
[issue:2:1]
1 source
2 text
@ -1411,7 +1559,6 @@ Error: oops::my::bad
2 text
help: try doing it better next time?
"#
.trim_start()
.to_string();
@ -1578,6 +1725,7 @@ Error: oops::my::related::error
2 text
help: try doing it better next time?
Warning: oops::my::related::warning
oops!
@ -1588,6 +1736,7 @@ Warning: oops::my::related::warning
2 text
help: try doing it better next time?
Advice: oops::my::related::advice
oops!
@ -1623,7 +1772,8 @@ fn zero_length_eol_span() {
let out = fmt_report(err.into());
println!("Error: {}", out);
let expected = r#" × oops!
let expected = r#"
× oops!
[issue:2:1]
1 this is the first line
2 this is the second line
@ -1657,7 +1807,8 @@ fn primary_label() {
println!("Error: {}", out);
// line 2 should be the primary, not line 1
let expected = r#" × oops!
let expected = r#"
× oops!
[issue:2:2]
1 this is the first line
·
@ -1808,7 +1959,8 @@ fn syntax_highlighter() {
GraphicalReportHandler::new_themed(GraphicalTheme::unicode())
.render_report(&mut out, &err)
.unwrap();
let expected = r#" × This is an error
let expected = r#"
× This is an error
[hello_world:2:5]
1 fn main() {
2 println!("Hello, World!");
@ -1862,7 +2014,8 @@ fn syntax_highlighter_on_real_file() {
.unwrap();
let expected = format!(
r#" × This is an error
r#"
× This is an error
[{filename}:{l2}:{CO}]
{l1}
{l2} let (filename, line) = (file!(), line!() as usize);

View File

@ -104,7 +104,8 @@ fn test_diagnostic_source_pass_extra_info() {
.render_report(&mut out, &diag)
.unwrap();
println!("Error: {}", out);
let expected = r#" × TestError
let expected = r#"
× TestError
× A complex error happened
[1:2]
1 Hello
@ -138,7 +139,8 @@ fn test_diagnostic_source_is_output() {
.unwrap();
println!("{}", out);
let expected = r#" × TestError
let expected = r#"
× TestError
× A complex error happened
1 right here
@ -186,7 +188,8 @@ fn test_nested_diagnostic_source_is_output() {
.unwrap();
println!("{}", out);
let expected = r#" × A nested error happened
let expected = r#"
× A nested error happened
× TestError
× A complex error happened
@ -252,10 +255,12 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
.unwrap();
println!("{}", out);
let expected = r#" × A nested error happened
let expected = r#"
× A nested error happened
× A multi-error happened
Error: × A nested error happened
Error:
× A nested error happened
× TestError
× A complex error happened
@ -271,7 +276,9 @@ fn test_nested_cause_chains_for_related_errors_are_output() {
·
· here
Error: × A complex error happened
Error:
× A complex error happened
1 You're actually a mess
·