Parsing enum inner type in "proc_macro_derive"

I'm using proc_macro2 and syn to parse enums that look like this:

#[derive(Message)]
enum Animal {
   #[prop]
   Dog(X), // X can be X, Option<X>, Vec<X> or Option<Vec<X>>
   #[prop]
   Cat(Y), // Y can be Y, Option<Y>, Vec<Y> or Option<Vec<Y>>
}

I have a macro that knows how to read the name of the enum and its fields (Dog or Cat) but it's not clear to me how to parse inner types:

#[proc_macro_derive(Message, attributes(prop))]
pub fn describe(input: TokenStream) -> TokenStream {
    let DeriveInput { ident, data, .. } = parse_macro_input!(input);
    match data {
        syn::Data::Enum(edata) => {
            for v in edata.variants {
               let name = v.ident.clone();
               let inner = // how to get `ident` for inner type (X and Y)
            }
            (quote! {}).into()
        },
        _ => {}
    }
}

How can I get the ident for the inner type? I would like to get everything inside each enum into a "string" (#ident) because it seems very difficult to parse every variant.

I need a complete name of the inner type (e.g. Vec) to construct chunks like this:

quote!{
   #inner::from(100) // the result would be e.g. "X::from(100)"
}

Also, is it possible to get to the inner type with Self::A::Self::from(...) where the last Self would actually represent the inner type?

You just have to follow the tree of values further. Variants have a fields field, of which both the named and the unnamed variants have a Punctuated<Field, Comma> associated data. You can in turn iterate over the individual Fields, which have a ty: Type. Note that the type is not necessarily a single identifier.

By the way, you could probably generate the required code in a much simpler way: just emit std::convert::From::from(100) instead of relying on the exact type name.

@H2CO3 so this means I have to actually continue parsing the tree and there's no shorter way?

Regarding the std::convert, maybe I chose the wrong function name which triggered an assumption. I need the type because it will be used in a generated function that will look like this:

impl Input for #ident {
   fn compile() -> Self {
      if condition1 {
         return  Self::#name1(#inner1{});
      }
      if condition2 {
         return  Self::#name2(#inner2{});
      }
}

Any further suggestions?

Well it's already parsed for you, you just have to access the appropriate fields and variants of the nested AST nodes that the parsing yields.

Got it. Well, the issue is just that the tree could be deep :). I would just do field.ty.to_string() instead of reconstructing the type if that would be possible.

Well, yes, indeed. This part of macro writing is quite boilerplate-heavy, and not fun/convenient. You could write the AST matching logic once, encapsulate it in a generic function, and then try to re-use it for other purposes, so at least you don't have to write it multiple times.

(Incidentally, macro authors are one of the reasons I tend to push back against mainly syntax-related feature proposals. I mean, in this specific case, named fields and multiple fields in a variant are great, but we could write with the same degree of expressiveness even if only a single unnamed field was permitted, since that could be a tuple or struct type.)

1 Like

Got it. Thanks.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.