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{});
}
}
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.)