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 ?