Как различать guish разные типы элементов в макросах macro_rules? - PullRequest
0 голосов
/ 09 июля 2020

Я хочу написать макрос на основе macro_rules, который будет использоваться для обертывания серии псевдонимов типов и определений структур. Я могу сопоставить «элементы» с помощью $e:item, но это будет соответствовать как псевдонимам, так и структурам. Я хотел бы рассматривать эти два отдельно (мне нужно добавить #[derive(...)] только в структуры). Должен ли я имитировать их синтаксис напрямую, сопоставляя что-то вроде type $name:ident = $type:ty;, или есть способ лучше? Этот маршрут кажется раздражающим для структур из-за обычного vs кортежа. Если бы я также хотел различать guish функции, это было бы очень болезненно, потому что они имеют много синтаксических вариаций.

1 Ответ

1 голос
/ 09 июля 2020

Я считаю, что для этой проблемы несколько простых случаев можно решить с помощью macro_rules!, но это, вероятно, будет ограничено (вы не можете смотреть вперед) и очень подвержено ошибкам. Я привожу пример только для типов, надеюсь, это будет достаточно убедительно, чтобы избежать macro_rules!. Рассмотрим этот простой макрос:

macro_rules! traverse_types {
    ($(type $tp:ident = $alias:ident;)*) => {
        $(type $tp = $alias;)*
    }
}

traverse_types!(
    type T = i32;
    type Y = Result<i32, u64>;
);

Он отлично подходит для тривиальных псевдонимов. В какой-то момент вы, вероятно, также захотите обрабатывать общие псевдонимы типа c (т.е. в форме type R<Y> = ...). Хорошо, вы все равно можете переписать макрос в рекурсивной форме (а это уже нетривиальная задача) для обработки всех случаев. Затем вы понимаете, что универсальные шаблоны могут быть сложными (границы типов, параметры времени жизни, предложение where, типы по умолчанию и т. Д. c):

type W<A: Send + Sync> = Option<A>;
type X<A: Iterator<Item = usize>> where A: 'static = Option<A>;
type Y<'a, X, Y: for<'t> Trait<'t>> = Result<&'a X, Y>;
type Z<A, B = u64> = Result<A, B>;

Вероятно, все эти случаи все еще можно обрабатывать с помощью еле читаемый macro_rules!. Тем не менее, было бы действительно трудно понять (даже человеку, который это написал), что происходит. Кроме того, сложно поддерживать новый синтаксис (например, impl-trait alias type T = impl K), вам даже может потребоваться полностью переписать макрос. И я покрываю только часть type aliases, есть еще кое-что, что нужно сделать для struct s.

Моя точка зрения: лучше избегать macro_rules! для этой (и аналогичной) проблемы (-ов), процедурный макрос - намного лучший инструмент для этого. Его легче читать (и, следовательно, расширять), и новый синтаксис обрабатывается бесплатно (если поддерживаются syn и quote ящики). Для псевдонима типа это можно сделать так же просто, как:

extern crate proc_macro;

use proc_macro::TokenStream;
use syn::parse::{Parse, ParseStream};

struct TypeAliases {
    aliases: Vec<syn::ItemType>,
}

impl Parse for TypeAliases {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut aliases = vec![];
        while !input.is_empty() {
            aliases.push(input.parse()?);
        }
        Ok(Self { aliases })
    }
}

#[proc_macro]
pub fn traverse_types(token: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(token as TypeAliases);

    // do smth with input here

    // You may remove this binding by implementing a `Deref` or `quote::ToTokens` for `TypeAliases`
    let aliases = input.aliases;
    let gen = quote::quote! {
        #(#aliases)*
    };

    TokenStream::from(gen)
}

Для кода синтаксического анализа struct то же самое, что и для типа ItemStruct, а также вам потребуется предварительный просмотр, чтобы определить, является ли это псевдонимом типа или структуру, для этого есть очень похожий пример в syn .

...