Need help to debug proc macro

My proc macro not compile and I cannot find the reason. I got an error:

error: expected one of `!`, `.`, `;`, `?`, `{`, `}`, or an operator, found `::`
  --> src\client\auth\get_realmlist.rs:23:14
   |
23 |     #[derive(Packet, Debug, Default)]
   |              ^^^^^^
   |              |
   |              expected one of 7 possible tokens
   |              in this derive macro expansion

I tried to run cargo +nightly rustc -- -Z macro-backtrace and got extra info which not useful:

12 | pub fn derive_packet(input: TokenStream) -> TokenStream {
   | ------------------------------------------------------- in this expansion of `#[derive(Packet)]`

This is the code where I got this error first (but when I comment this code I got same error on other struct):

with_opcode! {
    @login_opcode(Opcode::REALM_LIST)
    #[derive(Packet, Debug, Default)]
    struct Income {
        skip: [u8; 6],
        realms: Vec<Realm>,
    }
}

This is my proc macro code:

use proc_macro::{TokenStream};
use proc_macro2::{Ident};
use syn::ItemStruct;
use syn::{parse_macro_input};
use quote::{quote, format_ident};

mod types;

use types::{Attributes, Imports};

#[proc_macro_derive(Packet, attributes(options, dynamic_field, skip))]
pub fn derive_packet(input: TokenStream) -> TokenStream {
    let ItemStruct { ident, fields, attrs, .. } = parse_macro_input!(input);
    let Imports {
        binary_converter,
        byteorder,
        cursor,
        deflate_decoder,
        outcoming_opcode_length,
        read,
        types,
    } = Imports::get();

    let mut is_compressed = quote!(false);
    if attrs.iter().any(|attr| attr.path.is_ident("options")) {
        let attributes = attrs.iter().next().unwrap();
        let attr: Attributes = attributes.parse_args().unwrap();
        let value = attr.compressed.value;
        is_compressed = quote!(#value);
    }

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

    let mut dynamic_fields: Vec<Option<Ident>> = vec![];
    let mut skipped_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));
        } else if field.attrs.iter().any(|attr| attr.path.is_ident("skip")) {
            skipped_fields.push(Some(ident));
        }
    }

    let initializers = field_names
        .iter()
        .map(|field_name| {
            if dynamic_fields.contains(field_name) {
                quote!{ Self::#field_name(&mut reader, &mut initial) }
            } else if skipped_fields.contains(field_name) {
                quote!{ Default::default() }
            } else {
                quote!{ #binary_converter::read_from(&mut reader).unwrap() }
            }
        });

    let output = quote! {
        impl #ident {
            pub fn from_binary(buffer: &Vec<u8>) -> Self {
                let mut omit_bytes = Self::opcode().to_le_bytes().len();
                if omit_bytes == 4 {
                    // 2 bytes packet size + 4 bytes opcode size
                    omit_bytes += 2;
                }

                let mut buffer = match #is_compressed {
                    true => {
                        let mut internal_buffer: Vec<u8> = Vec::new();
                        // 4 bytes uncompressed + 2 bytes used by zlib
                        omit_bytes += 6;

                        let data = &buffer[omit_bytes..];
                        let mut decoder = #deflate_decoder::new(data);
                        #read::read_to_end(&mut decoder, &mut internal_buffer).unwrap();

                        internal_buffer.to_vec()
                    },
                    false => buffer.to_vec(),
                };

                let mut initial_reader = #cursor::new(&mut buffer);
                let mut initial = Self {
                    #(#field_names: #binary_converter::read_from(&mut initial_reader).unwrap()),*
                };

                let mut reader = #cursor::new(&mut buffer);
                Self {
                    #(#field_names: #initializers),*
                }
            }

            pub fn to_binary(&mut self) -> Vec<u8> {
                let body = self._build_body();

                let opcode = Self::opcode();
                if opcode.to_le_bytes().len() == 4 {
                    let header = Self::_build_world_packet_header(body.len(), opcode);
                    [header, body].concat()
                } else {
                    let header = Self::_build_login_packet_header(opcode);
                    [header, body].concat()
                }
            }

            // use this method in case you didn't use with_opcode! macro
            pub fn to_binary_with_login_opcode(&mut self, opcode: u8) -> Vec<u8> {
                let body = self._build_body();
                let header = Self::_build_login_packet_header(opcode);
                [header, body].concat()
            }

            // use this method in case you didn't use with_opcode! macro
            pub fn to_binary_with_world_opcode(&mut self, opcode: u32) -> Vec<u8> {
                let body = self._build_body();
                let header = Self::_build_world_packet_header(body.len(), opcode);
                [header, body].concat()
            }

            pub fn unpack(&mut self) -> #types::PacketOutcome {
                let opcode = Self::opcode() as u32;
                (opcode, self.to_binary())
            }

            pub fn unpack_login_packet(&mut self, opcode: u8) -> #types::PacketOutcome {
                (opcode as u32, self.to_binary_with_login_opcode(opcode))
            }

            pub fn unpack_world_packet(&mut self, opcode: u32) -> #types::PacketOutcome {
                (opcode, self.to_binary_with_world_opcode(opcode))
            }

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

                body
            }

            fn _build_login_packet_header(opcode: u8) -> Vec<u8> {
                let mut header: Vec<u8> = Vec::new();
                #byteorder::WriteBytesExt::write_u8(
                    &mut header,
                    opcode,
                ).unwrap();

                header
           }

            fn _build_world_packet_header(body_size: usize, opcode: u32) -> Vec<u8> {
                let mut header: Vec<u8> = Vec::new();
                let size = body_size + #outcoming_opcode_length;

                #byteorder::WriteBytesExt::write_u16::<#byteorder::BigEndian>(
                    &mut header,
                    size as u16,
                ).unwrap();

                #byteorder::WriteBytesExt::write_u32::<#byteorder::LittleEndian>(
                    &mut header,
                    opcode,
                ).unwrap();

                header
           }
        }
    };

    TokenStream::from(output)
}

This is my structs with data I use inside proc macro:

use proc_macro2::{TokenStream as TokenStream2};
use quote::{quote};
use structmeta::StructMeta;
use syn::LitBool;

pub struct Imports {
    pub binary_converter: TokenStream2,
    pub byteorder: TokenStream2,
    pub cursor: TokenStream2,
    pub deflate_decoder: TokenStream2,
    pub outcoming_opcode_length: TokenStream2,
    pub read: TokenStream2,
    pub types: TokenStream2,
}

impl Imports {
    pub fn get() -> Self {
        Self {
            binary_converter: quote!(crate::traits::binary_converter::BinaryConverter),
            byteorder: quote!(byteorder::{WriteBytesExt, BigEndian, LittleEndian}),
            cursor: quote!(std::io::Cursor),
            deflate_decoder: quote!(flate2::read::DeflateDecoder),
            // TODO: need to reorganize constants
            outcoming_opcode_length: quote!(crate::crypto::encryptor::OUTCOMING_OPCODE_LENGTH),
            read: quote!(std::io::Read),
            types: quote!(crate::types),
        }
    }
}

#[derive(StructMeta, Debug)]
pub struct Attributes {
    pub compressed: LitBool,
}

This is code of with_opcode! declarative macro:

#[macro_export]
macro_rules! with_opcode {
    (
        $(@login_opcode($login_opcode:expr))?
        $(@world_opcode($world_opcode:expr))?

        $(#[$outer:meta])*
        $vis:vis struct $PacketStruct:ident {
            $(
                $(#[$field_attr: meta])? $field_vis:vis $field_name:ident: $field_type:ty
            ),*$(,)?
        }

        $($PacketStructImpl: item)*
    ) => {
        $(#[$outer])*
        $vis struct $PacketStruct {
            $($(#[$field_attr])? $field_vis $field_name: $field_type),*
        }

         $($PacketStructImpl)*

        impl $PacketStruct {
            $(
                fn opcode() -> u8 {
                    $login_opcode as u8
                }
            )?

            $(
                fn opcode() -> u32 {
                    $world_opcode as u32
                }
            )?
        }
    };
}

Before run this command cargo +nightly rustc -- -Z macro-backtrace I also run rustup default nightly, but this change nothing. Also I tried cargo +nightly rustc -- -Zproc-macro-backtrace (found here), but this returns no additional output.

The cargo expand command returns same error.

Could somebody help me to debug the proc macro to got what is broken ?

When you have a proc macro error that prevents expansion but isn't a panic, I've found that the simplest debugging technique is to comment out parts of the expansion and check again until I can locate what part of the macro is causing the problem.

1 Like

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.