I have a macros which generates dynamic struct and allow to pass options to this struct. My issue is that my macros try to read wrong methods from generated struct.
So, this is how I use the macro:
packet! {
@option[world_opcode=100]
@option[dynamic_fields: ["nickname"]; deps=["nickname_size"]]
struct Income {
name: String,
age: u64,
nickname_size: u32,
nickname: String,
}
impl Income {
pub fn should_read_nickname(dep_values: Income) -> bool {
dep_values.nickname_size > 0
}
}
}
and inside macro I consider that only Self::should_read_nickname
will be called. But actually macro call method for each field in struct, so I got an errors like below:
error[E0599]: no function or associated item named `should_read_name` found for struct `Income` in the current scope
--> src/main.rs:182:46
|
103 | / $vis struct $PacketStruct {
104 | | $($field_vis $field_name: $field_type),*
105 | | }
| |_________- function or associated item `should_read_name` not found for this struct
...
182 | if Self::[<should_read_ $field_name>](dep_values) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| function or associated item not found in `Income`
| help: there is an associated function with a similar name: `should_read_nickname`
...
267 | / packet! {
268 | | @option[world_opcode=100]
269 | | @option[dynamic_fields: ["nickname"]; deps=["nickname_size"]]
270 | | struct Income {
... |
281 | | }
282 | | }
| |_- in this macro invocation
|
= note: this error originates in the macro `packet` (in Nightly builds, run with -Z macro-backtrace for more info)
This is my macro:
#[macro_export]
macro_rules! packet {
(
$(@option[login_opcode=$login_opcode:expr])?
$(@option[world_opcode=$world_opcode:expr])?
$(@option[compressed_if:$compressed_if:expr])?
$(@option[
dynamic_fields:$($dynamic_fields:expr),*
$(; deps=$($dependencies:expr),*)?
])?
$(#[$outer:meta])*
$vis:vis struct $PacketStruct:ident {
$($field_vis:vis $field_name:ident: $field_type:ty),*$(,)?
}
$($PacketStructImpl: item)*
) => {
$(#[$outer])*
#[derive(Clone, Debug, Default)]
$vis struct $PacketStruct {
$($field_vis $field_name: $field_type),*
}
$($PacketStructImpl)*
impl $PacketStruct {
#[allow(dead_code)]
pub fn from_binary(buffer: &Vec<u8>) -> Self {
#![allow(unused_mut)]
#![allow(unused_variables)]
#![allow(unused_assignments)]
let mut omit_bytes: usize = INCOMING_HEADER_LENGTH;
$(
omit_bytes = ($login_opcode as u8).to_le_bytes().len();
)?
let mut is_compressed = false;
$(
let mut reader = std::io::Cursor::new(buffer[2..].to_vec());
let opcode = byteorder::ReadBytesExt::read_u16::<byteorder::LittleEndian>(
&mut reader
).unwrap();
if $compressed_if == opcode {
// 4 bytes uncompressed + 2 bytes used by zlib
omit_bytes += 6;
is_compressed = true;
}
)?
let mut internal_buffer: Vec<u8> = Vec::new();
if is_compressed {
let data = &buffer[omit_bytes..];
let mut decoder = flate2::read::DeflateDecoder::new(data);
std::io::Read::read_to_end(&mut decoder, &mut internal_buffer).unwrap();
}
let buffer = if internal_buffer.is_empty() {
buffer[omit_bytes..].to_vec()
} else {
internal_buffer
};
let mut reader = std::io::Cursor::new(&buffer);
let mut dynamic_fields: Vec<&str> = vec![];
$(
$(
for index in 0..$dynamic_fields.len() {
dynamic_fields.push($dynamic_fields[index]);
}
)*
)?
let mut deps: Vec<&str> = vec![];
$(
$(
$(
for index in 0..$dependencies.len() {
deps.push($dependencies[index]);
}
)*
)?
)?
struct DepValues {
$($field_name: $field_type),*
}
let dep_values = DepValues {
$(
$field_name: <$field_type>::default()
),*
};
let initial = Self {
$(
$field_name: {
if dynamic_fields.contains(&stringify!($field_name)) {
paste::paste! {
if Self::[<should_read_ $field_name>](dep_values) {
BinaryConverter::read_from(
&mut reader
).unwrap()
} else {
<$field_type>::default()
}
}
} else {
let value = BinaryConverter::read_from(
&mut reader
).unwrap();
if deps.contains(&stringify!($field_name)) {
dep_values.$field_name = value;
}
value
}
}
),*
};
// initial is true until first dynamic field
initial
}
#[allow(dead_code)]
pub fn to_binary(&mut self) -> Vec<u8> {
#![allow(unused_mut)]
let mut body = Vec::new();
$(
BinaryConverter::write_into(
&mut self.$field_name,
&mut body
).unwrap();
)*
let header = Self::_build_header(&body);
[header, body].concat()
}
#[allow(unused_variables)]
fn _build_header(body: &Vec<u8>) -> Vec<u8> {
#![allow(unused_mut)]
let mut header: Vec<u8> = Vec::new();
$(
byteorder::WriteBytesExt::write_u8(
&mut header,
$login_opcode as u8
).unwrap();
)?
$(
let size = body.len() + OUTCOMING_OPCODE_LENGTH;
byteorder::WriteBytesExt::write_u16::<byteorder::BigEndian>(
&mut header,
size as u16,
).unwrap();
byteorder::WriteBytesExt::write_u32::<byteorder::LittleEndian>(
&mut header,
$world_opcode as u32
).unwrap();
)?
header
}
$(
#[allow(dead_code)]
pub fn unpack(&mut self) -> PacketOutcome {
($login_opcode as u32, self.to_binary())
}
)?
$(
#[allow(dead_code)]
pub fn unpack(&mut self) -> PacketOutcome {
($world_opcode as u32, self.to_binary())
}
)?
}
};
}
and this is the place in macro where I try to call Self::should_read_nickname
:
let initial = Self {
$(
$field_name: {
if dynamic_fields.contains(&stringify!($field_name)) {
paste::paste! {
if Self::[<should_read_ $field_name>](dep_values) {
BinaryConverter::read_from(
&mut reader
).unwrap()
} else {
<$field_type>::default()
}
}
} else {
let value = BinaryConverter::read_from(
&mut reader
).unwrap();
if deps.contains(&stringify!($field_name)) {
dep_values.$field_name = value;
}
value
}
}
),*
};
For me it's unclear why macro try to read methods for another fields if I use the check:
if dynamic_fields.contains(&stringify!($field_name)) {
This is sandbox to reproduce the issue.
Could somebody explain why this issue happen and how to fix this ?