Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add EnumConstIndex * Get working with discriminants * Remove unused/(unneeded?) features * Rename to EnumIndex and index(..). Make const_index conditional * Get repr(..) working * Fix issue to support rust 1.32 * Switch from VARIANT# to {ENUM}_{VARIANT} for variant constant names * Expose constants as part of implementation * Add discriminant error messages. Cargo fmt my code * Add rustversion to make compilation conditional on 1.46 * Handle expr discriminants * Fix generics handling * Make constants always available. No need to only expose them when const_index is defined * Change to FromDiscriminant. Only output a single function * Don't make constants accessible * Make rustversion a dev dependency in strum-tests due to upstream change * Cleanup doc tests for const * Rename to FromRepr/from_repr
- Loading branch information
Showing
5 changed files
with
285 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -217,6 +217,7 @@ DocumentMacroRexports! { | |
EnumProperty, | ||
EnumString, | ||
EnumVariantNames, | ||
FromRepr, | ||
IntoStaticStr, | ||
ToString | ||
} |
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,140 @@ | ||
use proc_macro2::{Span, TokenStream}; | ||
use quote::{format_ident, quote}; | ||
use syn::{Data, DeriveInput, PathArguments, Type, TypeParen}; | ||
|
||
use crate::helpers::{non_enum_error, HasStrumVariantProperties}; | ||
|
||
pub fn from_repr_inner(ast: &DeriveInput) -> syn::Result<TokenStream> { | ||
let name = &ast.ident; | ||
let gen = &ast.generics; | ||
let (impl_generics, ty_generics, where_clause) = gen.split_for_impl(); | ||
let vis = &ast.vis; | ||
let attrs = &ast.attrs; | ||
|
||
let mut discriminant_type: Type = syn::parse("usize".parse().unwrap()).unwrap(); | ||
for attr in attrs { | ||
let path = &attr.path; | ||
let tokens = &attr.tokens; | ||
if path.leading_colon.is_some() { | ||
continue; | ||
} | ||
if path.segments.len() != 1 { | ||
continue; | ||
} | ||
let segment = path.segments.first().unwrap(); | ||
if segment.ident != "repr" { | ||
continue; | ||
} | ||
if segment.arguments != PathArguments::None { | ||
continue; | ||
} | ||
let typ_paren = match syn::parse2::<Type>(tokens.clone()) { | ||
Ok(Type::Paren(TypeParen { elem, .. })) => *elem, | ||
_ => continue, | ||
}; | ||
let inner_path = match &typ_paren { | ||
Type::Path(t) => t, | ||
_ => continue, | ||
}; | ||
if let Some(seg) = inner_path.path.segments.last() { | ||
for t in &[ | ||
"u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize", | ||
] { | ||
if seg.ident == t { | ||
discriminant_type = typ_paren; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
|
||
if gen.lifetimes().count() > 0 { | ||
return Err(syn::Error::new( | ||
Span::call_site(), | ||
"This macro doesn't support enums with lifetimes. \ | ||
The resulting enums would be unbounded.", | ||
)); | ||
} | ||
|
||
let variants = match &ast.data { | ||
Data::Enum(v) => &v.variants, | ||
_ => return Err(non_enum_error()), | ||
}; | ||
|
||
let mut arms = Vec::new(); | ||
let mut constant_defs = Vec::new(); | ||
let mut has_additional_data = false; | ||
let mut prev_const_var_ident = None; | ||
for variant in variants { | ||
use syn::Fields::*; | ||
|
||
if variant.get_variant_properties()?.disabled.is_some() { | ||
continue; | ||
} | ||
|
||
let ident = &variant.ident; | ||
let params = match &variant.fields { | ||
Unit => quote! {}, | ||
Unnamed(fields) => { | ||
has_additional_data = true; | ||
let defaults = ::std::iter::repeat(quote!(::core::default::Default::default())) | ||
.take(fields.unnamed.len()); | ||
quote! { (#(#defaults),*) } | ||
} | ||
Named(fields) => { | ||
has_additional_data = true; | ||
let fields = fields | ||
.named | ||
.iter() | ||
.map(|field| field.ident.as_ref().unwrap()); | ||
quote! { {#(#fields: ::core::default::Default::default()),*} } | ||
} | ||
}; | ||
|
||
use heck::ShoutySnakeCase; | ||
let const_var_str = format!("{}_DISCRIMINANT", variant.ident).to_shouty_snake_case(); | ||
let const_var_ident = format_ident!("{}", const_var_str); | ||
|
||
let const_val_expr = match &variant.discriminant { | ||
Some((_, expr)) => quote! { #expr }, | ||
None => match &prev_const_var_ident { | ||
Some(prev) => quote! { #prev + 1 }, | ||
None => quote! { 0 }, | ||
}, | ||
}; | ||
|
||
constant_defs | ||
.push(quote! {const #const_var_ident: #discriminant_type = #const_val_expr;}); | ||
arms.push(quote! {v if v == #const_var_ident => ::core::option::Option::Some(#name::#ident #params)}); | ||
|
||
prev_const_var_ident = Some(const_var_ident); | ||
} | ||
|
||
arms.push(quote! { _ => ::core::option::Option::None }); | ||
|
||
let const_if_possible = if has_additional_data { | ||
quote! {} | ||
} else { | ||
#[rustversion::before(1.46)] | ||
fn filter_by_rust_version(s: TokenStream) -> TokenStream { | ||
quote! {} | ||
} | ||
|
||
#[rustversion::since(1.46)] | ||
fn filter_by_rust_version(s: TokenStream) -> TokenStream { | ||
s | ||
} | ||
filter_by_rust_version(quote! { const }) | ||
}; | ||
|
||
Ok(quote! { | ||
impl #impl_generics #name #ty_generics #where_clause { | ||
#vis #const_if_possible fn from_repr(discriminant: #discriminant_type) -> Option<#name #ty_generics> { | ||
#(#constant_defs)* | ||
match discriminant { | ||
#(#arms),* | ||
} | ||
} | ||
} | ||
}) | ||
} |
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,63 @@ | ||
use strum::FromRepr; | ||
|
||
#[derive(Debug, FromRepr, PartialEq)] | ||
#[repr(u8)] | ||
enum Week { | ||
Sunday, | ||
Monday, | ||
Tuesday, | ||
Wednesday, | ||
Thursday, | ||
Friday = 4 + 3, | ||
Saturday = 8, | ||
} | ||
|
||
#[test] | ||
fn simple_test() { | ||
assert_eq!(Week::from_repr(0), Some(Week::Sunday)); | ||
assert_eq!(Week::from_repr(1), Some(Week::Monday)); | ||
assert_eq!(Week::from_repr(6), None); | ||
assert_eq!(Week::from_repr(7), Some(Week::Friday)); | ||
assert_eq!(Week::from_repr(8), Some(Week::Saturday)); | ||
assert_eq!(Week::from_repr(9), None); | ||
} | ||
|
||
#[rustversion::since(1.46)] | ||
#[test] | ||
fn const_test() { | ||
// This is to test that it works in a const fn | ||
const fn from_repr(discriminant: u8) -> Option<Week> { | ||
Week::from_repr(discriminant) | ||
} | ||
assert_eq!(from_repr(0), Some(Week::Sunday)); | ||
assert_eq!(from_repr(1), Some(Week::Monday)); | ||
assert_eq!(from_repr(6), None); | ||
assert_eq!(from_repr(7), Some(Week::Friday)); | ||
assert_eq!(from_repr(8), Some(Week::Saturday)); | ||
assert_eq!(from_repr(9), None); | ||
} | ||
|
||
#[test] | ||
fn crate_module_path_test() { | ||
pub mod nested { | ||
pub mod module { | ||
pub use strum; | ||
} | ||
} | ||
|
||
#[derive(Debug, FromRepr, PartialEq)] | ||
#[strum(crate = "nested::module::strum")] | ||
enum Week { | ||
Sunday, | ||
Monday, | ||
Tuesday, | ||
Wednesday, | ||
Thursday, | ||
Friday, | ||
Saturday, | ||
} | ||
|
||
assert_eq!(Week::from_repr(0), Some(Week::Sunday)); | ||
assert_eq!(Week::from_repr(6), Some(Week::Saturday)); | ||
assert_eq!(Week::from_repr(7), None); | ||
} |