-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
253 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# What `#[derive(TryFrom)]` generates | ||
|
||
This derive allows you to convert enum discriminants into their corresponding variants. | ||
A requirement is, that the enum has a `#[repr(u/i*)]` attribute, e.g., `#[repr(u8)]`. | ||
Only field-less variants can be constructed from their variant, therefor the `TryFrom` implementation will return an error for a discriminant representing a variant with fields. | ||
|
||
## Example usage | ||
|
||
```rust | ||
# use derive_more::TryFrom; | ||
# | ||
#[derive(TryFrom, Debug, PartialEq)] | ||
#[repr(u32)] | ||
enum Enum { | ||
Implicit, | ||
Explicit = 5, | ||
Field(usize), | ||
Empty{}, | ||
} | ||
|
||
assert_eq!(Enum::Implicit, Enum::try_from(0).unwrap()); | ||
assert_eq!(Enum::Explicit, Enum::try_from(5).unwrap()); | ||
assert_eq!(Enum::Empty{}, Enum::try_from(7).unwrap()); | ||
|
||
// variants with fields are not supported | ||
assert!(Enum::try_from(6).is_error()); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
//! Implementation of a [`TryFrom`] derive macro. | ||
|
||
use proc_macro2::{Literal, Span, TokenStream}; | ||
use quote::{format_ident, quote, ToTokens as _}; | ||
use syn::{spanned::Spanned as _, Ident, Variant}; | ||
|
||
/// Expands a [`TryFrom`] derive macro. | ||
pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result<TokenStream> { | ||
match &input.data { | ||
syn::Data::Struct(data) => Err(syn::Error::new( | ||
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::Union(data) => Err(syn::Error::new( | ||
data.union_token.span(), | ||
"`TryFrom` cannot be derived for unions", | ||
)), | ||
} | ||
} | ||
|
||
/// Representation of a [`Repr`] derive macro struct container attribute. | ||
/// | ||
/// ```rust,ignore | ||
/// #[repr(<type>)] | ||
/// ``` | ||
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 | ||
.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", | ||
)), | ||
} | ||
} | ||
})? | ||
.map(Self)) | ||
} | ||
} | ||
|
||
/// Expansion of a macro for generating [`TryFrom`] implementation of an enum | ||
struct Expansion<'a> { | ||
/// Enum `#[repr(u/i*)]` | ||
repr: ReprAttribute, | ||
/// Enum [`Ident`]. | ||
ident: &'a Ident, | ||
|
||
/// Variant [`Ident`] in case of enum expansion. | ||
variants: Vec<&'a syn::Variant>, | ||
|
||
/// Struct or enum [`syn::Generics`]. | ||
generics: &'a syn::Generics, | ||
} | ||
|
||
impl<'a> Expansion<'a> { | ||
/// Expands [`TryFrom`] implementations for a struct. | ||
fn expand(&self) -> syn::Result<TokenStream> { | ||
let ident = self.ident; | ||
let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); | ||
|
||
let repr = &self.repr.0; | ||
|
||
let mut last_discriminant = quote!(0); | ||
let mut inc = 0usize; | ||
let (consts, (discriminants, variants)): ( | ||
Vec<Ident>, | ||
(Vec<TokenStream>, Vec<TokenStream>), | ||
) = self | ||
.variants | ||
.iter() | ||
.filter_map( | ||
|Variant { | ||
ident, | ||
fields, | ||
discriminant, | ||
.. | ||
}| { | ||
if let Some(discriminant) = discriminant { | ||
last_discriminant = discriminant.1.to_token_stream(); | ||
inc = 0; | ||
} | ||
let ret = { | ||
let inc = Literal::usize_unsuffixed(inc); | ||
fields.is_empty().then_some(( | ||
format_ident!("__DISCRIMINANT_{ident}"), | ||
(quote!(#last_discriminant + #inc), quote!(#ident #fields)), | ||
)) | ||
}; | ||
inc += 1; | ||
ret | ||
}, | ||
) | ||
.unzip(); | ||
|
||
Ok(quote! { | ||
#[automatically_derived] | ||
impl #impl_generics | ||
::core::convert::TryFrom<#repr #ty_generics> for #ident | ||
#where_clause | ||
{ | ||
type Error = ::derive_more::TryFromError<#repr>; | ||
|
||
#[inline] | ||
fn try_from(value: #repr) -> ::core::result::Result<Self, Self::Error> { | ||
#(#[allow(non_upper_case_globals)] const #consts: #repr = #discriminants;)* | ||
match value { | ||
#(#consts => ::core::result::Result::Ok(#ident::#variants),)* | ||
_ => ::core::result::Result::Err(::derive_more::TryFromError::new(value)), | ||
} | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
#![allow(dead_code)] | ||
|
||
use derive_more::TryFrom; | ||
|
||
#[derive(TryFrom, Clone, Copy, Debug, Eq, PartialEq)] | ||
#[repr(i16)] | ||
enum Enum { | ||
A, | ||
Discriminant = 5, | ||
Field(usize), | ||
Empty {}, | ||
FieldWithDiscriminant(u8, i64) = -14, | ||
EmptyTuple(), | ||
} | ||
|
||
#[test] | ||
fn test() { | ||
assert_eq!(Enum::A, Enum::try_from(0).unwrap()); | ||
assert_eq!(Enum::Discriminant, Enum::try_from(5).unwrap()); | ||
assert_eq!(Enum::Empty {}, Enum::try_from(7).unwrap()); | ||
assert_eq!(Enum::EmptyTuple(), Enum::try_from(-13).unwrap()); | ||
} |