Можно ли позволить макросу расширяться до структурного поля? - PullRequest
0 голосов
/ 02 декабря 2018

Я хотел бы сделать следующее, но макросы в этой позиции, кажется, не работают (я получаю error: expected `:`, found `!`. Как я могу сопоставить с образцом отдельные элементы структуры и прикрепить к ним атрибуты на основе совпадения?

use serde_derive::Serialize;

macro_rules! optional_param {
    ($name:ident : Option<$type:ty>) => { #[serde(skip_serializing_if = "Option::is_none")] pub $name: Option<$ty> };
    ($name:ident : Vec   <$type:ty>) => { #[serde(skip_serializing_if = "Vec::is_empty"  )] pub $name: Vec   <$ty> };
    ($name:ident : bool            ) => { #[serde(skip_serializing_if = "bool::not"      )] pub $name: bool        };
}

macro_rules! impl_extra {
    ( $name:ident { $( $param:ident : $type:ty ),* $(,)* } ) => (
        #[derive(Default,Debug,Serialize)]
        pub struct $name {
            $( optional_param!($param : $type), )*
        }
    );
}

impl_extra!(MyStruct { member: Option<String> });

Ссылка на игровую площадку

1 Ответ

0 голосов
/ 02 декабря 2018

Действительно, вызовы макросов недопустимы в середине определения структуры.Тем не менее, мы можем использовать метавариабельные там.Хитрость заключается в том, чтобы анализировать параметры постепенно , создавая токены для определений полей по пути, и, когда больше нет входных данных для обработки, генерировать определение структуры с определениями полей, полученными из мета-переменной.

В качестве первого шага давайте посмотрим, как конкретно выглядит макрос, который не обрабатывает типы полей:

macro_rules! impl_extra {
    ( @ $name:ident { } -> ($($result:tt)*) ) => (
        #[derive(Default, Debug, Serialize)]
        pub struct $name {
            $($result)*
        }
    );

    ( @ $name:ident { $param:ident : $type:ty, $($rest:tt)* } -> ($($result:tt)*) ) => (
        impl_extra!(@ $name { $($rest)* } -> (
            $($result)*
            pub $param : $type,
        ));
    );

    ( $name:ident { $( $param:ident : $type:ty ),* $(,)* } ) => (
        impl_extra!(@ $name { $($param : $type,)* } -> ());
    );
}

Единственное, что делает этот макрос, это добавляет pub в каждое поле и определяетpub struct с атрибутом #[derive].Первое правило обрабатывает случай терминала, т.е. когда больше нет полей для обработки.Второе правило обрабатывает рекурсивный регистр, а третье правило обрабатывает открытый синтаксис макроса и преобразует его в синтаксис «обработки».

Обратите внимание, что я использую @ в качестве исходного токена длявнутренние правила, чтобы отличать их от «публичных» правил.Если этот макрос не предназначен для экспорта в другие ящики, вы также можете переместить внутренние правила в другой макрос.Если макрос экспортируется, то, возможно, придется экспортировать и отдельный макрос для внутренних правил.

Теперь давайте разберемся с различными типами полей:

macro_rules! impl_extra {
    ( @ $name:ident { } -> ($($result:tt)*) ) => (
        #[derive(Default, Debug, Serialize)]
        pub struct $name {
            $($result)*
        }
    );

    ( @ $name:ident { $param:ident : Option<$type:ty>, $($rest:tt)* } -> ($($result:tt)*) ) => (
        impl_extra!(@ $name { $($rest)* } -> (
            $($result)*
            #[serde(skip_serializing_if = "Option::is_none")]
            pub $param : Option<$type>,
        ));
    );

    ( @ $name:ident { $param:ident : Vec<$type:ty>, $($rest:tt)* } -> ($($result:tt)*) ) => (
        impl_extra!(@ $name { $($rest)* } -> (
            $($result)*
            #[serde(skip_serializing_if = "Vec::is_empty")]
            pub $param : Vec<$type>,
        ));
    );

    ( @ $name:ident { $param:ident : bool, $($rest:tt)* } -> ($($result:tt)*) ) => (
        impl_extra!(@ $name { $($rest)* } -> (
            $($result)*
            #[serde(skip_serializing_if = "bool::not")]
            pub $param : bool,
        ));
    );

    ( $name:ident { $( $param:ident : $($type:tt)* ),* $(,)* } ) => (
        impl_extra!(@ $name { $($param : $($type)*,)* } -> ());
    );
}

Обратите внимание, что естьРазница в последнем правиле: вместо сопоставления на ty мы теперь сопоставляем на последовательности tt.Это потому, что когда макрос проанализировал ty, он не может быть разбит, поэтому, когда мы делаем рекурсивный вызов макроса, ty не может совпадать с чем-то вроде Option<$type:ty>.

...