Skip to content

Commit

Permalink
feat: support #[error(transparent)] in traits
Browse files Browse the repository at this point in the history
closes #17
  • Loading branch information
onkoe committed Aug 21, 2024
1 parent 94699b0 commit e304f68
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 16 deletions.
60 changes: 47 additions & 13 deletions macros/src/parser/variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ impl FromAttributeCheck {
/// checks that:
/// - variant has an error tag.
/// - the error tag should either:
/// - have a string, or
/// - extract a string when on a `from` variant. (TODO(#15))
/// - have a string, or be
/// - #[error(transparent)] for a from variant
pub(crate) struct ErrorAttributeCheck {
/// not all variants use a `#[from]` attr
ident: Ident,
Expand All @@ -140,37 +140,55 @@ impl ErrorAttributeCheck {

// warning: this mutates error_attributes (the iterator is being consumed)
let (first, second) = (error_attributes.next(), error_attributes.next());
let slice = &[first, second];

// check if we got any problems. otherwise, grab the metalist for f-string
let err_attr_args = match slice {
[None, _] => {
let error_attribute = match (first, second) {
(None, _) => {
return Err(Self::err_missing_error_attr(span));
}
[Some(_), Some(second_err_attr)] => {
(Some(_), Some(second_err_attr)) => {
return Err(Self::err_multiple_error_attrs(second_err_attr));
}
[Some(attr), None] => {
(Some(attr), None) => {
let Meta::List(ref attr_args) = attr.meta else {
return Err(Self::err_nothing_to_display(attr));
// give a more specific error, depending on if we have a `#[from]` field
return Err(if from_attribute.is_some() {
Self::err_nothing_to_display_from(attr)
} else {
Self::err_nothing_to_display(attr)
});
};

// make sure the attribute has something inside it
if attr_args.tokens.is_empty() {
return Err(Self::err_nothing_to_display(attr));
}

attr_args
let transparent_check =
{ attr_args.tokens.to_string() == ErrorAttribute::TRANSPARENT_LITERAL };

// let transparent_attr_path =
// &util::create_path(span, &[ErrorAttribute::TRANSPARENT_LITERAL]);

// check if we're stringy or just have `transparent`
if transparent_check {
// ok now make sure we have a `#[from]` attr
if from_attribute.is_none() {
return Err(Self::err_transparent_requires_from_variant(attr));
}

ErrorAttribute::Transparent
} else {
ErrorAttribute::Stringy(attr_args.tokens.clone())
}
}
};

Ok(Self {
ident,
fields,
from_attribute,
error_attribute: ErrorAttribute {
format_string: err_attr_args.tokens.clone(),
},
error_attribute,
})
}

Expand Down Expand Up @@ -202,7 +220,23 @@ impl ErrorAttributeCheck {
syn::Error::new_spanned(
attr,
"An `#[error(...)]` attribute must contain a format_args!() \
f-string for implementing Display.",
f-string to implement `Display`.",
)
}

fn err_nothing_to_display_from(attr: &Attribute) -> syn::Error {
syn::Error::new_spanned(
attr,
"A `#[from]` variant's `#[error(...)]` attribute must contain \
`transparent` or a valid format_args!() f-string.",
)
}

fn err_transparent_requires_from_variant(attr: &Attribute) -> syn::Error {
syn::Error::new_spanned(
attr,
"An `#[error(transparent)]` attribute requires a field marked with \
`#[from]`.",
)
}
}
Expand Down
40 changes: 37 additions & 3 deletions macros/src/traits/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,23 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;

use crate::parser::UserEnum;
use crate::parser::{attr::ErrorAttribute, UserEnum};

impl UserEnum {
/// The `Display` trait's `fmt` method.
///
/// Let's check up on the result with some tests.
/**
```compile_fail
#[derive(Debug, Error)]
enum Transparent {
#[error(transparent)]
VariantWithoutFrom,
}
```
*/
pub fn fmt(&self) -> TokenStream2 {
let match_arms = if self.variants().is_empty() {
// if there are no variants, add a catch-all arm.
Expand All @@ -19,8 +32,29 @@ impl UserEnum {
.iter()
.map(|v| {
let match_head = v.filled_match_head(self.ident());
let tokens = &v.error_attribute.format_string;
quote! { #match_head => {f.write_str(format!(#tokens).as_str())} }

// make the match arm
match &v.error_attribute {
ErrorAttribute::Stringy(format_args_str) => {
quote! { #match_head => {f.write_str(format!(#format_args_str).as_str())} }
}

ErrorAttribute::Transparent => {
// use our `#[from]` field. b/c we MUST have one.
let from_field_ident = v
.from_attribute
.clone()
.expect("a `transparent` variant will have a `#[from]` field.")
.ident;

// check if we even have an ident
let format_args_str = match from_field_ident {
Some(ident) => quote!(&#ident.to_string()),
None => quote!(&_0.to_string()),
};
quote! { #match_head => { f.write_str(#format_args_str) }}
}
}
})
.collect()
};
Expand Down

0 comments on commit e304f68

Please sign in to comment.