Repository: bitflags/bitflags (rust)

Issue:
Cannot use `#[doc(alias)]`
The following code:
```rs
bitflags::bitflags! {
  #[doc(alias = "SYMBOLIC_LINK_FLAGS")]
  pub struct SymbolicLinkFlags:u32 {
    #[doc(alias = "SYMBOLIC_LINK_FLAG_DIRECTORY")]
    const DIRECTORY = 0x1;
    #[doc(alias = "SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE")]
    const ALLOW_UNPRIVILEGED_CREATE = 0x2;
  }
}
```
Produces the error:
```
error: `#[doc(alias = "...")]` isn't allowed on expression
  --> src\fs.rs:67:15
   |
67 |         #[doc(alias = "SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE")]
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
```

Pull request:
Add a top-level macro for filtering attributes
Closes #308 

This PR re-introduces a macro that filters attributes so that only `cfg`s are applied on expressions that use flags constants. I noticed we already have the limitation that attributes must be plain identifiers, so attributes like `#[some::path]` already don't work on flags constants themselves.

I'd appreciate a sanity check of this macro if anyone is keen 🙂 Especially if there's any better way to document what's going on or make the thing more compact so we can easily add any more attributes that we need to propagate without the whole thing becoming impossible to follow.

Patch:
diff --git a/src/lib.rs b/src/lib.rs
index 7ba784c..a4a4046 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -558,6 +558,315 @@ macro_rules! bitflags {
         }
 
         $($t:tt)*
+    ) => {
+        __declare_bitflags!(
+            $(#[$outer])*
+            $vis struct $BitFlags: $T {
+                $(
+                    $(#[$inner $($args)*])*
+                    const $Flag = $value;
+                )*
+            }
+        );
+
+        bitflags! {
+            $($t)*
+        }
+    };
+    () => {};
+}
+
+/// A macro that processed the input to `bitflags!` and shuffles attributes around
+/// based on whether or not they're "expression-safe".
+///
+/// This macro is a token-tree muncher that works on 2 levels:
+///
+/// 1. Each flag, like `#[cfg(true)] const A: 42`
+/// 2. Each attribute on that flag, like `#[cfg(true)]`
+///
+/// Flags and attributes start in an "unprocessed" list, and are shifted one token
+/// at a time into an appropriate processed list until the unprocessed lists are empty.
+///
+/// For each attribute, we explicitly match on its identifier, like `cfg` to determine
+/// whether or not it should be considered expression-safe.
+///
+/// If you find yourself with an attribute that should be considered expression-safe
+/// and isn't, it can be added here.
+#[macro_export(local_inner_macros)]
+#[doc(hidden)]
+macro_rules! __declare_bitflags {
+    // Entrypoint: Move all flags and all attributes into `unprocessed` lists
+    // where they'll be munched one-at-a-time
+    (
+        $(#[$outer:meta])*
+        $vis:vis struct $BitFlags:ident: $T:ty {
+            $(
+                $(#[$inner:ident $($args:tt)*])*
+                const $Flag:ident = $value:expr;
+            )*
+        }
+    ) => {
+        __declare_bitflags! {
+            decl: {
+                attrs: [$(#[$outer])*],
+                vis: $vis,
+                ident: $BitFlags,
+                ty: $T,
+            },
+            flags: {
+                // All flags start here
+                unprocessed: [
+                    $(
+                        {
+                            ident: $Flag,
+                            value: $value,
+                            attrs: {
+                                // All attributes start here
+                                unprocessed: [$(#[$inner $($args)*])*],
+                                processed: {
+                                    // Attributes that should be added to item declarations go here
+                                    decl: [],
+                                    // Attributes that are safe on expressions go here
+                                    expr: [],
+                                }
+                            },
+                        },
+                    )*
+                ],
+                // Flags that have had their attributes sorted are pushed here
+                processed: [],
+            }
+        }
+    };
+    // Process the next attribute on the current flag
+    // `cfg`: The next flag should be propagated to expressions
+    // NOTE: You can copy this rules block and replace `cfg` with
+    // your attribute name that should be considered expression-safe
+    (
+        decl: {
+            attrs: [$(#[$outer:meta])*],
+            vis: $vis:vis,
+            ident: $BitFlags:ident,
+            ty: $T:ty,
+        },
+        flags: {
+            unprocessed: [
+                {
+                    ident: $Flag:ident,
+                    value: $value:expr,
+                    attrs: {
+                        unprocessed: [
+                            // cfg matched here
+                            #[cfg $($args:tt)*]
+                            $($attrs_rest:tt)*
+                        ],
+                        processed: {
+                            decl: [$($decl:tt)*],
+                            expr: [$($expr:tt)*],
+                        }
+                    },
+                },
+                $($flags_rest:tt)*
+            ],
+            processed: [
+                $($flags:tt)*
+            ],
+        }
+    ) => {
+        __declare_bitflags! {
+            decl: {
+                attrs: [$(#[$outer])*],
+                vis: $vis,
+                ident: $BitFlags,
+                ty: $T,
+            },
+            flags: {
+                unprocessed: [
+                    {
+                        ident: $Flag,
+                        value: $value,
+                        attrs: {
+                            unprocessed: [
+                                $($attrs_rest)*
+                            ],
+                            processed: {
+                                decl: [
+                                    // cfg added here
+                                    #[cfg $($args)*]
+                                    $($decl)*
+                                ],
+                                expr: [
+                                    // cfg added here
+                                    #[cfg $($args)*]
+                                    $($expr)*
+                                ],
+                            }
+                        },
+                    },
+                    $($flags_rest)*
+                ],
+                processed: [
+                    $($flags)*
+                ],
+            }
+        }
+    };
+    // Process the next attribute on the current flag
+    // `$other`: The next flag should not be propagated to expressions
+    (
+        decl: {
+            attrs: [$(#[$outer:meta])*],
+            vis: $vis:vis,
+            ident: $BitFlags:ident,
+            ty: $T:ty,
+        },
+        flags: {
+            unprocessed: [
+                {
+                    ident: $Flag:ident,
+                    value: $value:expr,
+                    attrs: {
+                        unprocessed: [
+                            // $other matched here
+                            #[$other:ident $($args:tt)*]
+                            $($attrs_rest:tt)*
+                        ],
+                        processed: {
+                            decl: [$($decl:tt)*],
+                            expr: [$($expr:tt)*],
+                        }
+                    },
+                },
+                $($flags_rest:tt)*
+            ],
+            processed: [
+                $($flags:tt)*
+            ],
+        }
+    ) => {
+        __declare_bitflags! {
+            decl: {
+                attrs: [$(#[$outer])*],
+                vis: $vis,
+                ident: $BitFlags,
+                ty: $T,
+            },
+            flags: {
+                unprocessed: [
+                    {
+                        ident: $Flag,
+                        value: $value,
+                        attrs: {
+                            unprocessed: [
+                                $($attrs_rest)*
+                            ],
+                            processed: {
+                                decl: [
+                                    // $other added here
+                                    #[$other $($args)*]
+                                    $($decl)*
+                                ],
+                                expr: [
+                                    // $other not added here
+                                    $($expr)*
+                                ],
+                            }
+                        },
+                    },
+                    $($flags_rest)*
+                ],
+                processed: [
+                    $($flags)*
+                ],
+            }
+        }
+    };
+    // Complete the current flag once there are no unprocessed attributes left
+    (
+        decl: {
+            attrs: [$(#[$outer:meta])*],
+            vis: $vis:vis,
+            ident: $BitFlags:ident,
+            ty: $T:ty,
+        },
+        flags: {
+            unprocessed: [
+                {
+                    ident: $Flag:ident,
+                    value: $value:expr,
+                    attrs: {
+                        unprocessed: [],
+                        processed: {
+                            decl: [$($decl:tt)*],
+                            expr: [$($expr:tt)*],
+                        }
+                    },
+                },
+                $($flags_rest:tt)*
+            ],
+            processed: [
+                $($flags:tt)*
+            ],
+        }
+    ) => {
+        __declare_bitflags! {
+            decl: {
+                attrs: [$(#[$outer])*],
+                vis: $vis,
+                ident: $BitFlags,
+                ty: $T,
+            },
+            flags: {
+                unprocessed: [
+                    $($flags_rest)*
+                ],
+                processed: [
+                    $($flags)*
+                    {
+                        ident: $Flag,
+                        value: $value,
+                        attrs: {
+                            unprocessed: [],
+                            processed: {
+                                decl: [
+                                    $($decl)*
+                                ],
+                                expr: [
+                                    $($expr)*
+                                ],
+                            }
+                        },
+                    },
+                ],
+            }
+        }
+    };
+    // Once all attributes on all flags are processed, generate the actual code
+    (
+        decl: {
+            attrs: [$(#[$outer:meta])*],
+            vis: $vis:vis,
+            ident: $BitFlags:ident,
+            ty: $T:ty,
+        },
+        flags: {
+            unprocessed: [],
+            processed: [
+                $(
+                    {
+                        ident: $Flag:ident,
+                        value: $value:expr,
+                        attrs: {
+                            unprocessed: [],
+                            processed: {
+                                decl: [$(#[$decl:ident $($declargs:tt)*])*],
+                                expr: [$(#[$expr:ident $($exprargs:tt)*])*],
+                            }
+                        },
+                    },
+                )*
+            ],
+        }
     ) => {
         // Declared in the scope of the `bitflags!` call
         // This type appears in the end-user's API
@@ -570,7 +879,7 @@ macro_rules! bitflags {
         __impl_public_bitflags_consts! {
             $BitFlags {
                 $(
-                    $(#[$inner $($args)*])*
+                    $(#[$decl $($declargs)*])*
                     #[allow(
                         dead_code,
                         deprecated,
@@ -603,7 +912,7 @@ macro_rules! bitflags {
             __impl_internal_bitflags! {
                 InternalBitFlags: $T, $BitFlags, Iter, IterRaw {
                     $(
-                        $(#[$inner $($args)*])*
+                        $(#[$expr $($exprargs)*])*
                         $Flag;
                     )*
                 }
@@ -613,7 +922,7 @@ macro_rules! bitflags {
             __impl_external_bitflags! {
                 InternalBitFlags: $T {
                     $(
-                        $(#[$inner $($args)*])*
+                        $(#[$expr $($exprargs)*])*
                         $Flag;
                     )*
                 }
@@ -623,12 +932,7 @@ macro_rules! bitflags {
                 $BitFlags: $T, InternalBitFlags, Iter, IterRaw;
             }
         };
-
-        bitflags! {
-            $($t)*
-        }
-    };
-    () => {};
+    }
 }
 
 #[macro_use]


Test patch:
diff --git a/tests/compile-pass/doc_alias.rs b/tests/compile-pass/doc_alias.rs
new file mode 100644
index 0000000..8fe1d90
--- /dev/null
+++ b/tests/compile-pass/doc_alias.rs
@@ -0,0 +1,14 @@
+#[macro_use]
+extern crate bitflags;
+
+bitflags! {
+    #[doc(alias = "FLAG")]
+    pub struct Flags: u8 {
+        #[doc(alias = "FLAG_A")]
+        const A = 1;
+    }
+}
+
+fn main() {
+    
+}