feat(protocol): reference-based DiagnosticReport!

This commit is contained in:
Kat Marchán 2021-08-17 15:30:52 -07:00
parent b7deb2d002
commit f390520b45
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
6 changed files with 52 additions and 59 deletions

View File

@ -50,7 +50,6 @@ You can derive a Diagnostic from any `std::error::Error` type.
`thiserror` is a great way to define them so, and plays extremely nicely with `miette`! `thiserror` is a great way to define them so, and plays extremely nicely with `miette`!
*/ */
use std::sync::Arc;
use miette::Diagnostic; use miette::Diagnostic;
use thiserror::Error; use thiserror::Error;
@ -62,7 +61,7 @@ use thiserror::Error;
help("try doing it better next time?"), help("try doing it better next time?"),
)] )]
struct MyBad { struct MyBad {
src: Arc<String>, src: String,
// Snippets and highlights can be included in the diagnostic! // Snippets and highlights can be included in the diagnostic!
#[snippet(src, "This is the part that broke")] #[snippet(src, "This is the part that broke")]
snip: SourceSpan, snip: SourceSpan,
@ -101,7 +100,7 @@ fn pretend_this_is_main() -> Result<(), MyBad> {
let len = src.len(); let len = src.len();
Err(MyBad { Err(MyBad {
src: Arc::new(src), src,
snip: ("bad_file.rs", 0, len).into(), snip: ("bad_file.rs", 0, len).into(),
bad_bit: ("this bit here", 9, 3).into(), bad_bit: ("this bit here", 9, 3).into(),
}) })

View File

@ -176,12 +176,12 @@ impl Snippets {
.map(|msg| match msg { .map(|msg| match msg {
MemberOrString::String(str) => { MemberOrString::String(str) => {
quote! { quote! {
message: std::option::Option::Some(#str.into()), message: std::option::Option::Some(#str),
} }
} }
MemberOrString::Member(m) => { MemberOrString::Member(m) => {
quote! { quote! {
message: std::option::Option::Some(self.#m.clone()), message: std::option::Option::Some(self.#m.as_ref()),
} }
} }
}) })
@ -194,21 +194,20 @@ impl Snippets {
// Source field // Source field
let src_ident = &snippet.source; let src_ident = &snippet.source;
let src_ident = quote! { let src_ident = quote! {
// TODO: I don't like this. Think about it more and maybe improve protocol? source: &self.#src_ident,
source: self.#src_ident.clone(),
}; };
// Context // Context
let context = &snippet.snippet; let context = &snippet.snippet;
let context = quote! { let context = quote! {
context: self.#context.clone(), context: &self.#context,
}; };
// Highlights // Highlights
let highlights = snippet.highlights.iter().map(|highlight| { let highlights = snippet.highlights.iter().map(|highlight| {
let Highlight { highlight } = highlight; let Highlight { highlight } = highlight;
quote! { quote! {
self.#highlight.clone() &self.#highlight
} }
}); });
let highlights = quote! { let highlights = quote! {
@ -229,7 +228,7 @@ impl Snippets {
}); });
Some(quote! { Some(quote! {
#[allow(unused_variables)] #[allow(unused_variables)]
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet>>> { fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>> {
Some(Box::new(vec![ Some(Box::new(vec![
#(#snippets),* #(#snippets),*
].into_iter())) ].into_iter()))
@ -248,7 +247,7 @@ impl Snippets {
.map(|msg| match msg { .map(|msg| match msg {
MemberOrString::String(str) => { MemberOrString::String(str) => {
quote! { quote! {
message: std::option::Option::Some(#str.into()), message: std::option::Option::Some(#str),
} }
} }
MemberOrString::Member(m) => { MemberOrString::Member(m) => {
@ -259,7 +258,7 @@ impl Snippets {
} }
}; };
quote! { quote! {
message: std::option::Option::Some(#m.clone()), message: std::option::Option::Some(#m.as_ref()),
} }
} }
}) })
@ -278,7 +277,7 @@ impl Snippets {
}; };
let src_ident = quote! { let src_ident = quote! {
// TODO: I don't like this. Think about it more and maybe improve protocol? // TODO: I don't like this. Think about it more and maybe improve protocol?
source: #src_ident.clone(), source: #src_ident,
}; };
// Context // Context
@ -289,7 +288,7 @@ impl Snippets {
} }
}; };
let context = quote! { let context = quote! {
context: #context.clone(), context: #context,
}; };
// Highlights // Highlights
@ -302,7 +301,7 @@ impl Snippets {
} }
}; };
quote! { quote! {
#m.clone() #m
} }
}); });
let highlights = quote! { let highlights = quote! {
@ -346,7 +345,7 @@ impl Snippets {
}); });
Some(quote! { Some(quote! {
#[allow(unused_variables)] #[allow(unused_variables)]
fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet>>> { fn snippets(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::DiagnosticSnippet> + '_>> {
match self { match self {
#(#variant_arms)* #(#variant_arms)*
_ => std::option::Option::None, _ => std::option::Option::None,

View File

@ -4,7 +4,7 @@ that you can implement to get access to miette's (and related library's) full
reporting and such features. reporting and such features.
*/ */
use std::{fmt::Display, sync::Arc}; use std::fmt::Display;
use crate::MietteError; use crate::MietteError;
@ -36,7 +36,7 @@ pub trait Diagnostic: std::error::Error {
/// Additional contextual snippets. This is typically used for adding /// Additional contextual snippets. This is typically used for adding
/// marked-up source file output the way compilers often do. /// marked-up source file output the way compilers often do.
fn snippets(&self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet>>> { fn snippets(&self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet> + '_>> {
None None
} }
} }
@ -171,17 +171,17 @@ impl<'a> SpanContents for MietteSpanContents<'a> {
A snippet from a [Source] to be displayed with a message and possibly some highlights. A snippet from a [Source] to be displayed with a message and possibly some highlights.
*/ */
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DiagnosticSnippet { pub struct DiagnosticSnippet<'a> {
/// Explanation of this specific diagnostic snippet. /// Explanation of this specific diagnostic snippet.
pub message: Option<String>, pub message: Option<&'a str>,
/// A [Source] that can be used to read the actual text of a source. /// A [Source] that can be used to read the actual text of a source.
pub source: Arc<dyn Source>, pub source: &'a (dyn Source),
/// The primary [SourceSpan] where this diagnostic is located. /// The primary [SourceSpan] where this diagnostic is located.
pub context: SourceSpan, pub context: &'a SourceSpan,
/// Additional [SourceSpan]s that mark specific sections of the span, for /// Additional [SourceSpan]s that mark specific sections of the span, for
/// example, to underline specific text within the larger span. They're /// example, to underline specific text within the larger span. They're
/// paired with labels that should be applied to those sections. /// paired with labels that should be applied to those sections.
pub highlights: Option<Vec<SourceSpan>>, pub highlights: Option<Vec<&'a SourceSpan>>,
} }
/** /**

View File

@ -33,7 +33,7 @@ impl MietteReporter {
writeln!(f)?; writeln!(f)?;
let context_data = snippet let context_data = snippet
.source .source
.read_span(&snippet.context) .read_span(snippet.context)
.map_err(|_| fmt::Error)?; .map_err(|_| fmt::Error)?;
let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected"); let context = std::str::from_utf8(context_data.data()).expect("Bad utf8 detected");
let mut line = context_data.line(); let mut line = context_data.line();
@ -180,7 +180,8 @@ impl DiagnosticReporter for JokeReporter {
"miette, her eyes enormous: you {} miette? you {}? oh! oh! jail for mother! jail for mother for One Thousand Years!!!!", "miette, her eyes enormous: you {} miette? you {}? oh! oh! jail for mother! jail for mother for One Thousand Years!!!!",
diagnostic.code(), diagnostic.code(),
diagnostic.snippets().map(|snippets| { diagnostic.snippets().map(|snippets| {
snippets.map(|snippet| snippet.message).collect::<Option<Vec<String>>>() snippets.map(|snippet| snippet.message.map(|x| x.to_owned()))
.collect::<Option<Vec<String>>>()
}).flatten().map(|x| x.join(", ")).unwrap_or_else(||"try and cause miette to panic".into()) }).flatten().map(|x| x.join(", ")).unwrap_or_else(||"try and cause miette to panic".into())
)?; )?;

View File

@ -1,5 +1,3 @@
use std::sync::Arc;
use miette::{Diagnostic, Severity, SourceSpan}; use miette::{Diagnostic, Severity, SourceSpan};
use thiserror::Error; use thiserror::Error;
@ -166,11 +164,9 @@ fn test_snippet_named_struct() {
#[diagnostic(code(foo::bar::baz))] #[diagnostic(code(foo::bar::baz))]
struct Foo { struct Foo {
// The actual "source code" our contexts will be using. This can be // The actual "source code" our contexts will be using. This can be
// reused by multiple contexts! // reused by multiple contexts, and just needs to implement
// // miette::Source!
// The `Arc` is so you don't have to clone the entire thing into this src: String,
// Diagnostic. We just need to be able to read it~
src: Arc<String>,
// The "snippet" span. This is the span that will be displayed to // The "snippet" span. This is the span that will be displayed to
// users. It should be a big enough slice of the Source to provide // users. It should be a big enough slice of the Source to provide
@ -215,7 +211,7 @@ fn test_snippet_unnamed_struct() {
#[error("welp")] #[error("welp")]
#[diagnostic(code(foo::bar::baz))] #[diagnostic(code(foo::bar::baz))]
struct Foo( struct Foo(
Arc<String>, String,
#[snippet(0, "hi")] SourceSpan, #[snippet(0, "hi")] SourceSpan,
#[highlight(1)] SourceSpan, #[highlight(1)] SourceSpan,
#[highlight(1)] SourceSpan, #[highlight(1)] SourceSpan,
@ -235,7 +231,7 @@ fn test_snippet_enum() {
enum Foo { enum Foo {
#[diagnostic(code(foo::a))] #[diagnostic(code(foo::a))]
A { A {
src: Arc<String>, src: String,
#[snippet(src, "my_snippet.rs", "hi this is where the thing went wrong")] #[snippet(src, "my_snippet.rs", "hi this is where the thing went wrong")]
snip: SourceSpan, snip: SourceSpan,
#[highlight(snip, "var 1")] #[highlight(snip, "var 1")]
@ -249,7 +245,7 @@ fn test_snippet_enum() {
}, },
#[diagnostic(code(foo::b))] #[diagnostic(code(foo::b))]
B( B(
Arc<String>, String,
#[snippet(0, "my_snippet.rs", "hi")] SourceSpan, #[snippet(0, "my_snippet.rs", "hi")] SourceSpan,
#[highlight(1, "var 1")] SourceSpan, #[highlight(1, "var 1")] SourceSpan,
#[highlight(1, "var 2")] SourceSpan, #[highlight(1, "var 2")] SourceSpan,

View File

@ -1,12 +1,17 @@
use std::{fmt, sync::Arc}; use std::fmt;
use miette::{Diagnostic, DiagnosticReporter, DiagnosticSnippet, MietteError, MietteReporter}; use miette::{
Diagnostic, DiagnosticReporter, DiagnosticSnippet, MietteError, MietteReporter, SourceSpan,
};
use thiserror::Error; use thiserror::Error;
#[derive(Error)] #[derive(Error)]
#[error("oops!")] #[error("oops!")]
struct MyBad { struct MyBad {
snippets: Vec<DiagnosticSnippet>, message: String,
src: String,
ctx: SourceSpan,
highlight: SourceSpan,
} }
impl fmt::Debug for MyBad { impl fmt::Debug for MyBad {
@ -24,35 +29,28 @@ impl Diagnostic for MyBad {
Some(Box::new(&"try doing it better next time?")) Some(Box::new(&"try doing it better next time?"))
} }
fn snippets(&self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet>>> { fn snippets(&self) -> Option<Box<dyn Iterator<Item = DiagnosticSnippet> + '_>> {
Some(Box::new(self.snippets.clone().into_iter())) Some(Box::new(
vec![DiagnosticSnippet {
message: Some(self.message.as_ref()),
source: &self.src,
context: &self.ctx,
highlights: Some(vec![&self.highlight]),
}]
.into_iter(),
))
} }
} }
#[test]
fn basic() -> Result<(), MietteError> {
let err = MyBad {
snippets: Vec::new(),
};
let out = format!("{:?}", err);
assert_eq!(
"Error[oops::my::bad]: oops!\n\n﹦try doing it better next time?\n".to_string(),
out
);
Ok(())
}
#[test] #[test]
fn fancy() -> Result<(), MietteError> { fn fancy() -> Result<(), MietteError> {
let src = "source\n text\n here".to_string(); let src = "source\n text\n here".to_string();
let len = src.len(); let len = src.len();
let err = MyBad { let err = MyBad {
snippets: vec![DiagnosticSnippet { message: "This is the part that broke".into(),
message: Some("This is the part that broke".into()), src,
source: Arc::new(src), ctx: ("bad_file.rs", 0, len).into(),
highlights: Some(vec![("this bit here", 9, 4).into()]), highlight: ("this bit here", 9, 4).into(),
context: ("bad_file.rs", 0, len).into(),
}],
}; };
let out = format!("{:?}", err); let out = format!("{:?}", err);
// println!("{}", out); // println!("{}", out);