Skip to content

Commit

Permalink
try_from(repr) attribute, and cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
ModProg committed Sep 9, 2023
1 parent 819cb3d commit 8cf36e4
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 19 deletions.
1 change: 1 addition & 0 deletions impl/doc/try_from.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Only field-less variants can be constructed from their variant, therefor the `Tr
# mod discriminant_on_non_unit_enum {
# use derive_more::TryFrom;
#[derive(TryFrom, Debug, PartialEq)]
#[try_from(repr)]
#[repr(u32)]
enum Enum {
Implicit,
Expand Down
77 changes: 58 additions & 19 deletions impl/src/try_from.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Implementation of a [`TryFrom`] derive macro.

use proc_macro2::{Literal, Span, TokenStream};
use quote::{format_ident, quote, ToTokens as _};
use quote::{format_ident, quote, ToTokens};
use syn::spanned::Spanned as _;

/// Expands a [`TryFrom`] derive macro.
Expand All @@ -11,22 +11,57 @@ 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) => Expansion {
syn::Data::Enum(data) => Ok(Expansion {
repr: ReprAttribute::parse_attrs(&input.attrs)?,
ident: &input.ident,
variants: data.variants.iter().collect(),
generics: &input.generics,
attr: ItemAttribute::parse_attrs(&input.attrs)?,
ident: input.ident.clone(),
variants: data.variants.clone().into_iter().collect(),
generics: input.generics.clone(),
}
.expand(),
.into_token_stream()),
syn::Data::Union(data) => Err(syn::Error::new(
data.union_token.span(),
"`TryFrom` cannot be derived for unions",
)),
}
}

/// Representation of a [`TryFrom`] derive macro struct item attribute.
///
/// ```rust,ignore
/// #[try_from(repr)]
/// ```
#[derive(Default)]
struct ItemAttribute {
/// plain `repr`
repr: bool,
}

impl ItemAttribute {
/// Parses a [`StructAttribute`] from the provided [`syn::Attribute`]s.
fn parse_attrs(attrs: impl AsRef<[syn::Attribute]>) -> syn::Result<Self> {
attrs
.as_ref()
.iter()
.filter(|attr| attr.path().is_ident("try_from"))
.try_fold(ItemAttribute::default(), |mut attrs, attr| {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("repr") {
attrs.repr = true;
Ok(())
} else {
Err(meta.error("only `repr` is allowed here"))
}
})
.map(|_| attrs)
})
}
}

/// Representation of a [`Repr`] derive macro struct container attribute.
///
/// Note: This disregards any non integer representation reprs.
///
/// ```rust,ignore
/// #[repr(<type>)]
/// ```
Expand Down Expand Up @@ -57,30 +92,34 @@ impl ReprAttribute {
.map(|_| repr)
})
// Default discriminant is interpreted as `isize` (https://doc.rust-lang.org/reference/items/enumerations.html#discriminants)
.map(|repr| repr.unwrap_or_else(|| syn::Ident::new("isize", Span::call_site())))
.map(|repr| {
repr.unwrap_or_else(|| syn::Ident::new("isize", Span::call_site()))
})
.map(Self)
}
}

/// Expansion of a macro for generating [`TryFrom`] implementation of an enum
struct Expansion<'a> {
struct Expansion {
/// Enum `#[repr(u/i*)]`
repr: ReprAttribute,

/// Attributes on item.
attr: ItemAttribute,
/// Enum [`Ident`].
ident: &'a syn::Ident,

ident: syn::Ident,
/// Variant [`Ident`] in case of enum expansion.
variants: Vec<&'a syn::Variant>,

variants: Vec<syn::Variant>,
/// Struct or enum [`syn::Generics`].
generics: &'a syn::Generics,
generics: syn::Generics,
}

impl<'a> Expansion<'a> {
impl ToTokens for Expansion {
/// Expands [`TryFrom`] implementations for a struct.
fn expand(&self) -> syn::Result<TokenStream> {
let ident = self.ident;
fn to_tokens(&self, tokens: &mut TokenStream) {
if !self.attr.repr {
return;
}
let ident = &self.ident;
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();

let repr = &self.repr.0;
Expand Down Expand Up @@ -120,7 +159,7 @@ impl<'a> Expansion<'a> {
)
.unzip();

Ok(quote! {
quote! {
#[automatically_derived]
impl #impl_generics
::core::convert::TryFrom<#repr #ty_generics> for #ident
Expand All @@ -137,6 +176,6 @@ impl<'a> Expansion<'a> {
}
}
}
})
}.to_tokens(tokens);
}
}
4 changes: 4 additions & 0 deletions tests/try_from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use derive_more::TryFrom;
fn test_with_repr() {
#[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)]
#[repr(i16)]
#[try_from(repr)]
enum Enum {
A,
B = -21,
Expand All @@ -23,6 +24,7 @@ fn test_with_repr() {
#[test]
fn enum_without_repr() {
#[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)]
#[try_from(repr)]
enum Enum {
A,
B = -21,
Expand All @@ -39,6 +41,7 @@ fn enum_without_repr() {
#[test]
fn enum_with_complex_repr() {
#[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)]
#[try_from(repr)]
#[repr(align(16), i32)]
enum Enum {
A,
Expand All @@ -58,6 +61,7 @@ mod discriminants_on_enum_with_fields {
use super::*;

#[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)]
#[try_from(repr)]
#[repr(i16)]
enum Enum {
A,
Expand Down

0 comments on commit 8cf36e4

Please sign in to comment.