Repository: Peternator7/strum (rust)

Issue:
Add ability to specify visibility in generated type via `#[strum_discriminants]`
By "visibility", I'm referring to `pub`, `pub(crate)`, etc. I have an enum that I want to keep crate-scoped, but I'd like to generate discriminants and have those discriminants be exported publicly. Perhaps it could be another property in `#[strum_discriminants]`, such as: `#[strum_discriminants(vis(pub))]`?

Pull request description:
Adding visibility option `vis(...)` to `#[strum_discriminants]` (#137)
Closes #137 

Validated using doctest example in the `EnumDiscriminants` section. While I was able to show in the doctest that I'm able to use the public discriminants properly (i.e. they work and are visible), I'm not sure how to show that I'm *not* exposing the original enum publicly. If anyone has suggestions for that, that would be helpful to include!

Patch:
diff --git a/strum_macros/src/helpers/metadata.rs b/strum_macros/src/helpers/metadata.rs
index cd36f60..e8b3558 100644
--- a/strum_macros/src/helpers/metadata.rs
+++ b/strum_macros/src/helpers/metadata.rs
@@ -4,7 +4,7 @@ use syn::{
     parse::{Parse, ParseStream},
     punctuated::Punctuated,
     spanned::Spanned,
-    Attribute, DeriveInput, Ident, LitStr, Path, Token, Variant,
+    Attribute, DeriveInput, Ident, LitStr, Path, Token, Variant, Visibility,
 };
 
 use super::case_style::CaseStyle;
@@ -18,6 +18,7 @@ pub mod kw {
     // enum discriminant metadata
     custom_keyword!(derive);
     custom_keyword!(name);
+    custom_keyword!(vis);
 
     // variant metadata
     custom_keyword!(message);
@@ -56,6 +57,7 @@ impl Spanned for EnumMeta {
 pub enum EnumDiscriminantsMeta {
     Derive { kw: kw::derive, paths: Vec<Path> },
     Name { kw: kw::name, name: Ident },
+    Vis { kw: kw::vis, vis: Visibility },
     Other { path: Path, nested: TokenStream },
 }
 
@@ -76,6 +78,12 @@ impl Parse for EnumDiscriminantsMeta {
             parenthesized!(content in input);
             let name = content.parse()?;
             Ok(EnumDiscriminantsMeta::Name { kw, name })
+        } else if input.peek(kw::vis) {
+            let kw = input.parse()?;
+            let content;
+            parenthesized!(content in input);
+            let vis = content.parse()?;
+            Ok(EnumDiscriminantsMeta::Vis { kw, vis })
         } else {
             let path = input.parse()?;
             let content;
@@ -91,6 +99,7 @@ impl Spanned for EnumDiscriminantsMeta {
         match self {
             EnumDiscriminantsMeta::Derive { kw, .. } => kw.span,
             EnumDiscriminantsMeta::Name { kw, .. } => kw.span,
+            EnumDiscriminantsMeta::Vis { kw, .. } => kw.span,
             EnumDiscriminantsMeta::Other { path, .. } => path.span(),
         }
     }
diff --git a/strum_macros/src/helpers/type_props.rs b/strum_macros/src/helpers/type_props.rs
index c942c5d..1e9c1e4 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};
+use syn::{DeriveInput, Ident, Path, Visibility};
 
 use super::case_style::CaseStyle;
 use super::metadata::{DeriveInputExt, EnumDiscriminantsMeta, EnumMeta};
@@ -17,6 +17,7 @@ pub struct StrumTypeProperties {
     pub discriminant_derives: Vec<Path>,
     pub discriminant_name: Option<Ident>,
     pub discriminant_others: Vec<TokenStream>,
+    pub discriminant_vis: Option<Visibility>,
 }
 
 impl HasTypeProperties for DeriveInput {
@@ -41,6 +42,7 @@ impl HasTypeProperties for DeriveInput {
         }
 
         let mut name_kw = None;
+        let mut vis_kw = None;
         for meta in discriminants_meta {
             match meta {
                 EnumDiscriminantsMeta::Derive { paths, .. } => {
@@ -54,6 +56,14 @@ impl HasTypeProperties for DeriveInput {
                     name_kw = Some(kw);
                     output.discriminant_name = Some(name);
                 }
+                EnumDiscriminantsMeta::Vis { vis, kw } => {
+                    if let Some(fst_kw) = vis_kw {
+                        return Err(occurrence_error(fst_kw, kw, "vis"));
+                    }
+
+                    vis_kw = Some(kw);
+                    output.discriminant_vis = Some(vis);
+                }
                 EnumDiscriminantsMeta::Other { path, nested } => {
                     output.discriminant_others.push(quote! { #path(#nested) });
                 }
diff --git a/strum_macros/src/lib.rs b/strum_macros/src/lib.rs
index f36a0e2..444794b 100644
--- a/strum_macros/src/lib.rs
+++ b/strum_macros/src/lib.rs
@@ -586,7 +586,7 @@ pub fn enum_properties(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
 ///     Variant1 { a: NonDefault },
 /// }
 ///
-/// // You can also rename the generated enum using the `#[strum_discriminants(name(OtherName))]` attribute:
+/// // You can rename the generated enum using the `#[strum_discriminants(name(OtherName))]` attribute:
 /// # #[allow(dead_code)]
 /// #[derive(Debug, EnumDiscriminants)]
 /// #[strum_discriminants(derive(EnumIter))]
@@ -607,6 +607,34 @@ pub fn enum_properties(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
 ///     MyVariants::iter().collect::<Vec<_>>()
 /// );
 /// ```
+///
+/// It is also possible to specify the visibility (e.g. `pub`/`pub(crate)`/etc.)
+/// of the generated enum. By default, the generated enum inherits the
+/// visibility of the parent enum it was generated from.
+///
+/// ```nocompile
+/// use strum_macros::EnumDiscriminants;
+///
+/// // You can set the visibility of the generated enum using the `#[strum_discriminants(vis(..))]` attribute:
+/// mod inner {
+///     use strum_macros::EnumDiscriminants;
+///
+///     # #[allow(dead_code)]
+///     #[derive(Debug, EnumDiscriminants)]
+///     #[strum_discriminants(vis(pub))]
+///     #[strum_discriminants(name(PubDiscriminants))]
+///     enum PrivateEnum {
+///         Variant0(bool),
+///         Variant1 { a: bool },
+///     }
+/// }
+///
+/// // test visibility example, `PrivateEnum` should not be accessible here
+/// assert_ne!(
+///     inner::PubDiscriminants::Variant0,
+///     inner::PubDiscriminants::Variant1,
+/// );
+/// ```
 #[cfg_attr(
     feature = "verbose-enumdiscriminants-name",
     proc_macro_derive(StrumEnumDiscriminants, attributes(strum, strum_discriminants))
diff --git a/strum_macros/src/macros/enum_discriminants.rs b/strum_macros/src/macros/enum_discriminants.rs
index 991b8f7..1aac79e 100644
--- a/strum_macros/src/macros/enum_discriminants.rs
+++ b/strum_macros/src/macros/enum_discriminants.rs
@@ -36,6 +36,7 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
     );
 
     let discriminants_name = type_properties.discriminant_name.unwrap_or(default_name);
+    let discriminants_vis = type_properties.discriminant_vis.unwrap_or_else(|| vis.clone());
 
     // Pass through all other attributes
     let pass_though_attributes = type_properties.discriminant_others;
@@ -127,7 +128,7 @@ pub fn enum_discriminants_inner(ast: &DeriveInput) -> syn::Result<TokenStream> {
         /// Auto-generated discriminant enum variants
         #derives
         #(#[ #pass_though_attributes ])*
-        #vis enum #discriminants_name {
+        #discriminants_vis enum #discriminants_name {
             #(#discriminants),*
         }
 


Test patch:
diff --git a/strum_tests/build.rs b/strum_tests/build.rs
index 9cf597e..2f7ff23 100644
--- a/strum_tests/build.rs
+++ b/strum_tests/build.rs
@@ -2,5 +2,6 @@ fn main() {
     // Check if version of rustc is >= 1.34
     if let Some(true) = version_check::is_min_version("1.34.0") {
         println!("cargo:rustc-cfg=try_from");
+        println!("cargo:rustc-cfg=bare_pub");
     }
 }
diff --git a/strum_tests/tests/enum_discriminants.rs b/strum_tests/tests/enum_discriminants.rs
index 04af86b..0bdd69b 100644
--- a/strum_tests/tests/enum_discriminants.rs
+++ b/strum_tests/tests/enum_discriminants.rs
@@ -215,3 +215,45 @@ fn filter_variant_attributes_pass_through() {
         VariantFilterAttrDiscs::from_str("dark_black").unwrap()
     );
 }
+
+#[cfg(bare_pub)]
+#[test]
+fn override_visibility() {
+    mod private {
+        use super::*;
+
+        #[allow(dead_code)]
+        #[derive(EnumDiscriminants)]
+        #[strum_discriminants(name(PubDiscriminants), vis(pub))]
+        enum PrivateEnum {
+            VariantA(bool),
+            VariantB(bool),
+        }
+    }
+
+    assert_ne!(
+        private::PubDiscriminants::VariantA,
+        private::PubDiscriminants::VariantB,
+    );
+}
+
+#[cfg(not(bare_pub))]
+#[test]
+fn override_visibility() {
+    mod private {
+        use super::*;
+
+        #[allow(dead_code)]
+        #[derive(EnumDiscriminants)]
+        #[strum_discriminants(name(PubDiscriminants), vis(r#pub))]
+        enum PrivateEnum {
+            VariantA(bool),
+            VariantB(bool),
+        }
+    }
+
+    assert_ne!(
+        private::PubDiscriminants::VariantA,
+        private::PubDiscriminants::VariantB,
+    );
+}