Search code examples
rustrust-macros

Can I rewrite this macro_rules! macro in a way that works with rustfmt?


I would like to use a macro to generate identical impl blocks for multiple concrete types. My code currently looks something like this:

macro_rules! impl_methods {
    ($ty:ty, {  $($method:item);+} ) => {
        impl $ty { $($method)+ }
    };
    ($ty:ty, $($more:ty),+ {$($method:item);+}) => {
        impl_methods!($ty, {$($method);+});
        impl_methods!($($more),+, {$($method);+});
    };
}

struct Hi;
struct Hello;

impl_methods!(Hi, Hello {
    /// `true` if it works, good
    fn works_good(&self) -> bool {
        true
    };

    /// `true` if rustfmt is working
    fn gets_rustfmt(&self) -> bool {
        false
    }
});

assert!(Hi.works_good() && Hello.works_good());
assert!(!(Hi.gets_rustfmt() | Hello.gets_rustfmt()));

This works well enough (the impls get generated) but it has one frustrating problem; the methods that are defined inside the macro don't get formatted by rustfmt.

This is a small problem, but it's annoying enough that I'm curious about a solution. I know that rustfmt will format the contents of a macro if those contents have some form (are an expression?) and so for instance the following macro's contents will get formatted:

macro_rules! fmt_me {
    ($inner:item) => {
        $inner
    };
}

fmt_me!(fn will_get_formatted() -> bool { true });

And so I'm hoping that there is some way that I can write my macro like,

impl_methods!(Hi, Hello {
    fmt_me!(fn my_method(&self) -> bool { true });
    fmt_me!(fn my_other_method(&self) -> bool { false });
});

And have each individual method get covered by rustfmt.

Is this possible? Is there some magic incantation that will give me the lovely formatting I desire?

Answer

Thanks to the answer below (from @seiichi-uchida), I can get this working with the following code:

macro_rules! impl_methods {
    ($ty:ty, {  $($method:item)+} ) => {
        impl $ty { $($method)+ }
    };
    ($ty:ty, $($more:ty),+, {$($method:item)+}) => {
        impl_methods!($ty, {$($method)+});
        impl_methods!($($more),+, {$($method)+});
    };
}

macro_rules! fmt_me {
    ($inner:item) => {
        $inner
    };
}

// called like:

impl_methods!(Hi, Hello, {
    fmt_me!(fn this_is_a_method(&self) -> bool { true });
    fmt_me!(fn this_is_another_method(&self) -> bool { true });
});

Solution

  • Adding a comma between the last type and the impl block should do the trick:

    impl_methods!(Hi, Hello, {
        fmt_me!(fn my_method(&self) -> bool { true });
        fmt_me!(fn my_other_method(&self) -> bool { false });
    });
    

    This will be formatted to:

     impl_methods!(Hi, Hello, {
    -    fmt_me!(fn my_method(&self) -> bool { true });
    -    fmt_me!(fn my_other_method(&self) -> bool { false });
    +    fmt_me!(
    +        fn my_method(&self) -> bool {
    +            true
    +        }
    +    );
    +    fmt_me!(
    +        fn my_other_method(&self) -> bool {
    +            false
    +        }
    +    );
     });
    

    In general, rustfmt can only format macro calls whose arguments can be parsed as valid Rust AST node (with some exceptions).