Search code examples
rustrust-macros

Automatically create enum variants


I want macros that automatically add structures with attribute macros to the enum. For example

#[add_enum(AutoEnum)]
struct A(usize);
#[add_enum(AutoEnum)]
struct B(i32);

#[auto_enum]
enum AutoEnum{}

Expand on the above

struct A(usize);
struct B(i32);

enum AutoEnum{
  A(A),
  B(B),
}

I have looked around and could not find a way to get information on other attribute macros (e.g. #[add_enum(AutoEnum)] from #[auto_enum]).

Is this possible?


Solution

  • Here's a crazy idea that will probably don't work in your real code but should work in the example you gave (after a slight modification) so 🤷.

    As opposed to every other item in Rust, macros can shadow each other. If you will have two macros with the same name, the macro defined later will shadow the first macro.

    So, I think... What if we will define macros for all possible names of the enum variant, then shadow them for each actual enum? This will give us a way to expand every variant to what we want.

    It will be clearer with an example. Suppose we want to only support letters A-C as variant names, and only support one letter (no AC). We first need some place to define the "default" no-variant macros, and it needs to be before any variant. So let's modify the example as follows:

    #[auto_enum(declare)] // New!
    enum AutoEnum {}
    
    #[add_enum(AutoEnum)]
    struct A(usize);
    #[add_enum(AutoEnum)]
    struct B(i32);
    
    #[auto_enum(define)] // Changed! (This is not necessary).
    enum AutoEnum {}
    

    Then, the #[auto_enum(declare)] will expand to the following three "default" macros, that pass their input as-is to the next macro except the last macro that creates the final enum:

    macro_rules! AutoEnum_A {
        ( $($t:tt)* ) => { AutoEnum_B! { $($t)* } };
    }
    macro_rules! AutoEnum_B {
        ( $($t:tt)* ) => { AutoEnum_C! { $($t)* } };
    }
    macro_rules! AutoEnum_C {
        ( $($t:tt)* ) => {
            enum AutoEnum { $($t)* }
        };
    }
    

    Now, every #[add_enum] call will expand to a macro that shadows the "default" macro, and instead of passing the input to the next macro as-is, it adds its variant to it:

    // #[add_enum(AutoEnum)]
    // struct A(usize);
    macro_rules! AutoEnum_A {
        ( $($t:tt)* ) => { AutoEnum_B! { $($t)* A(usize), } };
    }
    // #[add_enum(AutoEnum)]
    // struct B(i32);
    macro_rules! AutoEnum_B {
        ( $($t:tt)* ) => { AutoEnum_C! { $($t)* B(i32), } };
    }
    

    Finally, the #[auto_enum(define)] will expand to a call to the first macro, a call that eventually at the end of the chain will generate the enum:

    AutoEnum_A! {}
    

    Of course, for every letter (assuming you want to support uppercase/lowercase/numbers/underscores) this requires 63 combinations, which means that even supporting 5 letters would require 992436543 macros, so this is not really usable. But still, an interesting idea to explore.

    I'm still seeking better ideas that are hopefully also usable.