Proc macro: cannot use item as ident inside quote! when loop through vector of idents

In my proc macro (which I apply to struct) I want to get fields with specific attribute (#[dynamic_field]) and next I want to add struct method that will call methods from the struct, where each method_name equals to filtered field name. The code below shows what I want to achieve:

let output = quote! {
        impl #ident {
            pub fn debug() {
                for item in vec![#(#field_data),*] {
                    Self::#item();
                }
            }
        }
    };

but this not works:

error[E0425]: cannot find value `item` in this scope
  --> proc_macro\src/lib.rs:55:28
   |
55 |                     Self::#item();
   |                            ^^^^ not found in this scope

This is my proc macro code:

#[proc_macro_derive(Packet, attributes(dynamic_field))]
pub fn derive(input: TokenStream) -> TokenStream {
    let ItemStruct { ident, fields, .. } = parse_macro_input!(input);

    let field_data = fields.iter().filter_map(|f| {
        let ident = f.ident.clone().unwrap().to_string();
        println!("IDENT: {:?}", ident);
        let field_type = f.ty.clone();
        let args = f.attrs
            .iter()
            .flat_map(|a| {
                match a.parse_meta().unwrap() {
                    syn::Meta::Path(meta) => {
                        Some(meta.get_ident().unwrap().to_string())
                    },
                    syn::Meta::List(meta) => {
                        Some(meta.path.get_ident().unwrap().to_string())
                    },
                    _ => None,
                }
            })
            .collect::<Vec<_>>();

        if !args.is_empty() {
            Some(f.ident.clone().unwrap().to_string())
        } else {
            None
        }
    }).collect::<Vec<_>>();

    let output = quote! {
        impl #ident {
            pub fn debug() {
                for item in vec![#(#field_data),*] {
                    Self::#item();
                }
            }
        }
    };

    TokenStream::from(output)
}

and how I use it:

#[derive(Packet)]
struct Test {
    #[dynamic_field]
    size: u8,
    #[dynamic_field]
    size1: u8,
    #[dynamic_field]
    size2: u8,
    field2: u8,
    field3: u8,
}

impl Test {
    fn size() {
        println!("TEST1");
    }
    fn size1() {
        println!("TEST2");
    }
    fn size2() {
        println!("TEST3");
    }
}

fn main() {
    Test::debug();
}

Expected result: Test::debug() should call Test::size1(), Test::size2() and Test::size().

I tried to use quote! { Self::#item() } inside parent quote!:

let output = quote! {
	impl #ident {
		pub fn debug() {
			for item in vec![#(#field_data),*] {
				quote! { Self::#item(); }
			}
		}
	}
};

but this not helped.

Could somebody explain how to fix this ?

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, AttrStyle, ItemStruct};

#[proc_macro_derive(Packet, attributes(dynamic_field))]
pub fn derive(input: TokenStream) -> TokenStream {
    let ItemStruct { ident, fields, .. } = parse_macro_input!(input);

    let methods = fields.iter().filter_map(|f| {
        let found = f.attrs.iter().any(|attr| {
            matches!(attr.style, AttrStyle::Outer) && attr.path.is_ident("dynamic_field")
        });
        if found {
            // For named structs:
            let ident = f.ident.as_ref().unwrap();
            println!("IDENT: {:?}", ident);
            Some(quote!(Self::#ident();))
        } else {
            None
        }
    });

    quote! {
        impl #ident {
            pub fn debug() {
                #(#methods)*
            }
        }
    }
    .into()
}
// the result of `cargo expand`: 
impl Test {           
    pub fn debug() {  
        Self::size(); 
        Self::size1();
        Self::size2();
    }                 
}                     
2 Likes

The root issue is that you can't mix levels like that -- you're trying to write a for which is compiled in the target crate which loops over identifiers, but this isn't something Rust can do.

What you actually want is

let output = quote! {
    impl #ident {
        pub fn debug() {
            #(Self::#field_data();)*
        }
    }
};

The #()* is your loop over the field_data list. Additionally, you probably want to keep the Ident directly instead of stringifying it.

4 Likes

@vague @CAD97 thank you very much for explanation and examples !

We can just use attr.path.is_ident("dynamic_field") because the inner attribute is not allowed here.

error: an inner attribute is not permitted in this context
  --> src/main.rs:10:5
   |
10 |     #![dynamic_field]
   |     ^^^^^^^^^^^^^^^^^
   |
   = note: inner attributes, like `#![no_std]`, annotate the item enclosing them, and are usually found at the beginning of source files
   = note: outer attributes, like `#[test]`, annotate the item following them

error: expected square brackets
  --> src/main.rs:10:6
   |
10 |     #![dynamic_field]
   |      ^
2 Likes

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.