Repository: Peternator7/strum
Pull request #170 (2021-06-30 04:13:12)

Re-export macros
## Motivation

Our team is developing a library which requires downstream end-user to derive `strum_macro::EnumIter` for their custom enum.

```rust
pub trait RelationTrait: Iterable + Debug + 'static { ... }

// Derive EnumIter
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
    Cake,
    Filling,
}
```

However, the expanded codes from `strum_macro::EnumIter` looks something like this and failed to compile...
This signature, `::strum::IntoEnumIterator`, implies our downstream end-user must add the `strum` dependency on their own.

```rust
impl ::strum::IntoEnumIterator for Relation { ... }
```

```
error[E0433]: failed to resolve: could not find `strum` in the list of imported crates
   |
39 | #[derive(Copy, Clone, Debug, EnumIter)]
   |                              ^^^^^^^^ could not find `strum` in the list of imported crates
   |
```

Hence, we want to find a way to re-export `strum` & `strum_macros` in our library. And downstream end-user don't need to specify the `strum` dependency on their own.

## Proposed Solution

Adding an optional derive attribute `#[strum(crate_path = "some_crate::strum")]`, for example...

```rust
// Derive EnumIter with crate_path derive attribute
#[derive(Copy, Clone, Debug, EnumIter)]
#[strum(crate_path = "some_crate::strum")]
pub enum Relation {
    Cake,
    Filling,
}

// Derive macro will be expanded into
impl some_crate::strum::IntoEnumIterator for Relation { ... }
```

Backward compatibility, if the derive attribute was not provided...

```rust
// Derive EnumIter without crate_path derive attribute
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {
    Cake,
    Filling,
}

// Derive macro will be expanded into
impl ::strum::IntoEnumIterator for Relation { ... }
```


## References

Similar feature was discussed on serde-rs/serde#1465. And `serde` provides a optional derive attribute to achieve it.

```rust
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
#[serde(crate = "some_crate::serde")]
struct Vertex {
    position: [u32; 3],
}

```

billy1624: Please review @Peternator7 
Peternator7: lgtm! Thanks for the contribution. I'm on vacation next week, but I'll try and get a release out if I find a little time!

Patch:
diff --git a/strum_macros/src/helpers/metadata.rs b/strum_macros/src/helpers/metadata.rs
index bee07d2..6279e6a 100644
--- a/strum_macros/src/helpers/metadata.rs
+++ b/strum_macros/src/helpers/metadata.rs
@@ -2,6 +2,7 @@ use proc_macro2::{Span, TokenStream};
 use syn::{
     parenthesized,
     parse::{Parse, ParseStream},
+    parse2, parse_str,
     punctuated::Punctuated,
     spanned::Spanned,
     Attribute, DeriveInput, Ident, LitBool, LitStr, Path, Token, Variant, Visibility,
@@ -11,6 +12,7 @@ use super::case_style::CaseStyle;
 
 pub mod kw {
     use syn::custom_keyword;
+    pub use syn::token::Crate;
 
     // enum metadata
     custom_keyword!(serialize_all);
@@ -37,6 +39,10 @@ pub enum EnumMeta {
         case_style: CaseStyle,
     },
     AsciiCaseInsensitive(kw::ascii_case_insensitive),
+    Crate {
+        kw: kw::Crate,
+        crate_module_path: Path,
+    },
 }
 
 impl Parse for EnumMeta {
@@ -47,6 +53,16 @@ impl Parse for EnumMeta {
             input.parse::<Token![=]>()?;
             let case_style = input.parse()?;
             Ok(EnumMeta::SerializeAll { kw, case_style })
+        } else if lookahead.peek(kw::Crate) {
+            let kw = input.parse::<kw::Crate>()?;
+            input.parse::<Token![=]>()?;
+            let path_str: LitStr = input.parse()?;
+            let path_tokens = parse_str(&path_str.value())?;
+            let crate_module_path = parse2(path_tokens)?;
+            Ok(EnumMeta::Crate {
+                kw,
+                crate_module_path,
+            })
         } else if lookahead.peek(kw::ascii_case_insensitive) {
             let kw = input.parse()?;
             Ok(EnumMeta::AsciiCaseInsensitive(kw))
@@ -61,6 +77,7 @@ impl Spanned for EnumMeta {
         match self {
             EnumMeta::SerializeAll { kw, .. } => kw.span(),
             EnumMeta::AsciiCaseInsensitive(kw) => kw.span(),
+            EnumMeta::Crate { kw, .. } => kw.span(),
         }
     }
 }
diff --git a/strum_macros/src/helpers/type_props.rs b/strum_macros/src/helpers/type_props.rs
index 1e0a42c..39b0a2b 100644
--- a/strum_macros/src/helpers/type_props.rs
+++ b/strum_macros/src/helpers/type_props.rs
@@ -1,7 +1,7 @@
 use proc_macro2::TokenStream;
 use quote::quote;
 use std::default::Default;
-use syn::{DeriveInput, Ident, Path, Visibility};
+use syn::{parse_quote, DeriveInput, Ident, Path, Visibility};
 
 use super::case_style::CaseStyle;
 use super::metadata::{DeriveInputExt, EnumDiscriminantsMeta, EnumMeta};
@@ -15,6 +15,7 @@ pub trait HasTypeProperties {
 pub struct StrumTypeProperties {
     pub case_style: Option<CaseStyle>,
     pub ascii_case_insensitive: bool,
+    pub crate_module_path: Option<Path>,
     pub discriminant_derives: Vec<Path>,
     pub discriminant_name: Option<Ident>,
     pub discriminant_others: Vec<TokenStream>,
@@ -30,6 +31,7 @@ impl HasTypeProperties for DeriveInput {
 
         let mut serialize_all_kw = None;
         let mut ascii_case_insensitive_kw = None;
+        let mut crate_module_path_kw = None;
         for meta in strum_meta {
             match meta {
                 EnumMeta::SerializeAll { case_style, kw } => {
@@ -48,6 +50,17 @@ impl HasTypeProperties for DeriveInput {
                     ascii_case_insensitive_kw = Some(kw);
                     output.ascii_case_insensitive = true;
                 }
+                EnumMeta::Crate {
+                    crate_module_path,
+                    kw,
+                } => {
+                    if let Some(fst_kw) = crate_module_path_kw {
+                        return Err(occurrence_error(fst_kw, kw, "Crate"));
+                    }
+
+                    crate_module_path_kw = Some(kw);
+                    output.crate_module_path = Some(crate_module_path);
+                }
             }
         }
 
@@ -83,3 +96,13 @@ impl HasTypeProperties for DeriveInput {
         Ok(output)
     }
 }
+
+impl StrumTypeProperties {
+    pub fn crate_module_path(&self) -> Path {
+        if let Some(path) = &self.crate_module_path {
+            parse_quote!(#path)
+        } else {
+            parse_quote!(::strum)
+        }
+    }
+}
diff --git a/strum_macros/src/macros/enum_count.rs b/strum_macros/src/macros/enum_count.rs
index ada3a0f..44c7f2e 100644
--- a/strum_macros/src/macros/enum_count.rs
+++ b/strum_macros/src/macros/enum_count.rs
@@ -2,13 +2,15 @@ use proc_macro2::TokenStream;
 use quote::quote;
 use syn::{Data, DeriveInput};
 
-use crate::helpers::non_enum_error;
+use crate::helpers::{non_enum_error, HasTypeProperties};
 
 pub(crate) fn enum_count_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     let n = match &ast.data {
         Data::Enum(v) => v.variants.len(),
         _ => return Err(non_enum_error()),
     };
+    let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
 
     // Used in the quasi-quotation below as `#name`
     let name = &ast.ident;
@@ -18,7 +20,7 @@ pub(crate) fn enum_count_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
 
     Ok(quote! {
         // Implementation
-        impl #impl_generics ::strum::EnumCount for #name #ty_generics #where_clause {
+        impl #impl_generics #strum_module_path::EnumCount for #name #ty_generics #where_clause {
             const COUNT: usize = #n;
         }
     })
diff --git a/strum_macros/src/macros/enum_iter.rs b/strum_macros/src/macros/enum_iter.rs
index b9b2fa3..a006d85 100644
--- a/strum_macros/src/macros/enum_iter.rs
+++ b/strum_macros/src/macros/enum_iter.rs
@@ -2,13 +2,15 @@ use proc_macro2::{Span, TokenStream};
 use quote::quote;
 use syn::{Data, DeriveInput, Ident};
 
-use crate::helpers::{non_enum_error, HasStrumVariantProperties};
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
 
 pub fn enum_iter_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 type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
 
     if gen.lifetimes().count() > 0 {
         return Err(syn::Error::new(
@@ -80,7 +82,7 @@ pub fn enum_iter_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
             }
         }
 
-        impl #impl_generics ::strum::IntoEnumIterator for #name #ty_generics #where_clause {
+        impl #impl_generics #strum_module_path::IntoEnumIterator for #name #ty_generics #where_clause {
             type Iterator = #iter_name #ty_generics;
             fn iter() -> #iter_name #ty_generics {
                 #iter_name {
diff --git a/strum_macros/src/macros/enum_messages.rs b/strum_macros/src/macros/enum_messages.rs
index 19ff75c..6e599d0 100644
--- a/strum_macros/src/macros/enum_messages.rs
+++ b/strum_macros/src/macros/enum_messages.rs
@@ -13,6 +13,7 @@ pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     };
 
     let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
 
     let mut arms = Vec::new();
     let mut detailed_arms = Vec::new();
@@ -79,7 +80,7 @@ pub fn enum_message_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     }
 
     Ok(quote! {
-        impl #impl_generics ::strum::EnumMessage for #name #ty_generics #where_clause {
+        impl #impl_generics #strum_module_path::EnumMessage for #name #ty_generics #where_clause {
             fn get_message(&self) -> ::core::option::Option<&'static str> {
                 match self {
                     #(#arms),*
diff --git a/strum_macros/src/macros/enum_properties.rs b/strum_macros/src/macros/enum_properties.rs
index d1f03da..58265a7 100644
--- a/strum_macros/src/macros/enum_properties.rs
+++ b/strum_macros/src/macros/enum_properties.rs
@@ -2,7 +2,7 @@ use proc_macro2::TokenStream;
 use quote::quote;
 use syn::{Data, DeriveInput};
 
-use crate::helpers::{non_enum_error, HasStrumVariantProperties};
+use crate::helpers::{non_enum_error, HasStrumVariantProperties, HasTypeProperties};
 
 pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     let name = &ast.ident;
@@ -11,6 +11,8 @@ pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         Data::Enum(v) => &v.variants,
         _ => return Err(non_enum_error()),
     };
+    let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
 
     let mut arms = Vec::new();
     for variant in variants {
@@ -53,7 +55,7 @@ pub fn enum_properties_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     }
 
     Ok(quote! {
-        impl #impl_generics ::strum::EnumProperty for #name #ty_generics #where_clause {
+        impl #impl_generics #strum_module_path::EnumProperty for #name #ty_generics #where_clause {
             fn get_str(&self, prop: &str) -> ::core::option::Option<&'static str> {
                 match self {
                     #(#arms),*
diff --git a/strum_macros/src/macros/enum_variant_names.rs b/strum_macros/src/macros/enum_variant_names.rs
index b99f179..c54d45d 100644
--- a/strum_macros/src/macros/enum_variant_names.rs
+++ b/strum_macros/src/macros/enum_variant_names.rs
@@ -16,6 +16,7 @@ pub fn enum_variant_names_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
 
     // Derives for the generated enum
     let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
 
     let names = variants
         .iter()
@@ -26,7 +27,7 @@ pub fn enum_variant_names_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         .collect::<syn::Result<Vec<_>>>()?;
 
     Ok(quote! {
-        impl #impl_generics ::strum::VariantNames for #name #ty_generics #where_clause {
+        impl #impl_generics #strum_module_path::VariantNames for #name #ty_generics #where_clause {
             const VARIANTS: &'static [&'static str] = &[ #(#names),* ];
         }
     })
diff --git a/strum_macros/src/macros/strings/as_ref_str.rs b/strum_macros/src/macros/strings/as_ref_str.rs
index ca17abe..b487b36 100644
--- a/strum_macros/src/macros/strings/as_ref_str.rs
+++ b/strum_macros/src/macros/strings/as_ref_str.rs
@@ -75,6 +75,8 @@ pub fn as_static_str_inner(
     let name = &ast.ident;
     let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
     let arms = get_arms(ast)?;
+    let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
 
     let mut generics = ast.generics.clone();
     generics
@@ -88,7 +90,7 @@ pub fn as_static_str_inner(
 
     Ok(match trait_variant {
         GenerateTraitVariant::AsStaticStr => quote! {
-            impl #impl_generics ::strum::AsStaticRef<str> for #name #ty_generics #where_clause {
+            impl #impl_generics #strum_module_path::AsStaticRef<str> for #name #ty_generics #where_clause {
                 fn as_static(&self) -> &'static str {
                     match *self {
                         #(#arms),*
diff --git a/strum_macros/src/macros/strings/from_string.rs b/strum_macros/src/macros/strings/from_string.rs
index 0ca6b9b..2977fd1 100644
--- a/strum_macros/src/macros/strings/from_string.rs
+++ b/strum_macros/src/macros/strings/from_string.rs
@@ -15,10 +15,11 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     };
 
     let type_properties = ast.get_type_properties()?;
+    let strum_module_path = type_properties.crate_module_path();
 
     let mut default_kw = None;
     let mut default =
-        quote! { _ => ::std::result::Result::Err(::strum::ParseError::VariantNotFound) };
+        quote! { _ => ::std::result::Result::Err(#strum_module_path::ParseError::VariantNotFound) };
     let mut arms = Vec::new();
     for variant in variants {
         let ident = &variant.ident;
@@ -89,7 +90,7 @@ pub fn from_string_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     Ok(quote! {
         #[allow(clippy::use_self)]
         impl #impl_generics ::std::str::FromStr for #name #ty_generics #where_clause {
-            type Err = ::strum::ParseError;
+            type Err = #strum_module_path::ParseError;
             fn from_str(s: &str) -> ::std::result::Result< #name #ty_generics , Self::Err> {
                 match s {
                     #(#arms),*


Test patch:
diff --git a/strum_tests/tests/enum_count.rs b/strum_tests/tests/enum_count.rs
index 4f03de0..794e563 100644
--- a/strum_tests/tests/enum_count.rs
+++ b/strum_tests/tests/enum_count.rs
@@ -16,3 +16,27 @@ fn simple_test() {
     assert_eq!(7, Week::COUNT);
     assert_eq!(Week::iter().count(), Week::COUNT);
 }
+
+#[test]
+fn crate_module_path_test() {
+    pub mod nested {
+        pub mod module {
+            pub use strum;
+        }
+    }
+
+    #[derive(Debug, EnumCount, EnumIter)]
+    #[strum(crate = "nested::module::strum")]
+    enum Week {
+        Sunday,
+        Monday,
+        Tuesday,
+        Wednesday,
+        Thursday,
+        Friday,
+        Saturday,
+    }
+
+    assert_eq!(7, Week::COUNT);
+    assert_eq!(Week::iter().count(), Week::COUNT);
+}
diff --git a/strum_tests/tests/enum_discriminants.rs b/strum_tests/tests/enum_discriminants.rs
index 69026ba..a9233db 100644
--- a/strum_tests/tests/enum_discriminants.rs
+++ b/strum_tests/tests/enum_discriminants.rs
@@ -279,3 +279,26 @@ fn override_visibility() {
         private::PubDiscriminants::VariantB,
     );
 }
+
+#[test]
+fn crate_module_path_test() {
+    pub mod nested {
+        pub mod module {
+            pub use strum;
+        }
+    }
+
+    #[allow(dead_code)]
+    #[derive(Debug, Eq, PartialEq, EnumDiscriminants)]
+    #[strum_discriminants(derive(EnumIter))]
+    #[strum(crate = "nested::module::strum")]
+    enum Simple {
+        Variant0,
+        Variant1,
+    }
+
+    let discriminants = SimpleDiscriminants::iter().collect::<Vec<_>>();
+    let expected = vec![SimpleDiscriminants::Variant0, SimpleDiscriminants::Variant1];
+
+    assert_eq!(expected, discriminants);
+}
diff --git a/strum_tests/tests/enum_iter.rs b/strum_tests/tests/enum_iter.rs
index 35ec9eb..7468267 100644
--- a/strum_tests/tests/enum_iter.rs
+++ b/strum_tests/tests/enum_iter.rs
@@ -175,3 +175,37 @@ fn take_nth_test() {
     assert_eq!(None, iter.next());
     assert_eq!(None, iter.next_back());
 }
+
+#[test]
+fn crate_module_path_test() {
+    pub mod nested {
+        pub mod module {
+            pub use strum;
+        }
+    }
+
+    #[derive(Debug, Eq, PartialEq, EnumIter)]
+    #[strum(crate = "nested::module::strum")]
+    enum Week {
+        Sunday,
+        Monday,
+        Tuesday,
+        Wednesday,
+        Thursday,
+        Friday,
+        Saturday,
+    }
+
+    let results = Week::iter().collect::<Vec<_>>();
+    let expected = vec![
+        Week::Sunday,
+        Week::Monday,
+        Week::Tuesday,
+        Week::Wednesday,
+        Week::Thursday,
+        Week::Friday,
+        Week::Saturday,
+    ];
+
+    assert_eq!(expected, results);
+}
diff --git a/strum_tests/tests/enum_message.rs b/strum_tests/tests/enum_message.rs
index 1d3de33..b448ac0 100644
--- a/strum_tests/tests/enum_message.rs
+++ b/strum_tests/tests/enum_message.rs
@@ -76,3 +76,31 @@ fn get_serializations() {
         (Brightness::BrightWhite).get_serializations()
     );
 }
+
+#[test]
+fn crate_module_path_test() {
+    pub mod nested {
+        pub mod module {
+            pub use strum;
+        }
+    }
+
+    #[allow(dead_code)]
+    #[derive(Debug, Eq, PartialEq, EnumMessage)]
+    #[strum(crate = "nested::module::strum")]
+    enum Pets {
+        #[strum(message = "I'm a dog")]
+        Dog,
+        #[strum(message = "I'm a cat")]
+        #[strum(detailed_message = "I'm a very exquisite striped cat")]
+        Cat,
+        #[strum(detailed_message = "My fish is named Charles McFish")]
+        Fish,
+        Bird,
+        #[strum(disabled)]
+        Hamster,
+    }
+
+    assert_eq!("I'm a dog", (Pets::Dog).get_message().unwrap());
+    assert_eq!("I'm a dog", (Pets::Dog).get_detailed_message().unwrap());
+}
diff --git a/strum_tests/tests/enum_props.rs b/strum_tests/tests/enum_props.rs
index 55be0f6..68fbb4d 100644
--- a/strum_tests/tests/enum_props.rs
+++ b/strum_tests/tests/enum_props.rs
@@ -24,3 +24,24 @@ fn prop_test_not_found_2() {
     let b = Test::B;
     assert_eq!(None, b.get_str("key"));
 }
+
+#[test]
+fn crate_module_path_test() {
+    pub mod nested {
+        pub mod module {
+            pub use strum;
+        }
+    }
+
+    #[allow(dead_code)]
+    #[derive(Debug, EnumProperty)]
+    #[strum(crate = "nested::module::strum")]
+    enum Test {
+        #[strum(props(key = "value"))]
+        A,
+        B,
+    }
+
+    let a = Test::A;
+    assert_eq!("value", a.get_str("key").unwrap());
+}
diff --git a/strum_tests/tests/enum_variant_names.rs b/strum_tests/tests/enum_variant_names.rs
index f9c8889..54ee857 100644
--- a/strum_tests/tests/enum_variant_names.rs
+++ b/strum_tests/tests/enum_variant_names.rs
@@ -104,3 +104,25 @@ fn clap_and_structopt() {
         color: Color,
     }
 }
+
+#[test]
+fn crate_module_path_test() {
+    pub mod nested {
+        pub mod module {
+            pub use strum;
+        }
+    }
+
+    #[allow(dead_code)]
+    #[derive(EnumVariantNames)]
+    #[strum(crate = "nested::module::strum")]
+    enum Color {
+        Red,
+        #[strum(serialize = "b")]
+        Blue,
+        #[strum(to_string = "y", serialize = "yy")]
+        Yellow,
+    }
+
+    assert_eq!(Color::VARIANTS, &["Red", "b", "y"]);
+}