Parse field from quote in derive macro

I have a use case where I need to insert some fields into an enum variant if it's annotated with a certain attribute. So far I've tried the following approach:

use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Field, Fields, GenericParam, Generics, Index, AttrStyle, Visibility, Token, VisPublic};

// ...

    match ast.data {
        Data::Enum(data) => {
            for variant in data.variants {

                let mut is_created = false;
                let mut is_votable = false;

                for attr in variant.attrs {

                    if let AttrStyle::Outer = attr.style {
                        // Not sure if this will work...
                        if attr.path.is_ident("created") {
                            is_created = true;
                        } else if attr.path.is_ident("votable") {
                            is_votable = true;
                        }

                    } else {
                        panic!("invalid attribute!")
                    }
                }

                if is_created {
                    match variant.fields {
                        Fields::Named(fields) => {
                            fields.named.push(
                                Field::parse_named(
                                // What do I do here?
                                ).unwrap()
                            )
                        }
                    }
                }

            }
        }
        _ => panic!("Bad input data!")
    }

Ideally, I'd want to do something like:

// ...
                if is_created {
                    match variant.fields {
                        Fields::Named(fields) => {
                            fields.named.push(
                                Field::parse_named(
                                    parse_quote! {
                                        pub a: u32
                                    }
                                ).unwrap()
                            )
                        }
                    }
                }
// ...

But syn::Field doesn't seem to implement parse...

I'm not quite sure how to proceed here, any ideas?

Something like this:

use quote::quote;
use syn::parse::Parser;
use syn::{Field, Fields, Variant};

fn f(variant: &mut Variant) {
    match &mut variant.fields {
        Fields::Named(fields) => fields
            .named
            .push(Field::parse_named.parse2(quote! { pub a: u32 }).unwrap()),
        _ => {}
    }
}
1 Like

How exactly does this work? From the docs of Field it seems to me that parse_named is a function... could you explain this black magic?

Any function from ParseStream to ::syn::Result<T> implements ::syn::parse::Parser, which you can "convert" into taking a ::proc_macro::TokenStream or a ::proc_macro2::TokenStream (or even a &'_ str) by using the .parse and .parse2 (and .parse_str) methods respectively.

In a non-inlined fashion, the code would be:

let named_field_parser = Field::parse_named; // impl ::syn::parse::Parser

let extra_field =
    named_field_parser
        .parse2(quote! { ... }) // ::syn::Result<Field>
        .unwrap()
;
1 Like

that... is not at all obvious from just looking at the source for Field::parse_named... is there any documentation for the mechanism that this uses?

1 Like

Yep, it can be hard to "stumble" upon the relevant piece of documentation :sweat_smile:. I linked to the trait itself imagining that it would include the documentation, but it is actually located in the containing module:

The Parse trait is not implemented in cases [where types can be parsed in several ways depending on context] because there is no good behavior to consider the default.

[...]

In these cases the types provide a choice of parser functions rather than a single Parse implementation, and those parser functions can be invoked through the Parser trait.

  • which is the case for the Field type, for instance.
1 Like

hmm... what I still don't understand is how that inlining you talked about happens... what syntax is used which allows a function to behave like an instantiated struct?

EDIT

didn't read the docs for parser, now I see what's happening. it's just implementing itself on a specialisation of FnOnce.

thank you for your time.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.