From a91e4c98204a25ea0af32c045b4e3d64f750f315 Mon Sep 17 00:00:00 2001 From: NewWars <đł€đæł@đŋħ.đ€ð> Date: Thu, 31 Aug 2023 07:47:33 +0100 Subject: [PATCH 1/3] Report implement Error, move Cache to Report Builder and sequentially Report --- src/lib.rs | 55 +++++++++++++++++++++++------ src/source.rs | 8 +++++ src/write.rs | 97 +++++++++++++++++++++++++++++---------------------- 3 files changed, 108 insertions(+), 52 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 652b6a7..9b47393 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ use std::{ cmp::{PartialEq, Eq}, fmt, }; +use std::error::Error; use unicode_width::UnicodeWidthChar; /// A trait implemented by spans within a character-based source. @@ -143,7 +144,7 @@ impl Label { } /// A type representing a diagnostic that is ready to be written to output. -pub struct Report<'a, S: Span = Range> { +pub struct Report<'a, C: Cache, S: Span = Range> { kind: ReportKind<'a>, code: Option, msg: Option, @@ -152,11 +153,12 @@ pub struct Report<'a, S: Span = Range> { location: (::Owned, usize), labels: Vec>, config: Config, + cache: C } -impl Report<'_, S> { +impl> Report<'_, C, S> { /// Begin building a new [`Report`]. - pub fn build::Owned>>(kind: ReportKind, src_id: Id, offset: usize) -> ReportBuilder { + pub fn build::Owned>>(kind: ReportKind, src_id: Id, offset: usize) -> ReportBuilder { ReportBuilder { kind, code: None, @@ -166,11 +168,12 @@ impl Report<'_, S> { location: (src_id.into(), offset), labels: Vec::new(), config: Config::default(), + cache: None } } /// Write this diagnostic out to `stderr`. - pub fn eprint>(&self, cache: C) -> io::Result<()> { + pub fn eprint(&self, cache: C) -> io::Result<()> { self.write(cache, io::stderr()) } @@ -178,12 +181,19 @@ impl Report<'_, S> { /// /// In most cases, [`Report::eprint`] is the /// ['more correct'](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)) function to use. - pub fn print>(&self, cache: C) -> io::Result<()> { + pub fn print(&self, cache: C) -> io::Result<()> { self.write_for_stdout(cache, io::stdout()) } + + /// Write this diagnostic into a [`String`] + fn write_to_string(&self) -> String { + let mut vec = Vec::new(); + self.write(*self.cache.dupe(), &mut vec).unwrap(); + String::from_utf8(vec).unwrap() + } } -impl<'a, S: Span> fmt::Debug for Report<'a, S> { +impl<'a, C: Cache, S: Span> fmt::Debug for Report<'a, C, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Report") .field("kind", &self.kind) @@ -195,6 +205,16 @@ impl<'a, S: Span> fmt::Debug for Report<'a, S> { .finish() } } + +impl<'a, C: Cache, S:Span> fmt::Display for Report<'a, C, S> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.write_to_string()) + } +} + +impl> Error for Report<'_, C, S> {} + + /// A type that defines the kind of report being produced. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ReportKind<'a> { @@ -222,7 +242,7 @@ impl fmt::Display for ReportKind<'_> { } /// A type used to build a [`Report`]. -pub struct ReportBuilder<'a, S: Span> { +pub struct ReportBuilder<'a, C: Cache, S: Span> { kind: ReportKind<'a>, code: Option, msg: Option, @@ -231,11 +251,12 @@ pub struct ReportBuilder<'a, S: Span> { location: (::Owned, usize), labels: Vec>, config: Config, + cache: Option } -impl<'a, S: Span> ReportBuilder<'a, S> { +impl<'a, S: Span, C: Cache> ReportBuilder<'a, C, S> { /// Give this report a numerical code that may be used to more precisely look up the error in documentation. - pub fn with_code(mut self, code: C) -> Self { + pub fn with_code(mut self, code: D) -> Self { self.code = Some(format!("{:02}", code)); self } @@ -302,8 +323,19 @@ impl<'a, S: Span> ReportBuilder<'a, S> { self } + /// Add a file source to link spans and code + pub fn with_source(mut self, cache: C) -> Self { + self.cache = Some(cache); + self + } + /// Finish building the [`Report`]. - pub fn finish(self) -> Report<'a, S> { + pub fn finish(self) -> Report<'a, C, S> { + assert!( + self.cache.is_some(), + "Cache needs to be given before finish (e.g: builder.with_cache(...)" + ); + Report { kind: self.kind, code: self.code, @@ -313,11 +345,12 @@ impl<'a, S: Span> ReportBuilder<'a, S> { location: self.location, labels: self.labels, config: self.config, + cache: self.cache.unwrap() } } } -impl<'a, S: Span> fmt::Debug for ReportBuilder<'a, S> { +impl<'a, S: Span, C: Cache> fmt::Debug for ReportBuilder<'a, C, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ReportBuilder") .field("kind", &self.kind) diff --git a/src/source.rs b/src/source.rs index c1f7cb6..f48d99c 100644 --- a/src/source.rs +++ b/src/source.rs @@ -17,6 +17,9 @@ pub trait Cache { /// This function may make use of attributes from the [`Fmt`] trait. // TODO: Don't box fn display<'a>(&self, id: &'a Id) -> Option>; + + /// Duplicate self, same as Clone but manual + fn dupe(&self) -> Box { todo!() } } impl<'b, C: Cache, Id: ?Sized> Cache for &'b mut C { @@ -27,6 +30,7 @@ impl<'b, C: Cache, Id: ?Sized> Cache for &'b mut C { impl, Id: ?Sized> Cache for Box { fn fetch(&mut self, id: &Id) -> Result<&Source, Box> { C::fetch(self, id) } fn display<'a>(&self, id: &'a Id) -> Option> { C::display(self, id) } + fn dupe(&self) -> Box { Box::new(C::dupe(self)) } } /// A type representing a single line of a [`Source`]. @@ -159,6 +163,7 @@ impl Source { impl Cache<()> for Source { fn fetch(&mut self, _: &()) -> Result<&Source, Box> { Ok(self) } fn display(&self, _: &()) -> Option> { None } + fn dupe(&self) -> Box { Box::new(self.clone()) } } impl Cache for (Id, Source) { @@ -166,6 +171,7 @@ impl Cache for (Id, Source) { if id == &self.0 { Ok(&self.1) } else { Err(Box::new(format!("Failed to fetch source '{}'", id))) } } fn display<'a>(&self, id: &'a Id) -> Option> { Some(Box::new(id)) } + fn dupe(&self) -> Box { todo!() } } /// A [`Cache`] that fetches [`Source`]s from the filesystem. @@ -222,6 +228,8 @@ impl Cache for FnCache< }) } fn display<'a>(&self, id: &'a Id) -> Option> { Some(Box::new(id)) } + + fn dupe(&self) -> Box { todo!() } } /// Create a [`Cache`] from a collection of ID/strings, where each corresponds to a [`Source`]. diff --git a/src/write.rs b/src/write.rs index 542ec60..a36b8bc 100644 --- a/src/write.rs +++ b/src/write.rs @@ -29,7 +29,7 @@ struct SourceGroup<'a, S: Span> { labels: Vec>, } -impl Report<'_, S> { +impl> Report<'_, C, S> { fn get_source_groups(&self, cache: &mut impl Cache) -> Vec> { let mut groups = Vec::new(); for label in self.labels.iter() { @@ -80,27 +80,20 @@ impl Report<'_, S> { /// `stderr`. If you are printing to `stdout`, use the [`write_for_stdout`](Self::write_for_stdout) method instead. /// /// If you wish to write to `stderr` or `stdout`, you can do so via [`Report::eprint`] or [`Report::print`] respectively. - pub fn write, W: Write>(&self, cache: C, w: W) -> io::Result<()> { + pub fn write(&self, cache: C, w: W) -> io::Result<()> { self.write_for_stream(cache, w, StreamType::Stderr) } /// Write this diagnostic to an implementor of [`Write`], assuming that the output is ultimately going to be printed /// to `stdout`. - pub fn write_for_stdout, W: Write>( - &self, - cache: C, - w: W, - ) -> io::Result<()> { + pub fn write_for_stdout(&self, cache: C, w: W, ) -> io::Result<()> { self.write_for_stream(cache, w, StreamType::Stdout) } /// Write this diagnostic to an implementor of [`Write`], assuming that the output is ultimately going to be printed /// to the given output stream (`stdout` or `stderr`). - fn write_for_stream, W: Write>( - &self, - mut cache: C, - mut w: W, - s: StreamType, + fn write_for_stream( + &self, mut cache: C, mut w: W, s: StreamType, ) -> io::Result<()> { let draw = match self.config.char_set { CharSet::Unicode => draw::Characters::unicode(), @@ -801,19 +794,12 @@ mod tests { //! //! and insta will fill it in. + use std::error::Error; use std::ops::Range; use insta::assert_snapshot; - use crate::{Cache, CharSet, Config, Label, Report, ReportKind, Source, Span}; - - impl Report<'_, S> { - fn write_to_string>(&self, cache: C) -> String { - let mut vec = Vec::new(); - self.write(cache, &mut vec).unwrap(); - String::from_utf8(vec).unwrap() - } - } + use crate::{CharSet, Config, Label, Report, ReportKind, Source}; fn no_color_and_ascii() -> Config { Config::default() @@ -825,11 +811,12 @@ mod tests { #[test] fn one_message() { - let msg = Report::>::build(ReportKind::Error, (), 0) + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") + .with_source(Source::from("")) .finish() - .write_to_string(Source::from("")); + .write_to_string(); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges "###) @@ -838,13 +825,14 @@ mod tests { #[test] fn two_labels_without_messages() { let source = "apple == orange;"; - let msg = Report::>::build(ReportKind::Error, (), 0) + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5)) .with_label(Label::new(9..15)) + .with_source(Source::from(source)) .finish() - .write_to_string(Source::from(source)); + .write_to_string(); // TODO: it would be nice if these spans still showed up (like codespan-reporting does) assert_snapshot!(msg, @r###" Error: can't compare apples with oranges @@ -858,13 +846,14 @@ mod tests { #[test] fn two_labels_with_messages() { let source = "apple == orange;"; - let msg = Report::>::build(ReportKind::Error, (), 0) + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) + .with_source(Source::from(source)) .finish() - .write_to_string(Source::from(source)); + .write_to_string(); // TODO: it would be nice if these lines didn't cross assert_snapshot!(msg, @r###" Error: can't compare apples with oranges @@ -882,14 +871,15 @@ mod tests { #[test] fn label_at_end_of_long_line() { let source = format!("{}orange", "apple == ".repeat(100)); - let msg = Report::>::build(ReportKind::Error, (), 0) + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label( Label::new(source.len() - 5..source.len()).with_message("This is an orange"), ) + .with_source(Source::from(source)) .finish() - .write_to_string(Source::from(source)); + .write_to_string(); // TODO: it would be nice if the start of long lines would be omitted (like rustc does) assert_snapshot!(msg, @r###" Error: can't compare apples with oranges @@ -905,11 +895,12 @@ mod tests { #[test] fn multiline_label() { let source = "apple\n==\norange"; - let msg = Report::>::build(ReportKind::Error, (), 0) + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_label(Label::new(0..source.len()).with_message("illegal comparison")) + .with_source(Source::from(source)) .finish() - .write_to_string(Source::from(source)); + .write_to_string(); // TODO: it would be nice if the 2nd line wasn't omitted assert_snapshot!(msg, @r###" Error: @@ -927,12 +918,13 @@ mod tests { #[test] fn partially_overlapping_labels() { let source = "https://example.com/"; - let msg = Report::>::build(ReportKind::Error, (), 0) + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_label(Label::new(0..source.len()).with_message("URL")) .with_label(Label::new(0..source.find(':').unwrap()).with_message("scheme")) + .with_source(Source::from(source)) .finish() - .write_to_string(Source::from(source)); + .write_to_string(); // TODO: it would be nice if you could tell where the spans start and end. assert_snapshot!(msg, @r###" Error: @@ -950,7 +942,7 @@ mod tests { #[test] fn multiple_labels_same_span() { let source = "apple == orange;"; - let msg = Report::>::build(ReportKind::Error, (), 0) + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) @@ -959,8 +951,9 @@ mod tests { .with_label(Label::new(9..15).with_message("This is an orange")) .with_label(Label::new(9..15).with_message("Have I mentioned that this is an orange?")) .with_label(Label::new(9..15).with_message("No really, have I mentioned that?")) + .with_source(Source::from(source)) .finish() - .write_to_string(Source::from(source)); + .write_to_string(); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -985,14 +978,15 @@ mod tests { #[test] fn note() { let source = "apple == orange;"; - let msg = Report::>::build(ReportKind::Error, (), 0) + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) .with_note("stop trying ... this is a fruitless endeavor") + .with_source(Source::from(source)) .finish() - .write_to_string(Source::from(source)); + .write_to_string(); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -1011,14 +1005,15 @@ mod tests { #[test] fn help() { let source = "apple == orange;"; - let msg = Report::>::build(ReportKind::Error, (), 0) + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) .with_help("have you tried peeling the orange?") + .with_source(Source::from(source)) .finish() - .write_to_string(Source::from(source)); + .write_to_string(); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -1037,15 +1032,16 @@ mod tests { #[test] fn help_and_note() { let source = "apple == orange;"; - let msg = Report::>::build(ReportKind::Error, (), 0) + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) .with_help("have you tried peeling the orange?") .with_note("stop trying ... this is a fruitless endeavor") + .with_source(Source::from(source)) .finish() - .write_to_string(Source::from(source)); + .write_to_string(); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -1062,4 +1058,23 @@ mod tests { ---' "###) } + + #[test] + fn report_to_error() { + let source = "apple == orange;"; + let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_message("can't compare apples with oranges") + .with_label(Label::new(0..5).with_message("This is an apple")) + .with_label(Label::new(9..15).with_message("This is an orange")) + .with_help("have you tried peeling the orange?") + .with_note("stop trying ... this is a fruitless endeavor") + .with_source(Source::from(source)) + .finish(); + + let err: &dyn Error = &msg; // This bit compiling proves it is Error + + println!("{err}"); + println!("{err:?}"); + } } From 536f23cb3fa7a4a53bc2af762d332e90d8d116ea Mon Sep 17 00:00:00 2001 From: NewWars <đł€đæł@đŋħ.đ€ð> Date: Thu, 31 Aug 2023 07:47:33 +0100 Subject: [PATCH 2/3] Revert "Report implement Error, move Cache to Report Builder and sequentially Report" This reverts commit a91e4c98204a25ea0af32c045b4e3d64f750f315. --- src/lib.rs | 55 ++++++----------------------- src/source.rs | 8 ----- src/write.rs | 97 ++++++++++++++++++++++----------------------------- 3 files changed, 52 insertions(+), 108 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9b47393..652b6a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,6 @@ use std::{ cmp::{PartialEq, Eq}, fmt, }; -use std::error::Error; use unicode_width::UnicodeWidthChar; /// A trait implemented by spans within a character-based source. @@ -144,7 +143,7 @@ impl Label { } /// A type representing a diagnostic that is ready to be written to output. -pub struct Report<'a, C: Cache, S: Span = Range> { +pub struct Report<'a, S: Span = Range> { kind: ReportKind<'a>, code: Option, msg: Option, @@ -153,12 +152,11 @@ pub struct Report<'a, C: Cache, S: Span = Range> { location: (::Owned, usize), labels: Vec>, config: Config, - cache: C } -impl> Report<'_, C, S> { +impl Report<'_, S> { /// Begin building a new [`Report`]. - pub fn build::Owned>>(kind: ReportKind, src_id: Id, offset: usize) -> ReportBuilder { + pub fn build::Owned>>(kind: ReportKind, src_id: Id, offset: usize) -> ReportBuilder { ReportBuilder { kind, code: None, @@ -168,12 +166,11 @@ impl> Report<'_, C, S> { location: (src_id.into(), offset), labels: Vec::new(), config: Config::default(), - cache: None } } /// Write this diagnostic out to `stderr`. - pub fn eprint(&self, cache: C) -> io::Result<()> { + pub fn eprint>(&self, cache: C) -> io::Result<()> { self.write(cache, io::stderr()) } @@ -181,19 +178,12 @@ impl> Report<'_, C, S> { /// /// In most cases, [`Report::eprint`] is the /// ['more correct'](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)) function to use. - pub fn print(&self, cache: C) -> io::Result<()> { + pub fn print>(&self, cache: C) -> io::Result<()> { self.write_for_stdout(cache, io::stdout()) } - - /// Write this diagnostic into a [`String`] - fn write_to_string(&self) -> String { - let mut vec = Vec::new(); - self.write(*self.cache.dupe(), &mut vec).unwrap(); - String::from_utf8(vec).unwrap() - } } -impl<'a, C: Cache, S: Span> fmt::Debug for Report<'a, C, S> { +impl<'a, S: Span> fmt::Debug for Report<'a, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Report") .field("kind", &self.kind) @@ -205,16 +195,6 @@ impl<'a, C: Cache, S: Span> fmt::Debug for Report<'a, C, S> { .finish() } } - -impl<'a, C: Cache, S:Span> fmt::Display for Report<'a, C, S> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.write_to_string()) - } -} - -impl> Error for Report<'_, C, S> {} - - /// A type that defines the kind of report being produced. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ReportKind<'a> { @@ -242,7 +222,7 @@ impl fmt::Display for ReportKind<'_> { } /// A type used to build a [`Report`]. -pub struct ReportBuilder<'a, C: Cache, S: Span> { +pub struct ReportBuilder<'a, S: Span> { kind: ReportKind<'a>, code: Option, msg: Option, @@ -251,12 +231,11 @@ pub struct ReportBuilder<'a, C: Cache, S: Span> { location: (::Owned, usize), labels: Vec>, config: Config, - cache: Option } -impl<'a, S: Span, C: Cache> ReportBuilder<'a, C, S> { +impl<'a, S: Span> ReportBuilder<'a, S> { /// Give this report a numerical code that may be used to more precisely look up the error in documentation. - pub fn with_code(mut self, code: D) -> Self { + pub fn with_code(mut self, code: C) -> Self { self.code = Some(format!("{:02}", code)); self } @@ -323,19 +302,8 @@ impl<'a, S: Span, C: Cache> ReportBuilder<'a, C, S> { self } - /// Add a file source to link spans and code - pub fn with_source(mut self, cache: C) -> Self { - self.cache = Some(cache); - self - } - /// Finish building the [`Report`]. - pub fn finish(self) -> Report<'a, C, S> { - assert!( - self.cache.is_some(), - "Cache needs to be given before finish (e.g: builder.with_cache(...)" - ); - + pub fn finish(self) -> Report<'a, S> { Report { kind: self.kind, code: self.code, @@ -345,12 +313,11 @@ impl<'a, S: Span, C: Cache> ReportBuilder<'a, C, S> { location: self.location, labels: self.labels, config: self.config, - cache: self.cache.unwrap() } } } -impl<'a, S: Span, C: Cache> fmt::Debug for ReportBuilder<'a, C, S> { +impl<'a, S: Span> fmt::Debug for ReportBuilder<'a, S> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ReportBuilder") .field("kind", &self.kind) diff --git a/src/source.rs b/src/source.rs index f48d99c..c1f7cb6 100644 --- a/src/source.rs +++ b/src/source.rs @@ -17,9 +17,6 @@ pub trait Cache { /// This function may make use of attributes from the [`Fmt`] trait. // TODO: Don't box fn display<'a>(&self, id: &'a Id) -> Option>; - - /// Duplicate self, same as Clone but manual - fn dupe(&self) -> Box { todo!() } } impl<'b, C: Cache, Id: ?Sized> Cache for &'b mut C { @@ -30,7 +27,6 @@ impl<'b, C: Cache, Id: ?Sized> Cache for &'b mut C { impl, Id: ?Sized> Cache for Box { fn fetch(&mut self, id: &Id) -> Result<&Source, Box> { C::fetch(self, id) } fn display<'a>(&self, id: &'a Id) -> Option> { C::display(self, id) } - fn dupe(&self) -> Box { Box::new(C::dupe(self)) } } /// A type representing a single line of a [`Source`]. @@ -163,7 +159,6 @@ impl Source { impl Cache<()> for Source { fn fetch(&mut self, _: &()) -> Result<&Source, Box> { Ok(self) } fn display(&self, _: &()) -> Option> { None } - fn dupe(&self) -> Box { Box::new(self.clone()) } } impl Cache for (Id, Source) { @@ -171,7 +166,6 @@ impl Cache for (Id, Source) { if id == &self.0 { Ok(&self.1) } else { Err(Box::new(format!("Failed to fetch source '{}'", id))) } } fn display<'a>(&self, id: &'a Id) -> Option> { Some(Box::new(id)) } - fn dupe(&self) -> Box { todo!() } } /// A [`Cache`] that fetches [`Source`]s from the filesystem. @@ -228,8 +222,6 @@ impl Cache for FnCache< }) } fn display<'a>(&self, id: &'a Id) -> Option> { Some(Box::new(id)) } - - fn dupe(&self) -> Box { todo!() } } /// Create a [`Cache`] from a collection of ID/strings, where each corresponds to a [`Source`]. diff --git a/src/write.rs b/src/write.rs index a36b8bc..542ec60 100644 --- a/src/write.rs +++ b/src/write.rs @@ -29,7 +29,7 @@ struct SourceGroup<'a, S: Span> { labels: Vec>, } -impl> Report<'_, C, S> { +impl Report<'_, S> { fn get_source_groups(&self, cache: &mut impl Cache) -> Vec> { let mut groups = Vec::new(); for label in self.labels.iter() { @@ -80,20 +80,27 @@ impl> Report<'_, C, S> { /// `stderr`. If you are printing to `stdout`, use the [`write_for_stdout`](Self::write_for_stdout) method instead. /// /// If you wish to write to `stderr` or `stdout`, you can do so via [`Report::eprint`] or [`Report::print`] respectively. - pub fn write(&self, cache: C, w: W) -> io::Result<()> { + pub fn write, W: Write>(&self, cache: C, w: W) -> io::Result<()> { self.write_for_stream(cache, w, StreamType::Stderr) } /// Write this diagnostic to an implementor of [`Write`], assuming that the output is ultimately going to be printed /// to `stdout`. - pub fn write_for_stdout(&self, cache: C, w: W, ) -> io::Result<()> { + pub fn write_for_stdout, W: Write>( + &self, + cache: C, + w: W, + ) -> io::Result<()> { self.write_for_stream(cache, w, StreamType::Stdout) } /// Write this diagnostic to an implementor of [`Write`], assuming that the output is ultimately going to be printed /// to the given output stream (`stdout` or `stderr`). - fn write_for_stream( - &self, mut cache: C, mut w: W, s: StreamType, + fn write_for_stream, W: Write>( + &self, + mut cache: C, + mut w: W, + s: StreamType, ) -> io::Result<()> { let draw = match self.config.char_set { CharSet::Unicode => draw::Characters::unicode(), @@ -794,12 +801,19 @@ mod tests { //! //! and insta will fill it in. - use std::error::Error; use std::ops::Range; use insta::assert_snapshot; - use crate::{CharSet, Config, Label, Report, ReportKind, Source}; + use crate::{Cache, CharSet, Config, Label, Report, ReportKind, Source, Span}; + + impl Report<'_, S> { + fn write_to_string>(&self, cache: C) -> String { + let mut vec = Vec::new(); + self.write(cache, &mut vec).unwrap(); + String::from_utf8(vec).unwrap() + } + } fn no_color_and_ascii() -> Config { Config::default() @@ -811,12 +825,11 @@ mod tests { #[test] fn one_message() { - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") - .with_source(Source::from("")) .finish() - .write_to_string(); + .write_to_string(Source::from("")); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges "###) @@ -825,14 +838,13 @@ mod tests { #[test] fn two_labels_without_messages() { let source = "apple == orange;"; - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5)) .with_label(Label::new(9..15)) - .with_source(Source::from(source)) .finish() - .write_to_string(); + .write_to_string(Source::from(source)); // TODO: it would be nice if these spans still showed up (like codespan-reporting does) assert_snapshot!(msg, @r###" Error: can't compare apples with oranges @@ -846,14 +858,13 @@ mod tests { #[test] fn two_labels_with_messages() { let source = "apple == orange;"; - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) - .with_source(Source::from(source)) .finish() - .write_to_string(); + .write_to_string(Source::from(source)); // TODO: it would be nice if these lines didn't cross assert_snapshot!(msg, @r###" Error: can't compare apples with oranges @@ -871,15 +882,14 @@ mod tests { #[test] fn label_at_end_of_long_line() { let source = format!("{}orange", "apple == ".repeat(100)); - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label( Label::new(source.len() - 5..source.len()).with_message("This is an orange"), ) - .with_source(Source::from(source)) .finish() - .write_to_string(); + .write_to_string(Source::from(source)); // TODO: it would be nice if the start of long lines would be omitted (like rustc does) assert_snapshot!(msg, @r###" Error: can't compare apples with oranges @@ -895,12 +905,11 @@ mod tests { #[test] fn multiline_label() { let source = "apple\n==\norange"; - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_label(Label::new(0..source.len()).with_message("illegal comparison")) - .with_source(Source::from(source)) .finish() - .write_to_string(); + .write_to_string(Source::from(source)); // TODO: it would be nice if the 2nd line wasn't omitted assert_snapshot!(msg, @r###" Error: @@ -918,13 +927,12 @@ mod tests { #[test] fn partially_overlapping_labels() { let source = "https://example.com/"; - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_label(Label::new(0..source.len()).with_message("URL")) .with_label(Label::new(0..source.find(':').unwrap()).with_message("scheme")) - .with_source(Source::from(source)) .finish() - .write_to_string(); + .write_to_string(Source::from(source)); // TODO: it would be nice if you could tell where the spans start and end. assert_snapshot!(msg, @r###" Error: @@ -942,7 +950,7 @@ mod tests { #[test] fn multiple_labels_same_span() { let source = "apple == orange;"; - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) @@ -951,9 +959,8 @@ mod tests { .with_label(Label::new(9..15).with_message("This is an orange")) .with_label(Label::new(9..15).with_message("Have I mentioned that this is an orange?")) .with_label(Label::new(9..15).with_message("No really, have I mentioned that?")) - .with_source(Source::from(source)) .finish() - .write_to_string(); + .write_to_string(Source::from(source)); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -978,15 +985,14 @@ mod tests { #[test] fn note() { let source = "apple == orange;"; - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) .with_note("stop trying ... this is a fruitless endeavor") - .with_source(Source::from(source)) .finish() - .write_to_string(); + .write_to_string(Source::from(source)); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -1005,15 +1011,14 @@ mod tests { #[test] fn help() { let source = "apple == orange;"; - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) .with_help("have you tried peeling the orange?") - .with_source(Source::from(source)) .finish() - .write_to_string(); + .write_to_string(Source::from(source)); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -1032,16 +1037,15 @@ mod tests { #[test] fn help_and_note() { let source = "apple == orange;"; - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) + let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) .with_help("have you tried peeling the orange?") .with_note("stop trying ... this is a fruitless endeavor") - .with_source(Source::from(source)) .finish() - .write_to_string(); + .write_to_string(Source::from(source)); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -1058,23 +1062,4 @@ mod tests { ---' "###) } - - #[test] - fn report_to_error() { - let source = "apple == orange;"; - let msg = Report::<_, Range>::build(ReportKind::Error, (), 0) - .with_config(no_color_and_ascii()) - .with_message("can't compare apples with oranges") - .with_label(Label::new(0..5).with_message("This is an apple")) - .with_label(Label::new(9..15).with_message("This is an orange")) - .with_help("have you tried peeling the orange?") - .with_note("stop trying ... this is a fruitless endeavor") - .with_source(Source::from(source)) - .finish(); - - let err: &dyn Error = &msg; // This bit compiling proves it is Error - - println!("{err}"); - println!("{err:?}"); - } } From 386aba769b568e7979b708bed0b4778c8b449b31 Mon Sep 17 00:00:00 2001 From: NewWars <đł€đæł@đŋħ.đ€ð> Date: Sat, 2 Sep 2023 00:02:07 +0100 Subject: [PATCH 3/3] Report Implement Error and Display, moved Cache argument to Report::finish and render it, store the render in the Report --- .idea/vcs.xml | 6 ++++ examples/multifile.rs | 4 +-- examples/multiline.rs | 4 +-- examples/simple.rs | 8 ++--- examples/stresstest.rs | 4 +-- src/lib.rs | 42 +++++++++++++++++++++----- src/write.rs | 68 ++++++++++++++++++++++++------------------ 7 files changed, 90 insertions(+), 46 deletions(-) create mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/examples/multifile.rs b/examples/multifile.rs index 201160b..feebe9d 100644 --- a/examples/multifile.rs +++ b/examples/multifile.rs @@ -25,10 +25,10 @@ fn main() { .with_message(format!("Original definition of {} is here", "five".fg(a))) .with_color(a)) .with_note(format!("{} is a number and can only be added to other numbers", "Nat".fg(a))) - .finish() - .print(sources(vec![ + .finish(sources(vec![ ("a.tao", include_str!("a.tao")), ("b.tao", include_str!("b.tao")), ])) + .print() .unwrap(); } diff --git a/examples/multiline.rs b/examples/multiline.rs index ad2a77a..acf1f60 100644 --- a/examples/multiline.rs +++ b/examples/multiline.rs @@ -37,7 +37,7 @@ fn main() { )) .with_color(out2)) .with_note(format!("Outputs of {} expressions must coerce to the same type", "match".fg(out))) - .finish() - .print(("sample.tao", Source::from(include_str!("sample.tao")))) + .finish(("sample.tao", Source::from(include_str!("sample.tao")))) + .print() .unwrap(); } diff --git a/examples/simple.rs b/examples/simple.rs index 5f2002c..5b14099 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -6,8 +6,8 @@ fn main() { .with_message("Incompatible types") .with_label(Label::new(32..33).with_message("This is of type Nat")) .with_label(Label::new(42..45).with_message("This is of type Str")) - .finish() - .print(Source::from(include_str!("sample.tao"))) + .finish(Source::from(include_str!("sample.tao"))) + .print() .unwrap(); const SOURCE: &str = "a b c d e f"; @@ -19,7 +19,7 @@ fn main() { .with_label(Label::new(2..3).with_color(Color::Blue).with_message("`b` for banana").with_order(1)) .with_label(Label::new(4..5).with_color(Color::Green)) .with_label(Label::new(7..9).with_color(Color::Cyan).with_message("`e` for emerald")) - .finish() - .print(Source::from(SOURCE)) + .finish(Source::from(SOURCE)) + .print() .unwrap(); } diff --git a/examples/stresstest.rs b/examples/stresstest.rs index 0fc9d5f..b29a887 100644 --- a/examples/stresstest.rs +++ b/examples/stresstest.rs @@ -53,7 +53,7 @@ fn main() { .with_compact(true) .with_underlines(true) .with_tab_width(4)) - .finish() - .print(("stresstest.tao", Source::from(include_str!("stresstest.tao")))) + .finish(("stresstest.tao", Source::from(include_str!("stresstest.tao")))) + .print() .unwrap(); } diff --git a/src/lib.rs b/src/lib.rs index 652b6a7..3d7c3a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,6 +23,7 @@ use std::{ cmp::{PartialEq, Eq}, fmt, }; +use std::fmt::Formatter; use unicode_width::UnicodeWidthChar; /// A trait implemented by spans within a character-based source. @@ -152,6 +153,7 @@ pub struct Report<'a, S: Span = Range> { location: (::Owned, usize), labels: Vec>, config: Config, + rendered: Option } impl Report<'_, S> { @@ -170,16 +172,23 @@ impl Report<'_, S> { } /// Write this diagnostic out to `stderr`. - pub fn eprint>(&self, cache: C) -> io::Result<()> { - self.write(cache, io::stderr()) + pub fn eprint(&self) -> io::Result<()> { + write!(io::stderr(), "{}", self.rendered.as_ref().unwrap()) } /// Write this diagnostic out to `stdout`. /// /// In most cases, [`Report::eprint`] is the /// ['more correct'](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_(stderr)) function to use. - pub fn print>(&self, cache: C) -> io::Result<()> { - self.write_for_stdout(cache, io::stdout()) + pub fn print(&self) -> io::Result<()> { + write!(io::stdout(), "{}", self.rendered.as_ref().unwrap()) + } + + /// TODO: Move the description from the fork + fn write_to_string>(&self, cache: C) -> String { + let mut vec = Vec::new(); + self.write(cache, &mut vec).unwrap(); + String::from_utf8(vec).unwrap() } } @@ -195,6 +204,21 @@ impl<'a, S: Span> fmt::Debug for Report<'a, S> { .finish() } } + +impl<'a, S: Span> fmt::Display for Report<'a, S> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + assert!( + self.rendered.is_some(), + "A report render is necessary to do display" + ); + + write!(f, "{}", self.rendered.as_ref().unwrap()) + } +} + +impl<'a, S: Span> std::error::Error for Report<'a, S> {} + + /// A type that defines the kind of report being produced. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum ReportKind<'a> { @@ -303,8 +327,8 @@ impl<'a, S: Span> ReportBuilder<'a, S> { } /// Finish building the [`Report`]. - pub fn finish(self) -> Report<'a, S> { - Report { + pub fn finish>(self, cache: C) -> Report<'a, S> { + let mut report = Report { kind: self.kind, code: self.code, msg: self.msg, @@ -313,7 +337,11 @@ impl<'a, S: Span> ReportBuilder<'a, S> { location: self.location, labels: self.labels, config: self.config, - } + rendered: None + }; + report.rendered = Some(report.write_to_string(cache)); + + report } } diff --git a/src/write.rs b/src/write.rs index 542ec60..9d2cf2e 100644 --- a/src/write.rs +++ b/src/write.rs @@ -805,15 +805,7 @@ mod tests { use insta::assert_snapshot; - use crate::{Cache, CharSet, Config, Label, Report, ReportKind, Source, Span}; - - impl Report<'_, S> { - fn write_to_string>(&self, cache: C) -> String { - let mut vec = Vec::new(); - self.write(cache, &mut vec).unwrap(); - String::from_utf8(vec).unwrap() - } - } + use crate::{CharSet, Config, Label, Report, ReportKind, Source}; fn no_color_and_ascii() -> Config { Config::default() @@ -828,8 +820,8 @@ mod tests { let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_message("can't compare apples with oranges") - .finish() - .write_to_string(Source::from("")); + .finish(Source::from("")) + .to_string(); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges "###) @@ -843,8 +835,8 @@ mod tests { .with_message("can't compare apples with oranges") .with_label(Label::new(0..5)) .with_label(Label::new(9..15)) - .finish() - .write_to_string(Source::from(source)); + .finish(Source::from(source)) + .to_string(); // TODO: it would be nice if these spans still showed up (like codespan-reporting does) assert_snapshot!(msg, @r###" Error: can't compare apples with oranges @@ -863,8 +855,8 @@ mod tests { .with_message("can't compare apples with oranges") .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) - .finish() - .write_to_string(Source::from(source)); + .finish(Source::from(source)) + .to_string(); // TODO: it would be nice if these lines didn't cross assert_snapshot!(msg, @r###" Error: can't compare apples with oranges @@ -888,8 +880,8 @@ mod tests { .with_label( Label::new(source.len() - 5..source.len()).with_message("This is an orange"), ) - .finish() - .write_to_string(Source::from(source)); + .finish(Source::from(source)) + .to_string(); // TODO: it would be nice if the start of long lines would be omitted (like rustc does) assert_snapshot!(msg, @r###" Error: can't compare apples with oranges @@ -908,8 +900,8 @@ mod tests { let msg = Report::>::build(ReportKind::Error, (), 0) .with_config(no_color_and_ascii()) .with_label(Label::new(0..source.len()).with_message("illegal comparison")) - .finish() - .write_to_string(Source::from(source)); + .finish(Source::from(source)) + .to_string(); // TODO: it would be nice if the 2nd line wasn't omitted assert_snapshot!(msg, @r###" Error: @@ -931,8 +923,8 @@ mod tests { .with_config(no_color_and_ascii()) .with_label(Label::new(0..source.len()).with_message("URL")) .with_label(Label::new(0..source.find(':').unwrap()).with_message("scheme")) - .finish() - .write_to_string(Source::from(source)); + .finish(Source::from(source)) + .to_string(); // TODO: it would be nice if you could tell where the spans start and end. assert_snapshot!(msg, @r###" Error: @@ -959,8 +951,8 @@ mod tests { .with_label(Label::new(9..15).with_message("This is an orange")) .with_label(Label::new(9..15).with_message("Have I mentioned that this is an orange?")) .with_label(Label::new(9..15).with_message("No really, have I mentioned that?")) - .finish() - .write_to_string(Source::from(source)); + .finish(Source::from(source)) + .to_string(); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -991,8 +983,8 @@ mod tests { .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) .with_note("stop trying ... this is a fruitless endeavor") - .finish() - .write_to_string(Source::from(source)); + .finish(Source::from(source)) + .to_string(); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -1017,8 +1009,8 @@ mod tests { .with_label(Label::new(0..5).with_message("This is an apple")) .with_label(Label::new(9..15).with_message("This is an orange")) .with_help("have you tried peeling the orange?") - .finish() - .write_to_string(Source::from(source)); + .finish(Source::from(source)) + .to_string(); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -1044,8 +1036,8 @@ mod tests { .with_label(Label::new(9..15).with_message("This is an orange")) .with_help("have you tried peeling the orange?") .with_note("stop trying ... this is a fruitless endeavor") - .finish() - .write_to_string(Source::from(source)); + .finish(Source::from(source)) + .to_string(); assert_snapshot!(msg, @r###" Error: can't compare apples with oranges ,-[:1:1] @@ -1062,4 +1054,22 @@ mod tests { ---' "###) } + + #[test] + fn report_to_error() { + let source = "apple == orange;"; + let msg = Report::>::build(ReportKind::Error, (), 0) + .with_config(no_color_and_ascii()) + .with_message("can't compare apples with oranges") + .with_label(Label::new(0..5).with_message("This is an apple")) + .with_label(Label::new(9..15).with_message("This is an orange")) + .with_help("have you tried peeling the orange?") + .with_note("stop trying ... this is a fruitless endeavor") + .finish(Source::from(source)); + + let err: &dyn std::error::Error = &msg; // This bit compiling proves it is Error + + println!("{err}"); + println!("{err:?}"); + } }