Need help to fix the declarative macro (local ambiguity issue)

I want to implement declarative macro that will allow me to insert variables into proc macro attribute.
So, I have a struct like this:

#[derive(Packet, Debug)]
#[option(opcode=Opcode::SMSG_MESSAGECHAT)]
struct Test {
	#[dynamic_field]
	field1: u8,
	#[dynamic_field]
	field2: u16,
	#[dynamic_field]
	field3: u8,
	#[dynamic_field]
	field4: u8,
	field5: String,
}

impl Test {
	// ...
}

and I want to insert expression inside #[option(opcode=...)], but since there restrictions on proc macro side, I want to use declarative macros like next to bypass proc macro restrictions:

#[macro_export]
macro_rules! with_opcode {
    (
        $(opcode:$opcode:expr;)?
        $(#[$outer:meta])*
        $($PacketStruct: item)*
    ) => {
        $(#[$outer])*
        $(#[option(opcode=$opcode)])*
        $($PacketStruct)*
    };
}

this macros expected to be used like next:

with_opcode! {
    opcode: Opcode::SMSG_MESSAGECHAT;
    #[derive(Packet, Debug)]
    struct Test {
        #[dynamic_field]
        field1: u8,
        #[dynamic_field]
        field2: u16,
        #[dynamic_field]
        field3: u8,
        #[dynamic_field]
        field4: u8,
        field5: String,
    }

    impl Test {
        // ...
    }
}

but when I try this macros I got an error on compile:

error: local ambiguity when calling macro `with_opcode`: multiple parsing options: built-in NTs item ('PacketStruct') or 1 other option.
 --> src\main.rs:9:5
  |
9 |     opcode: Opcode::SMSG_MESSAGECHAT;
  |     ^^^^^^

I also tried to use macros in different way:

#[macro_export]
macro_rules! with_opcode {
    (
        opcode: $opcode:expr;
        $(#[$outer:meta])*
        $($tt:tt)*
    ) => {
        $(#[$outer])*
        #[option(opcode=$opcode)]
        with_opcode!($($tt)*);
    };
    (
        $vis:vis struct $PacketStruct:ident {
            $($field_vis:vis $field_name:ident: $field_type:ty),*$(,)?
        }

        $($PacketStructImpl: item)*
    ) => {
        $vis struct $PacketStruct {
            $($field_vis $field_name: $field_type),*
        }

        $($PacketStructImpl)*
    };
}

but have same issue.

Could somebody explain how to fix this issue ? And if this have sense or declarative macros will not help me to bypass proc macro restrictions ?

The problem is that the compiler has no way to know whether opcode: Opcode::SMSG_MESSAGECHAT should be parsed into $opcode or into $PacketStruct (with an omitted opcode).

For this macro, does it really make sense for opcode to be optional? I think that removing the ? repetition will solve the problem.

yep, it should be optional since I allow structs without opcode. And when I remove ? unfortunately it does not help.

Using with_opcode doesn't really seem any more concise than just using the normal attribute syntax #[option(opcode=Opcode::SMSG_MESSAGECHAT)] directly

Is there something else you're trying to accomplish with this macro?

no, this macros only for parsing value I pass into #[option(opcode=...)]. Unfortunately I cannot use attribute syntax directly (current topic is related to another my topic Cannot pass variable from outer scope into proc macro attribute).

You're still going to have the same problem with a macro_rules macro, because it will still place the expression in the derive macro input which still has that restriction.

You can either have users provide a constant, or a function that returns a value, but there's no way to pass an actual run time value into a macro invocation.

2 Likes

Well, finally I decided to generate impl, for now not sure what will be better readable: just to use impl on each struct or use macros like below:

#[macro_export]
macro_rules! with_opcode {
    (
        $(@[$opcode:expr])?
        $(#[$outer:meta])*
        $vis:vis struct $PacketStruct:ident {
            $($field_vis:vis $field_name:ident: $field_type:ty),*$(,)?
        }

        $($PacketStructImpl: item)*
    ) => {
        $(#[$outer])*
        $vis struct $PacketStruct {
            $($field_vis $field_name: $field_type),*
        }
        
        impl $PacketStruct {
            $(
                fn opcode() {
                    println!("OPCODE: {:?}", $opcode);
                }
            )?
        }
    };
}

const TEST: u32 = 100;

with_opcode! {
    @[TEST]
    #[derive(Debug)]
    struct Test {
        size: u8,
        size1: u16,
        size2: u8,
        field2: u8,
        field3: String,
    }
}

the only issue with this macro: I am not sure how to detect #[attr] on fields (asked question about that here Cannot recognize #[attrs] in declarative macro rule)

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.