0

I'm writing a procedural macro to convert the variants of an enum into individual structs and implement some traits for that struct.

This works fine for unit and unnamed variants, but variants with named data will cause it to silently fail :).

Here is an example proc_macro definition:

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

When I run it on this code:

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

I get this expanded code (via cargo expand):


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

The expected result however would be:

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 Answers1

0

The problem is in the semi-colon in the quote!().

Structs with unnamed fields should be terminated with a semicolon:

pub struct MultiUnNamed(bool, bool, bool);

But structs with named fields shouldn't:

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

The problem was solved by replacing:

quote! {
    pub struct #var_id #fields;
}

with

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

(Also had to import syn::Fields)