feat(label): use macro magic instead of optional flag for optional labels

This commit is contained in:
Kat Marchán 2022-04-17 22:35:14 -07:00
parent 12bf4f78cf
commit 445cdef6e3
No known key found for this signature in database
GPG Key ID: AEB529C08A3C7E9E
4 changed files with 73 additions and 119 deletions

View File

@ -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))),
}),
}
}) })
}, },
) )

View File

@ -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 {

View File

@ -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

View File

@ -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>,
), ),
} }
} }