mirror of https://github.com/zkat/miette.git
feat(label): use macro magic instead of optional flag for optional labels
This commit is contained in:
parent
12bf4f78cf
commit
445cdef6e3
|
|
@ -18,35 +18,21 @@ pub struct Labels(Vec<Label>);
|
||||||
|
|
||||||
struct Label {
|
struct Label {
|
||||||
label: Option<Display>,
|
label: Option<Display>,
|
||||||
optional: bool,
|
ty: syn::Type,
|
||||||
span: syn::Member,
|
span: syn::Member,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LabelAttr {
|
struct LabelAttr {
|
||||||
label: Option<Display>,
|
label: Option<Display>,
|
||||||
optional: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for LabelAttr {
|
impl Parse for LabelAttr {
|
||||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||||
let la = input.lookahead1();
|
let la = input.lookahead1();
|
||||||
let (label, optional) = if la.peek(syn::token::Paren) {
|
let label = if la.peek(syn::token::Paren) {
|
||||||
// #[label("{}", x)]
|
// #[label("{}", x)]
|
||||||
let content;
|
let content;
|
||||||
parenthesized!(content in input);
|
parenthesized!(content in input);
|
||||||
let optional = if content.peek(syn::Ident) {
|
|
||||||
let ident = content.parse::<syn::Ident>()?;
|
|
||||||
if ident == "optional" {
|
|
||||||
if content.peek(syn::Token![,]) {
|
|
||||||
content.parse::<syn::Token![,]>()?;
|
|
||||||
}
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
if content.peek(syn::LitStr) {
|
if content.peek(syn::LitStr) {
|
||||||
let fmt = content.parse()?;
|
let fmt = content.parse()?;
|
||||||
let args = if content.is_empty() {
|
let args = if content.is_empty() {
|
||||||
|
|
@ -59,27 +45,22 @@ impl Parse for LabelAttr {
|
||||||
args,
|
args,
|
||||||
has_bonus_display: false,
|
has_bonus_display: false,
|
||||||
};
|
};
|
||||||
(Some(display), optional)
|
Some(display)
|
||||||
} else if optional {
|
|
||||||
(None, optional)
|
|
||||||
} else {
|
} else {
|
||||||
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The first argument must be a literal string or the identifier `optional`"));
|
return Err(syn::Error::new(input.span(), "Invalid argument to label() attribute. The first argument must be a literal string."));
|
||||||
}
|
}
|
||||||
} else if la.peek(Token![=]) {
|
} else if la.peek(Token![=]) {
|
||||||
// #[label = "blabla"]
|
// #[label = "blabla"]
|
||||||
input.parse::<Token![=]>()?;
|
input.parse::<Token![=]>()?;
|
||||||
(
|
Some(Display {
|
||||||
Some(Display {
|
fmt: input.parse()?,
|
||||||
fmt: input.parse()?,
|
args: TokenStream::new(),
|
||||||
args: TokenStream::new(),
|
has_bonus_display: false,
|
||||||
has_bonus_display: false,
|
})
|
||||||
}),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(None, false)
|
None
|
||||||
};
|
};
|
||||||
Ok(LabelAttr { label, optional })
|
Ok(LabelAttr { label })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,12 +88,11 @@ impl Labels {
|
||||||
span: field.span(),
|
span: field.span(),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let LabelAttr { label, optional } =
|
let LabelAttr { label } = syn::parse2::<LabelAttr>(attr.tokens.clone())?;
|
||||||
syn::parse2::<LabelAttr>(attr.tokens.clone())?;
|
|
||||||
labels.push(Label {
|
labels.push(Label {
|
||||||
label,
|
label,
|
||||||
span,
|
span,
|
||||||
optional,
|
ty: field.ty.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -127,43 +107,23 @@ 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 = self.0.iter().map(|highlight| {
|
||||||
let Label {
|
let Label { span, label, ty } = highlight;
|
||||||
span,
|
|
||||||
label,
|
|
||||||
optional,
|
|
||||||
} = highlight;
|
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
if let Some(display) = label {
|
if let Some(display) = label {
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
if *optional {
|
|
||||||
quote! {
|
|
||||||
self.#span.clone().map(|#var|
|
|
||||||
miette::LabeledSpan::new_with_span(
|
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
|
||||||
#var,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
Some(miette::LabeledSpan::new_with_span(
|
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
|
||||||
self.#span.clone(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if *optional {
|
|
||||||
quote! {
|
quote! {
|
||||||
self.#span.clone().map(|#var|
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
miette::LabeledSpan::new_with_span(
|
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||||
std::option::Option::None,
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
#var,
|
#var.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
Some(miette::LabeledSpan::new_with_span(
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(&self.#span)
|
||||||
|
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||||
std::option::Option::None,
|
std::option::Option::None,
|
||||||
self.#span.clone(),
|
#var.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -171,6 +131,7 @@ impl Labels {
|
||||||
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;
|
||||||
let Self #display_pat = self;
|
let Self #display_pat = self;
|
||||||
std::option::Option::Some(Box::new(vec![
|
std::option::Option::Some(Box::new(vec![
|
||||||
#(#labels),*
|
#(#labels),*
|
||||||
|
|
@ -186,59 +147,46 @@ 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 variant_labels = labels.0.iter().map(|label| {
|
||||||
let Label { span, label, optional } = label;
|
let Label { span, label, ty } = label;
|
||||||
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, .. }) => {
|
||||||
format_ident!("_{}", index)
|
format_ident!("_{}", index)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let var = quote! { __miette_internal_var };
|
let var = quote! { __miette_internal_var };
|
||||||
if let Some(display) = label {
|
if let Some(display) = label {
|
||||||
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
let (fmt, args) = display.expand_shorthand_cloned(&display_members);
|
||||||
if *optional {
|
|
||||||
quote! {
|
quote! {
|
||||||
#field.clone().map(|#var|
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||||
miette::LabeledSpan::new_with_span(
|
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
std::option::Option::Some(format!(#fmt #args)),
|
||||||
#var,
|
#var.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
Some(miette::LabeledSpan::new_with_span(
|
miette::macro_helpers::OptionalWrapper::<#ty>::new().to_option(#field)
|
||||||
std::option::Option::Some(format!(#fmt #args)),
|
.map(|#var| miette::LabeledSpan::new_with_span(
|
||||||
#field.clone(),
|
std::option::Option::None,
|
||||||
|
#var.clone(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if *optional {
|
});
|
||||||
quote! {
|
let variant_name = ident.clone();
|
||||||
#field.clone().map(|#var|
|
match &fields {
|
||||||
miette::LabeledSpan::new_with_span(
|
syn::Fields::Unit => None,
|
||||||
std::option::Option::None,
|
_ => Some(quote! {
|
||||||
#var,
|
Self::#variant_name #display_pat => {
|
||||||
))
|
use miette::macro_helpers::ToOption;
|
||||||
}
|
std::option::Option::Some(std::boxed::Box::new(vec![
|
||||||
} else {
|
#(#variant_labels),*
|
||||||
quote! {
|
].into_iter().filter(Option::is_some).map(Option::unwrap)))
|
||||||
Some(miette::LabeledSpan::new_with_span(
|
}
|
||||||
std::option::Option::None,
|
}),
|
||||||
#field.clone(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
let variant_name = ident.clone();
|
|
||||||
match &fields {
|
|
||||||
syn::Fields::Unit => None,
|
|
||||||
_ => Some(quote! {
|
|
||||||
Self::#variant_name #display_pat => std::option::Option::Some(std::boxed::Box::new(vec![
|
|
||||||
#(#variant_labels),*
|
|
||||||
].into_iter().filter(Option::is_some).map(Option::unwrap))),
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -369,12 +369,12 @@
|
||||||
//! #[label("This is bad")]
|
//! #[label("This is bad")]
|
||||||
//! snip2: (usize, usize), // `(usize, usize)` is `Into<SourceSpan>`!
|
//! snip2: (usize, usize), // `(usize, usize)` is `Into<SourceSpan>`!
|
||||||
//!
|
//!
|
||||||
//! // Snippets can be optional, by tagging them as such:
|
//! // Snippets can be optional, by using Option:
|
||||||
//! #[label(optional, "some text")]
|
//! #[label("some text")]
|
||||||
//! snip3: Option<SourceSpan>,
|
//! snip3: Option<SourceSpan>,
|
||||||
//!
|
//!
|
||||||
//! // with or without label text
|
//! // with or without label text
|
||||||
//! #[label(optional)]
|
//! #[label]
|
||||||
//! snip4: Option<SourceSpan>,
|
//! snip4: Option<SourceSpan>,
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
@ -407,7 +407,7 @@
|
||||||
//! #[diagnostic()]
|
//! #[diagnostic()]
|
||||||
//! struct Foo {
|
//! struct Foo {
|
||||||
//! #[help]
|
//! #[help]
|
||||||
//! advice: Option<String>,
|
//! advice: Option<String>, // Can also just be `String`
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! let err = Foo {
|
//! let err = Foo {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
|
// Huge thanks to @jam1gamer for this hack:
|
||||||
|
// https://twitter.com/jam1garner/status/1515887996444323840
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait IsOption {}
|
pub trait IsOption {}
|
||||||
impl <T> IsOption for Option<T> {}
|
impl<T> IsOption for Option<T> {}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
@ -18,7 +21,10 @@ pub trait ToOption {
|
||||||
fn to_option<T>(self, value: T) -> Option<T>;
|
fn to_option<T>(self, value: T) -> Option<T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> OptionalWrapper<T> where T: IsOption {
|
impl<T> OptionalWrapper<T>
|
||||||
|
where
|
||||||
|
T: IsOption,
|
||||||
|
{
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn to_option(self, value: &T) -> &T {
|
pub fn to_option(self, value: &T) -> &T {
|
||||||
value
|
value
|
||||||
|
|
|
||||||
|
|
@ -319,9 +319,9 @@ fn test_snippet_named_struct() {
|
||||||
var2: (usize, usize),
|
var2: (usize, usize),
|
||||||
#[label]
|
#[label]
|
||||||
var3: (usize, usize),
|
var3: (usize, usize),
|
||||||
#[label(optional, "var 4")]
|
#[label("var 4")]
|
||||||
var4: Option<(usize, usize)>,
|
var4: Option<(usize, usize)>,
|
||||||
#[label(optional)]
|
#[label]
|
||||||
var5: Option<(usize, usize)>,
|
var5: Option<(usize, usize)>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -336,8 +336,8 @@ fn test_snippet_unnamed_struct() {
|
||||||
#[label("{0}")] SourceSpan,
|
#[label("{0}")] SourceSpan,
|
||||||
#[label = "idk"] SourceSpan,
|
#[label = "idk"] SourceSpan,
|
||||||
#[label] SourceSpan,
|
#[label] SourceSpan,
|
||||||
#[label(optional, "foo")] Option<SourceSpan>,
|
#[label("foo")] Option<SourceSpan>,
|
||||||
#[label(optional)] Option<SourceSpan>,
|
#[label] Option<SourceSpan>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -358,9 +358,9 @@ fn test_snippet_enum() {
|
||||||
var1: SourceSpan,
|
var1: SourceSpan,
|
||||||
#[label]
|
#[label]
|
||||||
var2: SourceSpan,
|
var2: SourceSpan,
|
||||||
#[label(optional, "var 3")]
|
#[label("var 3")]
|
||||||
var3: Option<(usize, usize)>,
|
var3: Option<(usize, usize)>,
|
||||||
#[label(optional)]
|
#[label]
|
||||||
var4: Option<(usize, usize)>,
|
var4: Option<(usize, usize)>,
|
||||||
},
|
},
|
||||||
#[diagnostic(code(foo::b))]
|
#[diagnostic(code(foo::b))]
|
||||||
|
|
@ -370,8 +370,8 @@ fn test_snippet_enum() {
|
||||||
#[label("{1}")] SourceSpan,
|
#[label("{1}")] SourceSpan,
|
||||||
#[label = "blorp"] SourceSpan,
|
#[label = "blorp"] SourceSpan,
|
||||||
#[label] SourceSpan,
|
#[label] SourceSpan,
|
||||||
#[label(optional, "foo")] Option<SourceSpan>,
|
#[label("foo")] Option<SourceSpan>,
|
||||||
#[label(optional)] Option<SourceSpan>,
|
#[label] Option<SourceSpan>,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue