Skip to content

Commit

Permalink
allow more complex repr and missing repr
Browse files Browse the repository at this point in the history
  • Loading branch information
ModProg committed Sep 5, 2023
1 parent bcbfbe6 commit 15fa1fc
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 45 deletions.
62 changes: 27 additions & 35 deletions impl/src/try_from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,13 @@ pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStr
data.struct_token.span(),
"`TryFrom` cannot be derived for structs",
)),
syn::Data::Enum(data) => {
let Some(repr) = ReprAttribute::parse_attrs(&input.attrs)? else {
return Err(syn::Error::new(
Span::call_site(),
"`TryFrom` needs repr specified `#[repr(u*/i*)]`",
));
};

Expansion {
repr,
ident: &input.ident,
variants: data.variants.iter().collect(),
generics: &input.generics,
}
.expand()
syn::Data::Enum(data) => Expansion {
repr: ReprAttribute::parse_attrs(&input.attrs)?,
ident: &input.ident,
variants: data.variants.iter().collect(),
generics: &input.generics,
}
.expand(),
syn::Data::Union(data) => Err(syn::Error::new(
data.union_token.span(),
"`TryFrom` cannot be derived for unions",
Expand All @@ -43,30 +34,31 @@ struct ReprAttribute(Ident);

impl ReprAttribute {
/// Parses a [`StructAttribute`] from the provided [`syn::Attribute`]s.
fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result<Option<Self>> {
Ok(attrs
fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result<Self> {
attrs
.as_ref()
.iter()
.filter(|attr| attr.path().is_ident("repr"))
.try_fold(None, |attrs, attr| {
if attrs.is_some() {
Err(syn::Error::new(
attr.path().span(),
"only a single `#[repr(...)]` attribute is supported",
))
} else {
let repr: Ident = attr.parse_args()?;
match repr.to_string().as_str() {
"u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8"
| "i16" | "i32" | "i64" | "i128" | "isize" => Ok(Some(repr)),
_ => Err(syn::Error::new(
repr.span(),
"only integer discriminants (u*/i*) are supported",
)),
.try_fold(None, |mut repr, attr| {
attr.parse_nested_meta(|meta| {
if let Some(ident) = meta.path.get_ident() {
if let "u8" | "u16" | "u32" | "u64" | "u128" | "usize" | "i8"
| "i16" | "i32" | "i64" | "i128" | "isize" =
ident.to_string().as_str()
{
repr = Some(ident.clone());
return Ok(());
}
}
}
})?
.map(Self))
// ignore all other attributes that could have a body e.g. `align`
_ = meta.input.parse::<proc_macro2::Group>();
Ok(())
})
.map(|_| repr)
})
// Default discriminant is interpreted as `isize` (https://doc.rust-lang.org/reference/items/enumerations.html#discriminants)
.map(|repr| repr.unwrap_or_else(|| Ident::new("isize", Span::call_site())))
.map(Self)
}
}

Expand Down
52 changes: 42 additions & 10 deletions tests/try_from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,50 @@

use derive_more::TryFrom;

#[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)]
#[repr(i16)]
enum Enum {
A,
B = -21,
C,
D,
#[test]
fn test_with_repr() {
#[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)]
#[repr(i16)]
enum Enum {
A,
B = -21,
C,
D,
}
assert_eq!(Enum::A, Enum::try_from(0i16).unwrap());
assert_eq!(Enum::B, Enum::try_from(-21).unwrap());
assert_eq!(Enum::C, Enum::try_from(-20).unwrap());
assert_eq!(Enum::D, Enum::try_from(-19).unwrap());
assert!(Enum::try_from(-1).is_err());
}

#[test]
fn test() {
assert_eq!(Enum::A, Enum::try_from(0).unwrap());
fn enum_without_repr() {
#[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)]
enum Enum {
A,
B = -21,
C,
D,
}
assert_eq!(Enum::A, Enum::try_from(0isize).unwrap());
assert_eq!(Enum::B, Enum::try_from(-21).unwrap());
assert_eq!(Enum::C, Enum::try_from(-20).unwrap());
assert_eq!(Enum::D, Enum::try_from(-19).unwrap());
assert!(Enum::try_from(-1).is_err());
}

#[test]
fn enum_with_complex_repr() {
#[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)]
#[repr(align(16), i32)]
enum Enum {
A,
B = -21,
C,
D,
}
assert_eq!(Enum::A, Enum::try_from(0i32).unwrap());
assert_eq!(Enum::B, Enum::try_from(-21).unwrap());
assert_eq!(Enum::C, Enum::try_from(-20).unwrap());
assert_eq!(Enum::D, Enum::try_from(-19).unwrap());
Expand All @@ -37,7 +69,7 @@ mod discriminants_on_enum_with_fields {
}

#[test]
fn test() {
fn test_discriminants_on_enum_with_fields() {
assert_eq!(Enum::A, Enum::try_from(0).unwrap());
assert_eq!(Enum::Discriminant, Enum::try_from(5).unwrap());
assert!(Enum::try_from(6).is_err());
Expand Down

0 comments on commit 15fa1fc

Please sign in to comment.