Repository: Peternator7/strum (Rust)

Issue:
EnumDiscriminants should inherit the repr of the enum they are derived from
The below example code will generate `VehicleDiscriminants` enum with `Car` and `Truck` variants but it will be `repr(usize)` instead of inheriting the `u8` repr of `Vehicle` additionally they will have the default enum representations (`Car = 0` and `Vehicle = 1` in this case)

```
// Custom discriminant tests
#[derive(EnumDiscriminants, Debug, PartialEq)]
#[strum_discriminants(derive(FromRepr))]
#[repr(u8)]
enum Vehicle {
    Car(CarModel) = 1,
    Truck(TruckModel) = 3,
}
```

I would have expected the above code to produce the following:
```
#[repr(u8)]
#[derive(FromRepr)]
enum VehicleDiscriminants {
    Car = 1,
    Truck = 3,
}
```


Discussion:
It's a breaking change, but I agree it's certainly more inline with what would be expected. Happy to take a PR if you're interested.



Patch:
diff --git a/strum_macros/src/helpers/type_props.rs b/strum_macros/src/helpers/type_props.rs
index 0d49e04e..17d84c98 100644
--- a/strum_macros/src/helpers/type_props.rs
+++ b/strum_macros/src/helpers/type_props.rs
@@ -21,6 +21,7 @@ pub struct StrumTypeProperties {
     pub discriminant_others: Vec<TokenStream>,
     pub discriminant_vis: Option<Visibility>,
     pub use_phf: bool,
+    pub enum_repr: Option<TokenStream>,
 }
 
 impl HasTypeProperties for DeriveInput {
@@ -103,6 +104,17 @@ impl HasTypeProperties for DeriveInput {
             }
         }
 
+        let attrs = &self.attrs;
+        for attr in attrs {
+            if let Ok(list) = attr.meta.require_list() {
+                if let Some(ident) = list.path.get_ident() {
+                    if ident == "repr" {
+                        output.enum_repr = Some(list.tokens.clone())
+                    }
+                }
+            }
+        }
+
         Ok(output)
     }
 }
diff --git a/strum_macros/src/macros/enum_discriminants.rs b/strum_macros/src/macros/enum_discriminants.rs
index 4e54a30c..f7d63727 100644
--- a/strum_macros/src/macros/enum_discriminants.rs
+++ b/strum_macros/src/macros/enum_discriminants.rs
@@ -40,10 +40,16 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     // Pass through all other attributes
     let pass_though_attributes = type_properties.discriminant_others;
 
+    let repr = type_properties.enum_repr.map(|repr| quote!(#[repr(#repr)]));
+
     // Add the variants without fields, but exclude the `strum` meta item
     let mut discriminants = Vec::new();
     for variant in variants {
         let ident = &variant.ident;
+        let discriminant = variant
+            .discriminant
+            .as_ref()
+            .map(|(_, expr)| quote!( = #expr));
 
         // Don't copy across the "strum" meta attribute. Only passthrough the whitelisted
         // attributes and proxy `#[strum_discriminants(...)]` attributes
@@ -81,7 +87,7 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             })
             .collect::<Result<Vec<_>, _>>()?;
 
-        discriminants.push(quote! { #(#attrs)* #ident });
+        discriminants.push(quote! { #(#attrs)* #ident #discriminant});
     }
 
     // Ideally:
@@ -153,6 +159,7 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     Ok(quote! {
         /// Auto-generated discriminant enum variants
         #derives
+        #repr
         #(#[ #pass_though_attributes ])*
         #discriminants_vis enum #discriminants_name {
             #(#discriminants),*
diff --git a/strum_macros/src/macros/from_repr.rs b/strum_macros/src/macros/from_repr.rs
index 92fd7ad9..a70c6f86 100644
--- a/strum_macros/src/macros/from_repr.rs
+++ b/strum_macros/src/macros/from_repr.rs
@@ -1,62 +1,32 @@
 use heck::ToShoutySnakeCase;
 use proc_macro2::{Span, TokenStream};
-use quote::{format_ident, quote, ToTokens};
-use syn::{Data, DeriveInput, Fields, PathArguments, Type, TypeParen};
+use quote::{format_ident, quote};
+use syn::{Data, DeriveInput, Fields, Type};
 
-use crate::helpers::{non_enum_error, HasStrumVariantProperties};
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
 
 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 mut ts = if let Ok(ts) = attr
-            .meta
-            .require_list()
-            .map(|metas| metas.to_token_stream().into_iter())
-        {
-            ts
-        } else {
-            continue;
-        };
-        // Discard the path
-        let _ = ts.next();
-        let tokens: TokenStream = ts.collect();
-
-        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 let Some(type_path) = ast
+        .get_type_properties()
+        .ok()
+        .and_then(|tp| tp.enum_repr)
+        .and_then(|repr_ts| syn::parse2::<Type>(repr_ts).ok())
+    {
+        if let Type::Path(path) = type_path.clone() {
+            if let Some(seg) = path.path.segments.last() {
+                for t in &[
+                    "u8", "u16", "u32", "u64", "usize", "i8", "i16", "i32", "i64", "isize",
+                ] {
+                    if seg.ident == t {
+                        discriminant_type = type_path;
+                        break;
+                    }
                 }
             }
         }


Test patch:
diff --git a/strum_tests/tests/enum_discriminants.rs b/strum_tests/tests/enum_discriminants.rs
index eb29e6d1..d6e4668e 100644
--- a/strum_tests/tests/enum_discriminants.rs
+++ b/strum_tests/tests/enum_discriminants.rs
@@ -1,6 +1,9 @@
-use enum_variant_type::EnumVariantType;
-use strum::{Display, EnumDiscriminants, EnumIter, EnumMessage, EnumString, IntoEnumIterator};
+use std::mem::{align_of, size_of};
 
+use enum_variant_type::EnumVariantType;
+use strum::{
+    Display, EnumDiscriminants, EnumIter, EnumMessage, EnumString, FromRepr, IntoEnumIterator,
+};
 
 mod core {} // ensure macros call `::core`
 
@@ -305,3 +308,58 @@ fn crate_module_path_test() {
 
     assert_eq!(expected, discriminants);
 }
+
+#[allow(dead_code)]
+#[derive(EnumDiscriminants)]
+#[repr(u16)]
+enum WithReprUInt {
+    Variant0,
+    Variant1,
+}
+
+#[test]
+fn with_repr_uint() {
+    // These tests would not be proof of proper functioning on a 16 bit system
+    assert_eq!(size_of::<u16>(), size_of::<WithReprUIntDiscriminants>());
+    assert_eq!(
+        size_of::<WithReprUInt>(),
+        size_of::<WithReprUIntDiscriminants>()
+    )
+}
+
+#[allow(dead_code)]
+#[derive(EnumDiscriminants)]
+#[repr(align(16), u8)]
+enum WithReprAlign {
+    Variant0,
+    Variant1,
+}
+
+#[test]
+fn with_repr_align() {
+    assert_eq!(
+        align_of::<WithReprAlign>(),
+        align_of::<WithReprAlignDiscriminants>()
+    );
+    assert_eq!(16, align_of::<WithReprAlignDiscriminants>());
+}
+
+#[allow(dead_code)]
+#[derive(EnumDiscriminants)]
+#[strum_discriminants(derive(FromRepr))]
+enum WithExplicitDicriminantValue {
+    Variant0 = 42 + 100,
+    Variant1 = 11,
+}
+
+#[test]
+fn with_explicit_discriminant_value() {
+    assert_eq!(
+        WithExplicitDicriminantValueDiscriminants::from_repr(11),
+        Some(WithExplicitDicriminantValueDiscriminants::Variant1)
+    );
+    assert_eq!(
+        142,
+        WithExplicitDicriminantValueDiscriminants::Variant0 as u8
+    );
+}