Преобразование вариантов перечисления с именованными данными в отдельные структуры с использованием процедурных макросов

Я пишу процедурный макрос, чтобы преобразовать варианты перечисления в отдельные структуры и реализовать некоторые черты для этой структуры.

Это хорошо работает для единичных и безымянных вариантов, но варианты с именованными данными приведут к молчаливому сбою:).

Вот пример определения proc_macro:

extern crate proc_macro;
use quote::ToTokens;

use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DataEnum, DeriveInput};

#[proc_macro_derive(MyProcMacro)]
pub fn derive_my_proc_macro(input: TokenStream) -> TokenStream {
    let ast: DeriveInput = syn::parse(input).unwrap();

    // Error out if we're not annotating an enum
    let data: DataEnum = match ast.data {
        Data::Enum(d) => d,
        _ => panic!("My structs can only be derived for enums"),
    };
    let variants = data.variants.iter();
    let variant_structs = variants.map(|v| {
        let var_id = &v.ident;
        let fields = v.fields.clone().into_token_stream();
        quote! {
            pub struct #var_id #fields;
            /* Implement traits for the new struct and stuff */
        }
    });
    let gen = quote! {
        #(#variant_structs)*
    };
    gen.into()
}

Когда я запускаю его на этот код:

#[derive(MyProcMacro)]
enum AllTypesOfVariants {
    Unit,
    OneUnNamed(bool),
    MultiUnNamed(bool, bool, bool),
    Named { _thing: bool },
    MultiNamed { _thing: bool, _thing2: bool },
}

Я получил этот расширенный код (через cargo expand):


pub struct Unit;
pub struct OneUnNamed(bool);
pub struct MultiUnNamed(bool, bool, bool);
pub struct Named {
    _thing: bool,
}

Ожидаемый результат однако будет:

pub struct Unit;
pub struct OneUnNamed(bool);
pub struct MultiUnNamed(bool, bool, bool);
pub struct Named {
    _thing: bool,
}
pub struct MultiNamed {
    _thing: bool,
    _thing2: bool
}

1 ответ

Проблема в точке с запятой в quote!(),

Структуры с неназванными полями должны заканчиваться точкой с запятой:

pub struct MultiUnNamed(bool, bool, bool);

Но структуры с именованными полями не должны:

pub struct MultiNamed {
    _thing: bool,
    _thing2: bool
}

Проблема была решена путем замены:

quote! {
    pub struct #var_id #fields;
}

с участием

match &v.fields {
    Fields::Named(_) => {
        quote! {
            pub struct #var_id #fields
        }
    },
    _ => {
        quote! {
            pub struct #var_id #fields;
        }
    }
}

(Также пришлось импортировать syn::Fields)

Другие вопросы по тегам