How to use async code inside proc-macro function?

Hi folks,

in my proc-macro I want to use some async functions, like below:

let initial_initializers = fields
        .iter()
        .map(|f| async {
            let field_name = f.ident.clone();

            if dynamic_fields.contains(&field_name) {
                quote!{ Default::default() }
            } else {
                quote! { #binary_converter::read_from_stream(...).await? }
            }
        });

but I got this error on compile:

error[E0277]: the trait bound `{async block@src/primary/macros/packet/src/lib.rs:358:18: 373:10}: ToTokens` is not satisfied
   --> src/primary/macros/packet/src/lib.rs:375:22
    |
375 |       let mut output = quote! {
    |  ______________________^
376 | |         impl #ident {
377 | |             pub fn from_binary(buffer: &[u8]) -> #result<(Self, String)> {
378 | |                 let mut initial_reader = #cursor::new(buffer.to_vec());
...   |
409 | |         }
410 | |     };
    | |     ^
    | |     |
    | |_____the trait `ToTokens` is not implemented for `{async block@src/primary/macros/packet/src/lib.rs:358:18: 373:10}`
    |       required by a bound introduced by this call
    |
    = help: the following other types implement trait `ToTokens`:
              bool
              char
              isize
              i8
              i16
              i32
              i64
              i128
            and 299 others
    = note: required for `RepInterp<{async block@src/primary/macros/packet/src/lib.rs:358:18: 373:10}>` to implement `ToTokens`
    = note: this error originates in the macro `$crate::quote_token_with_context` which comes from the expansion of the macro `quote` (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 1 previous error

This is full code of the macro:

#[proc_macro_derive(FieldsSerializer, attributes(dynamic_field))]
pub fn derive_fields_serializer(input: TokenStream) -> TokenStream {
    let ItemStruct { ident, fields, attrs, .. } = parse_macro_input!(input);
    let Imports {
        binary_converter,
        cursor,
        json_formatter,
        result,
        serialize,
        ..
    } = Imports::get();

    let field_names = fields.iter().map(|f| {
        f.ident.clone()
    }).collect::<Vec<Option<Ident>>>();

    let mut dynamic_fields: Vec<Option<Ident>> = vec![];
    for field in fields.iter() {
        let ident = format_ident!("{}", field.ident.as_ref().unwrap());

        if field.attrs.iter().any(|attr| attr.path().is_ident("dynamic_field")) {
            dynamic_fields.push(Some(ident));
        }
    }

    let initial_initializers = fields
        .iter()
        .map(|f| {
            let field_name = f.ident.clone();

            if dynamic_fields.contains(&field_name) {
                quote!{ Default::default() }
            } else {
                quote! { #binary_converter::read_from(&mut initial_reader)? }
            }
        });

    let initializers = fields
        .iter()
        .map(|f| {
            let field_name = f.ident.clone();
            let field_type = f.ty.clone();

            if dynamic_fields.contains(&field_name) {
                quote!{ Self::#field_name(&mut reader, &mut initial) }
            } else {
                quote! {
                    {
                        let value: #field_type = #binary_converter::read_from(&mut reader)?;
                        initial.#field_name = value.clone();
                        value
                    }
                }
            }
        });

    let mut output = quote! {
        impl #ident {
            pub fn from_binary(buffer: &[u8]) -> #result<(Self, String)> {
                let mut initial_reader = #cursor::new(buffer.to_vec());
                let mut initial = Self {
                    #(#field_names: #initial_initializers),*
                };

                let mut reader = #cursor::new(buffer);
                let mut instance = Self {
                    #(#field_names: #initializers),*
                };
                let details = instance.get_json_details()?;

                Ok((instance, details))
            }

            pub fn get_json_details(&mut self) -> #result<String> {
                let mut serializer = #json_formatter::init();
                #serialize::serialize(self, &mut serializer)?;
                String::from_utf8(serializer.into_inner()).map_err(|e| e.into())
            }

            pub fn to_binary(&mut self) -> #result<Vec<u8>> {
                let mut body = Vec::new();
                #(
                    #binary_converter::write_into(
                        &mut self.#field_names,
                        &mut body
                    )?;
                )*

                Ok(body)
            }
        }
    };

    TokenStream::from(output)
}

Could somebody explain, how can I fix this ?

let initial_initializers = fields
        .iter()
-       .map(|f| async {
+       .map(|f| {
            let field_name = f.ident.clone();

            if dynamic_fields.contains(&field_name) {
                quote!{ Default::default() }
            } else {
                quote! { #binary_converter::read_from_stream(...).await? }
            }
        });

should help.

Note that what is inside quote is just a token stream, it is not executed during your proc macro, so you don't need to mark your closure as async.

1 Like