Proc macro not compiled when try to return vector of struct instances within quote!

I am learning proc macroses and trying to implement proc macro that will allow to display data about each struct's field: field name, field type and attributes. For this purpose I implemented next struct:

#[derive(Debug)]
struct Item {
    name: String,
    item_type: String,
    attrs: Vec<String>,
}

and I want to display info as vector of Item instances. But for now I got an error:

error: proc-macro derive panicked
 --> src\main.rs:3:10
  |
3 | #[derive(Debugger)]
  |          ^^^^^^^^
  |
  = help: message: not yet implemented

error[E0599]: no function or associated item named `debug` found for struct `Test` in the current scope
  --> src\main.rs:22:11
   |
4  | struct Test {
   | ----------- function or associated item `debug` not found for this struct
...
22 |     Test::debug();
   |           ^^^^^ function or associated item not found in `Test`

I assume I require to impl ToTokens trait on Item, but it's not clear what exactly I need to do.

This is my macro code:

use proc_macro::{TokenStream};
use std::io::{Error, ErrorKind};
use std::io::ErrorKind::Other;
use proc_macro2::Ident;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, DeriveInput, ItemStruct, Token, punctuated::Punctuated, NestedMeta};

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

    let field_data = fields.iter().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<_>>();

        Item {
            name: f.ident.clone().unwrap().to_string(),
            item_type: quote!(#field_type).to_string(),
            attrs: args,
        }
    }).collect::<Vec<_>>();

    let output = quote! {
        impl #ident {
            pub fn debug() {
                println!("{:#?}",  vec![#(#field_data),*]);
            }
        }
    };

    TokenStream::from(output)
}

and how I use it:

use my_proc_macros_lib::Debugger;

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

impl Test {
    fn size() {}
    fn size1() {}
    fn size2() {}
}

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

I am not sure if it's possible to implement sandbox for proc macro on play-rust, so I created test repo for reproduce.

Could somebody explain what have I missed and how to fix this ?

P.S. forgot to say, I need to store fields data in structs because I want to use this data later.

impl quote::ToTokens for Item {
    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
        todo!()
    }
}

Is this intentional, or just forgotten?

1 Like

it's intentional, this subject a bit unclear for me and I didn't find enough examples

Well, if you intentionally panic in your derive macro, then why are you surprised it panics? It did exactly what you told it to do.

Anyway, if you are trying to parse attributes, use darling. If you find yourself writing repetitive ToTokens impls by hand, you might find #[derive(parsel::ToTokens) useful.

1 Like

could you tell if it possible to display on which line macro panicked ?

Something like

    let formatted = format!("{:#?}",  field_data);
    let output = quote! {
        impl #ident {
            pub fn debug() {
                println!("{}", #formatted);
            }
        }
    };

seems to work fine, then you can skip the ToTokens implementation.

(It does the debug-formatting [into a string literal] at compile-time, which makes sense, because the Vec<Item> value being printed exists at compile-time only, too. To do it at run-time one would need to re-create the same value somehow (make Item public and generate code that calls the right constructors), which seems unnecessarily complex. I initially searched for interpolation in string literals but the docs kindly suggested to just use format! outside of the quote!.)

2 Likes

I'm not sure; search for "Rust macro backtrace panic" or something. Anyway, it is known that in your code, at least the unconditionally-hit todo!() will panic, because that is what todo!() does.

1 Like