Могу я переписать это macro_rules! макрос способом, который работает с rustfmt? - PullRequest
2 голосов
/ 28 мая 2020

Я хотел бы использовать макрос для создания идентичных impl блоков для нескольких конкретных типов. В настоящее время мой код выглядит примерно так:

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()));

Это работает достаточно хорошо (генерируются импы), но у него есть одна неприятная проблема; методы, определенные внутри макроса, не форматируются как rustfmt.

Это небольшая проблема, но она достаточно раздражает, и мне интересно ее решение. Я знаю, что rustfmt отформатирует содержимое макроса, если это содержимое имеет некоторую форму (является выражением?), И поэтому, например, следующее содержимое макроса будет отформатировано:

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

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

Итак, я надеясь , что есть какой-то способ, которым я могу написать свой макрос, например,

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

И чтобы каждый отдельный метод был покрыт rustfmt.

Возможно ли это? Есть ли какое-нибудь заклинание c магов, которое даст мне прекрасное форматирование, которое я хочу?

Ответ

Благодаря ответу ниже (от @ seiichi-uchida) я могу заставить это работать с следующий код:

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 });
});

1 Ответ

1 голос
/ 29 мая 2020

Добавление запятой между последним типом и блоком impl должно помочь:

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

Это будет отформатировано как:

 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
+        }
+    );
 });

В общем, rustfmt может только форматировать вызовы макросов, аргументы которых могут быть проанализированы как действительный узел AST Rust (с некоторыми исключениями).

...