mirror of https://github.com/zkat/miette.git
feat(collection): add support for collection of labels (#341)
Fixes: https://github.com/zkat/miette/issues/315 Allow errors to have a number of labels determined at runtime. An example of this is when the rust compiler labels all the arms of a `match` expression when one of them has an incompatible type To allow customization of the text for each label in a collection, add support for using LabeledSpan in collections instead of just regular spans
This commit is contained in:
parent
6f09250cca
commit
03060245d8
52
README.md
52
README.md
|
|
@ -49,6 +49,7 @@ libraries and such might not want.
|
||||||
- [... handler options](#-handler-options)
|
- [... handler options](#-handler-options)
|
||||||
- [... dynamic diagnostics](#-dynamic-diagnostics)
|
- [... dynamic diagnostics](#-dynamic-diagnostics)
|
||||||
- [... syntax highlighting](#-syntax-highlighting)
|
- [... syntax highlighting](#-syntax-highlighting)
|
||||||
|
- [... collection of labels](#-collection-of-labels)
|
||||||
- [Acknowledgements](#acknowledgements)
|
- [Acknowledgements](#acknowledgements)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
|
|
@ -671,6 +672,57 @@ trait to [`MietteHandlerOpts`] by calling the
|
||||||
[`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
[`with_syntax_highlighting`](MietteHandlerOpts::with_syntax_highlighting)
|
||||||
method. See the [`highlighters`] module docs for more details.
|
method. See the [`highlighters`] module docs for more details.
|
||||||
|
|
||||||
|
#### ... collection of labels
|
||||||
|
|
||||||
|
When the number of labels is unknown, you can use a collection of `SourceSpan`
|
||||||
|
(or any type convertible into `SourceSpan`). For this, add the `collection`
|
||||||
|
parameter to `label` and use any type than can be iterated over for the field.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyError {
|
||||||
|
#[label("main issue")]
|
||||||
|
primary_span: SourceSpan,
|
||||||
|
|
||||||
|
#[label(collection, "related to this")]
|
||||||
|
other_spans: Vec<Range<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let report: miette::Report = MyError {
|
||||||
|
primary_span: (6, 9).into(),
|
||||||
|
other_spans: vec![19..26, 30..41],
|
||||||
|
}.into();
|
||||||
|
|
||||||
|
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>, // 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
|
### MSRV
|
||||||
|
|
||||||
This crate requires rustc 1.70.0 or later.
|
This crate requires rustc 1.70.0 or later.
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,23 @@ use crate::{
|
||||||
|
|
||||||
pub struct Labels(Vec<Label>);
|
pub struct Labels(Vec<Label>);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
enum LabelType {
|
||||||
|
Default,
|
||||||
|
Primary,
|
||||||
|
Collection,
|
||||||
|
}
|
||||||
|
|
||||||
struct Label {
|
struct Label {
|
||||||
label: Option<Display>,
|
label: Option<Display>,
|
||||||
ty: syn::Type,
|
ty: syn::Type,
|
||||||
span: syn::Member,
|
span: syn::Member,
|
||||||
primary: bool,
|
lbl_ty: LabelType,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LabelAttr {
|
struct LabelAttr {
|
||||||
label: Option<Display>,
|
label: Option<Display>,
|
||||||
primary: bool,
|
lbl_ty: LabelType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for LabelAttr {
|
impl Parse for LabelAttr {
|
||||||
|
|
@ -42,20 +49,24 @@ impl Parse for LabelAttr {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let la = input.lookahead1();
|
let la = input.lookahead1();
|
||||||
let (primary, label) = if la.peek(syn::token::Paren) {
|
let (lbl_ty, label) = if la.peek(syn::token::Paren) {
|
||||||
// #[label(primary?, "{}", x)]
|
// #[label(primary?, "{}", x)]
|
||||||
let content;
|
let content;
|
||||||
parenthesized!(content in input);
|
parenthesized!(content in input);
|
||||||
|
|
||||||
let primary = if content.peek(syn::Ident) {
|
let attr = match content.parse::<Option<syn::Ident>>()? {
|
||||||
let ident: syn::Ident = content.parse()?;
|
Some(ident) if ident == "primary" => {
|
||||||
if ident != "primary" {
|
let _ = content.parse::<Token![,]>();
|
||||||
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or the keyword `primary`."));
|
LabelType::Primary
|
||||||
}
|
}
|
||||||
let _ = content.parse::<Token![,]>();
|
Some(ident) if ident == "collection" => {
|
||||||
true
|
let _ = content.parse::<Token![,]>();
|
||||||
} else {
|
LabelType::Collection
|
||||||
false
|
}
|
||||||
|
Some(_) => {
|
||||||
|
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or either the keyword `primary` or `collection`."));
|
||||||
|
}
|
||||||
|
_ => LabelType::Default,
|
||||||
};
|
};
|
||||||
|
|
||||||
if content.peek(syn::LitStr) {
|
if content.peek(syn::LitStr) {
|
||||||
|
|
@ -70,17 +81,17 @@ impl Parse for LabelAttr {
|
||||||
args,
|
args,
|
||||||
has_bonus_display: false,
|
has_bonus_display: false,
|
||||||
};
|
};
|
||||||
(primary, Some(display))
|
(attr, Some(display))
|
||||||
} else if !primary {
|
} else if !content.is_empty() {
|
||||||
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or the keyword `primary`."));
|
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The argument must be a literal string or either the keyword `primary` or `collection`."));
|
||||||
} else {
|
} else {
|
||||||
(primary, None)
|
(attr, None)
|
||||||
}
|
}
|
||||||
} else if la.peek(Token![=]) {
|
} else if la.peek(Token![=]) {
|
||||||
// #[label = "blabla"]
|
// #[label = "blabla"]
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
(
|
(
|
||||||
false,
|
LabelType::Default,
|
||||||
Some(Display {
|
Some(Display {
|
||||||
fmt: input.parse()?,
|
fmt: input.parse()?,
|
||||||
args: TokenStream::new(),
|
args: TokenStream::new(),
|
||||||
|
|
@ -88,9 +99,9 @@ impl Parse for LabelAttr {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(false, None)
|
(LabelType::Default, None)
|
||||||
};
|
};
|
||||||
Ok(LabelAttr { label, primary })
|
Ok(LabelAttr { label, lbl_ty })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,10 +130,14 @@ impl Labels {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
let LabelAttr { label, primary } =
|
let LabelAttr { label, lbl_ty } =
|
||||||
syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
|
syn::parse2::<LabelAttr>(attr.meta.to_token_stream())?;
|
||||||
|
|
||||||
if primary && labels.iter().any(|l: &Label| l.primary) {
|
if lbl_ty == LabelType::Primary
|
||||||
|
&& labels
|
||||||
|
.iter()
|
||||||
|
.any(|l: &Label| l.lbl_ty == LabelType::Primary)
|
||||||
|
{
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
field.span(),
|
field.span(),
|
||||||
"Cannot have more than one primary label.",
|
"Cannot have more than one primary label.",
|
||||||
|
|
@ -133,7 +148,7 @@ impl Labels {
|
||||||
label,
|
label,
|
||||||
span,
|
span,
|
||||||
ty: field.ty.clone(),
|
ty: field.ty.clone(),
|
||||||
primary,
|
lbl_ty,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -147,46 +162,82 @@ impl Labels {
|
||||||
|
|
||||||
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
pub(crate) fn gen_struct(&self, fields: &syn::Fields) -> Option<TokenStream> {
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
let labels = self.0.iter().map(|highlight| {
|
let labels_gen_var = quote! { labels };
|
||||||
|
let labels = self.0.iter().filter_map(|highlight| {
|
||||||
let Label {
|
let Label {
|
||||||
span,
|
span,
|
||||||
label,
|
label,
|
||||||
ty,
|
ty,
|
||||||
primary,
|
lbl_ty,
|
||||||
} = highlight;
|
} = highlight;
|
||||||
|
if *lbl_ty == LabelType::Collection {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
let ctor = if *primary {
|
let display = if let Some(display) = label {
|
||||||
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
|
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
||||||
|
} else {
|
||||||
|
quote! { std::option::Option::None }
|
||||||
|
};
|
||||||
|
let ctor = if *lbl_ty == LabelType::Primary {
|
||||||
quote! { miette::LabeledSpan::new_primary_with_span }
|
quote! { miette::LabeledSpan::new_primary_with_span }
|
||||||
} else {
|
} else {
|
||||||
quote! { miette::LabeledSpan::new_with_span }
|
quote! { miette::LabeledSpan::new_with_span }
|
||||||
};
|
};
|
||||||
if let Some(display) = label {
|
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
Some(quote! {
|
||||||
quote! {
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
.map(|#var| #ctor(
|
||||||
.map(|#var| #ctor(
|
#display,
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
#var.clone(),
|
||||||
#var.clone(),
|
))
|
||||||
))
|
})
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
|
||||||
.map(|#var| #ctor(
|
|
||||||
std::option::Option::None,
|
|
||||||
#var.clone(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
let collections = self.0.iter().filter_map(|label| {
|
||||||
|
let Label {
|
||||||
|
span,
|
||||||
|
label,
|
||||||
|
ty,
|
||||||
|
lbl_ty,
|
||||||
|
} = label;
|
||||||
|
if *lbl_ty != LabelType::Collection {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let display = if let Some(display) = label {
|
||||||
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
|
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
||||||
|
} else {
|
||||||
|
quote! { std::option::Option::None }
|
||||||
|
};
|
||||||
|
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| {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
|
fn labels(&self) -> std::option::Option<std::boxed::Box<dyn std::iter::Iterator<Item = miette::LabeledSpan> + '_>> {
|
||||||
use miette::macro_helpers::ToOption;
|
use miette::macro_helpers::ToOption;
|
||||||
let Self #display_pat = self;
|
let Self #display_pat = self;
|
||||||
std::option::Option::Some(Box::new(vec![
|
|
||||||
|
let mut #labels_gen_var = vec![
|
||||||
#(#labels),*
|
#(#labels),*
|
||||||
].into_iter().filter(Option::is_some).map(Option::unwrap)))
|
];
|
||||||
|
#(#collections)*
|
||||||
|
|
||||||
|
std::option::Option::Some(Box::new(#labels_gen_var.into_iter().filter(Option::is_some).map(Option::unwrap)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -198,8 +249,12 @@ impl Labels {
|
||||||
|ident, fields, DiagnosticConcreteArgs { labels, .. }| {
|
|ident, fields, DiagnosticConcreteArgs { labels, .. }| {
|
||||||
let (display_pat, display_members) = display_pat_members(fields);
|
let (display_pat, display_members) = display_pat_members(fields);
|
||||||
labels.as_ref().and_then(|labels| {
|
labels.as_ref().and_then(|labels| {
|
||||||
let variant_labels = labels.0.iter().map(|label| {
|
let labels_gen_var = quote! { labels };
|
||||||
let Label { span, label, ty, primary } = label;
|
let variant_labels = labels.0.iter().filter_map(|label| {
|
||||||
|
let Label { span, label, ty, lbl_ty } = label;
|
||||||
|
if *lbl_ty == LabelType::Collection {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
let field = match &span {
|
let field = match &span {
|
||||||
syn::Member::Named(ident) => ident.clone(),
|
syn::Member::Named(ident) => ident.clone(),
|
||||||
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
|
@ -207,29 +262,57 @@ impl Labels {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
let ctor = if *primary {
|
let display = if let Some(display) = label {
|
||||||
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
|
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
||||||
|
} else {
|
||||||
|
quote! { std::option::Option::None }
|
||||||
|
};
|
||||||
|
let ctor = if *lbl_ty == LabelType::Primary {
|
||||||
quote! { miette::LabeledSpan::new_primary_with_span }
|
quote! { miette::LabeledSpan::new_primary_with_span }
|
||||||
} else {
|
} else {
|
||||||
quote! { miette::LabeledSpan::new_with_span }
|
quote! { miette::LabeledSpan::new_with_span }
|
||||||
};
|
};
|
||||||
if let Some(display) = label {
|
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
Some(quote! {
|
||||||
quote! {
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
.map(|#var| #ctor(
|
||||||
.map(|#var| #ctor(
|
#display,
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
#var.clone(),
|
||||||
#var.clone(),
|
))
|
||||||
))
|
})
|
||||||
}
|
});
|
||||||
} else {
|
let collections = labels.0.iter().filter_map(|label| {
|
||||||
quote! {
|
let Label { span, label, ty, lbl_ty } = label;
|
||||||
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
if *lbl_ty != LabelType::Collection {
|
||||||
.map(|#var| #ctor(
|
return None;
|
||||||
std::option::Option::None,
|
|
||||||
#var.clone(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let field = match &span {
|
||||||
|
syn::Member::Named(ident) => ident.clone(),
|
||||||
|
syn::Member::Unnamed(syn::Index { index, .. }) => {
|
||||||
|
format_ident!("_{}", index)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let display = if let Some(display) = label {
|
||||||
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
|
quote! { std::option::Option::Some(format!(#fmt #args)) }
|
||||||
|
} else {
|
||||||
|
quote! { std::option::Option::None }
|
||||||
|
};
|
||||||
|
Some(quote! {
|
||||||
|
let display = #display;
|
||||||
|
#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();
|
let variant_name = ident.clone();
|
||||||
match &fields {
|
match &fields {
|
||||||
|
|
@ -237,9 +320,11 @@ impl Labels {
|
||||||
_ => Some(quote! {
|
_ => Some(quote! {
|
||||||
Self::#variant_name #display_pat => {
|
Self::#variant_name #display_pat => {
|
||||||
use miette::macro_helpers::ToOption;
|
use miette::macro_helpers::ToOption;
|
||||||
std::option::Option::Some(std::boxed::Box::new(vec![
|
let mut #labels_gen_var = vec![
|
||||||
#(#variant_labels),*
|
#(#variant_labels),*
|
||||||
].into_iter().filter(Option::is_some).map(Option::unwrap)))
|
];
|
||||||
|
#(#collections)*
|
||||||
|
std::option::Option::Some(std::boxed::Box::new(#labels_gen_var.into_iter().filter(Option::is_some).map(Option::unwrap)))
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::protocol::{LabeledSpan, SourceSpan};
|
||||||
|
|
||||||
// Huge thanks to @jam1gamer for this hack:
|
// Huge thanks to @jam1gamer for this hack:
|
||||||
// https://twitter.com/jam1garner/status/1515887996444323840
|
// https://twitter.com/jam1garner/status/1515887996444323840
|
||||||
|
|
||||||
|
|
@ -36,3 +38,24 @@ impl<T> ToOption for &OptionalWrapper<T> {
|
||||||
Some(value)
|
Some(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ToLabelSpanWrapper {}
|
||||||
|
pub trait ToLabeledSpan<T> {
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn to_labeled_span(span: T) -> LabeledSpan;
|
||||||
|
}
|
||||||
|
impl ToLabeledSpan<LabeledSpan> for ToLabelSpanWrapper {
|
||||||
|
fn to_labeled_span(span: LabeledSpan) -> LabeledSpan {
|
||||||
|
span
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> ToLabeledSpan<T> for ToLabelSpanWrapper
|
||||||
|
where
|
||||||
|
T: Into<SourceSpan>,
|
||||||
|
{
|
||||||
|
fn to_labeled_span(span: T) -> LabeledSpan {
|
||||||
|
LabeledSpan::new_with_span(None, span.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -293,6 +293,11 @@ impl LabeledSpan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Change the text of the label
|
||||||
|
pub fn set_label(&mut self, label: Option<String>) {
|
||||||
|
self.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
/// Makes a new label at specified span
|
/// Makes a new label at specified span
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,306 @@
|
||||||
|
use std::{
|
||||||
|
collections::{LinkedList, VecDeque},
|
||||||
|
ops::Range,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Testing of the `diagnostic` attr used by derive(Diagnostic)
|
||||||
|
use miette::{Diagnostic, LabeledSpan, NamedSource, SourceSpan};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attr_collection_in_enum() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
enum MyBad {
|
||||||
|
Only {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource<String>,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
#[label(collection, "and here")]
|
||||||
|
highlight2: Vec<SourceSpan>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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![(1, 2).into(), (3, 4).into()],
|
||||||
|
};
|
||||||
|
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("and here".into()), 1usize, 2usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
let err_span = label_iter.next().unwrap();
|
||||||
|
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attr_collection_in_struct() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource<String>,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
#[label(collection, "and here")]
|
||||||
|
highlight2: Vec<SourceSpan>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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![(1, 2).into(), (3, 4).into()],
|
||||||
|
};
|
||||||
|
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("and here".into()), 1usize, 2usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
let err_span = label_iter.next().unwrap();
|
||||||
|
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attr_collection_as_deque() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource<String>,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
#[label(collection, "and here")]
|
||||||
|
highlight2: VecDeque<SourceSpan>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (9, 4).into(),
|
||||||
|
highlight2: VecDeque::from([(1, 2).into(), (3, 4).into()]),
|
||||||
|
};
|
||||||
|
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("and here".into()), 1usize, 2usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
let err_span = label_iter.next().unwrap();
|
||||||
|
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attr_collection_as_linked_list() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource<String>,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
#[label(collection, "and here")]
|
||||||
|
highlight2: LinkedList<SourceSpan>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (9, 4).into(),
|
||||||
|
highlight2: LinkedList::from([(1, 2).into(), (3, 4).into()]),
|
||||||
|
};
|
||||||
|
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("and here".into()), 1usize, 2usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
let err_span = label_iter.next().unwrap();
|
||||||
|
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attr_collection_of_tuple() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource<String>,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
#[label(collection, "and here")]
|
||||||
|
highlight2: Vec<(usize, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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![(1, 2), (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("and here".into()), 1usize, 2usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
let err_span = label_iter.next().unwrap();
|
||||||
|
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn attr_collection_of_range() {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource<String>,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
#[label(collection, "and here")]
|
||||||
|
highlight2: Vec<Range<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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![1..3, 3..7],
|
||||||
|
};
|
||||||
|
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("and here".into()), 1usize, 2usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
let err_span = label_iter.next().unwrap();
|
||||||
|
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||||
|
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<String>,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
#[label(collection, "then there")]
|
||||||
|
highlight2: Vec<LabeledSpan>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<String>,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
#[label(collection, "then there")]
|
||||||
|
highlight2: Vec<LabeledSpan>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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)]
|
||||||
|
#[error("oops!")]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource<String>,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
#[label(collection, "and here")]
|
||||||
|
highlight2: Vec<SourceSpan>,
|
||||||
|
#[label(collection, "and there")]
|
||||||
|
highlight3: Vec<SourceSpan>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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![(1, 2).into(), (3, 4).into()],
|
||||||
|
highlight3: vec![(5, 6).into(), (7, 8).into()],
|
||||||
|
};
|
||||||
|
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("and here".into()), 1usize, 2usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
let err_span = label_iter.next().unwrap();
|
||||||
|
let expectation = LabeledSpan::new(Some("and here".into()), 3usize, 4usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
let err_span = label_iter.next().unwrap();
|
||||||
|
let expectation = LabeledSpan::new(Some("and there".into()), 5usize, 6usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
let err_span = label_iter.next().unwrap();
|
||||||
|
let expectation = LabeledSpan::new(Some("and there".into()), 7usize, 8usize);
|
||||||
|
assert_eq!(err_span, expectation);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue