diff --git a/README.md b/README.md index 19a7917..302e0b5 100644 --- a/README.md +++ b/README.md @@ -697,6 +697,32 @@ let report: miette::Report = MyError { println!("{:?}", report.with_source_code("About something or another or yet another ...".to_string())); ``` +A collection can also be of `LabeledSpan` if you want to have different text +for different labels. Labels with no text will use the one from the `label` +attribute + +```rust +#[derive(Debug, Diagnostic, Error)] +#[error("oops!")] +struct MyError { + #[label("main issue")] + primary_span: SourceSpan, + + #[label(collection, "related to this")] + other_spans: Vec, // LabeledSpan +} + +let report: miette::Report = MyError { + primary_span: (6, 9).into(), + other_spans: vec![ + LabeledSpan::new(None, 19, 7), // Use default text `related to this` + LabeledSpan::new(Some("and also this".to_string()), 30, 11), // Use specific text + ], +}.into(); + +println!("{:?}", report.with_source_code("About something or another or yet another ...".to_string())); +``` + ### MSRV This crate requires rustc 1.70.0 or later. diff --git a/miette-derive/src/label.rs b/miette-derive/src/label.rs index fa4d29a..cd6994a 100644 --- a/miette-derive/src/label.rs +++ b/miette-derive/src/label.rs @@ -212,15 +212,17 @@ impl Labels { }; Some(quote! { let display = #display; - #labels_gen_var.extend( - self.#span.iter().map(|label| { - miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(label) - .map(|span| miette::LabeledSpan::new_with_span( - #display, - span.clone(), - )) - }) - ); + #labels_gen_var.extend(self.#span.iter().map(|label| { + miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(label) + .map(|span| { + use miette::macro_helpers::{ToLabelSpanWrapper,ToLabeledSpan}; + let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone()); + if #display.is_some() && labeled_span.label().is_none() { + labeled_span.set_label(#display) + } + labeled_span + }) + })); }) }); @@ -299,15 +301,17 @@ impl Labels { }; Some(quote! { let display = #display; - #labels_gen_var.extend( - #field.iter().map(|label| { - miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(label) - .map(|span| miette::LabeledSpan::new_with_span( - #display, - span.clone(), - )) - }) - ); + #labels_gen_var.extend(#field.iter().map(|label| { + miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(label) + .map(|span| { + use miette::macro_helpers::{ToLabelSpanWrapper,ToLabeledSpan}; + let mut labeled_span = ToLabelSpanWrapper::to_labeled_span(span.clone()); + if #display.is_some() && labeled_span.label().is_none() { + labeled_span.set_label(#display) + } + labeled_span + }) + })); }) }); let variant_name = ident.clone(); diff --git a/src/macro_helpers.rs b/src/macro_helpers.rs index 5520899..157f2b3 100644 --- a/src/macro_helpers.rs +++ b/src/macro_helpers.rs @@ -1,3 +1,5 @@ +use crate::protocol::{LabeledSpan, SourceSpan}; + // Huge thanks to @jam1gamer for this hack: // https://twitter.com/jam1garner/status/1515887996444323840 @@ -36,3 +38,24 @@ impl ToOption for &OptionalWrapper { Some(value) } } + +#[doc(hidden)] +#[derive(Debug)] +pub struct ToLabelSpanWrapper {} +pub trait ToLabeledSpan { + #[doc(hidden)] + fn to_labeled_span(span: T) -> LabeledSpan; +} +impl ToLabeledSpan for ToLabelSpanWrapper { + fn to_labeled_span(span: LabeledSpan) -> LabeledSpan { + span + } +} +impl ToLabeledSpan for ToLabelSpanWrapper +where + T: Into, +{ + fn to_labeled_span(span: T) -> LabeledSpan { + LabeledSpan::new_with_span(None, span.into()) + } +} diff --git a/src/protocol.rs b/src/protocol.rs index 9f312db..589cd0b 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -293,6 +293,11 @@ impl LabeledSpan { } } + /// Change the text of the label + pub fn set_label(&mut self, label: Option) { + self.label = label; + } + /// Makes a new label at specified span /// /// # Examples diff --git a/tests/test_derive_collection.rs b/tests/test_derive_collection.rs index accd0e1..952b505 100644 --- a/tests/test_derive_collection.rs +++ b/tests/test_derive_collection.rs @@ -195,6 +195,76 @@ fn attr_collection_of_range() { assert_eq!(err_span, expectation); } +#[test] +fn attr_collection_of_labeled_span_in_struct() { + #[derive(Debug, Diagnostic, Error)] + #[error("oops!")] + struct MyBad { + #[source_code] + src: NamedSource, + #[label("this bit here")] + highlight: SourceSpan, + #[label(collection, "then there")] + highlight2: Vec, + } + + let src = "source\n text\n here".to_string(); + let err = MyBad { + src: NamedSource::new("bad_file.rs", src), + highlight: (9, 4).into(), + highlight2: vec![ + LabeledSpan::new_with_span(Some("continuing here".to_string()), (1, 2)), + LabeledSpan::new_with_span(None, (3, 4)), + ], + }; + let mut label_iter = err.labels().unwrap(); + let err_span = label_iter.next().unwrap(); + let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize); + assert_eq!(err_span, expectation); + let err_span = label_iter.next().unwrap(); + let expectation = LabeledSpan::new(Some("continuing here".into()), 1usize, 2usize); + assert_eq!(err_span, expectation); + let err_span = label_iter.next().unwrap(); + let expectation = LabeledSpan::new(Some("then there".into()), 3usize, 4usize); + assert_eq!(err_span, expectation); +} + +#[test] +fn attr_collection_of_labeled_span_in_enum() { + #[derive(Debug, Diagnostic, Error)] + #[error("oops!")] + enum MyBad { + Only { + #[source_code] + src: NamedSource, + #[label("this bit here")] + highlight: SourceSpan, + #[label(collection, "then there")] + highlight2: Vec, + }, + } + + let src = "source\n text\n here".to_string(); + let err = MyBad::Only { + src: NamedSource::new("bad_file.rs", src), + highlight: (9, 4).into(), + highlight2: vec![ + LabeledSpan::new_with_span(Some("continuing here".to_string()), (1, 2)), + LabeledSpan::new_with_span(None, (3, 4)), + ], + }; + let mut label_iter = err.labels().unwrap(); + let err_span = label_iter.next().unwrap(); + let expectation = LabeledSpan::new(Some("this bit here".into()), 9usize, 4usize); + assert_eq!(err_span, expectation); + let err_span = label_iter.next().unwrap(); + let expectation = LabeledSpan::new(Some("continuing here".into()), 1usize, 2usize); + assert_eq!(err_span, expectation); + let err_span = label_iter.next().unwrap(); + let expectation = LabeledSpan::new(Some("then there".into()), 3usize, 4usize); + assert_eq!(err_span, expectation); +} + #[test] fn attr_collection_multi() { #[derive(Debug, Diagnostic, Error)]