Cannot compile macro (implemented by macro_rules!)

I want to implement macro that will generate struct for me, but got an error on compile:

error: `$prop_type:expr` may be followed by `#`, which is not allowed for `expr` fragments
 --> src/main.rs:4:11
  |
4 |         $(#[$outer:meta])*
  |           ^ not allowed after `expr` fragments
  |
  = note: allowed there are: `=>`, `,` or `;`

error: `$prop_type:expr` may be followed by `$vis:vis`, which is not allowed for `expr` fragments
 --> src/main.rs:5:9
  |
5 |         $vis:vis struct $PacketStruct:ident {
  |         ^^^^^^^^ not allowed after `expr` fragments
  |
  = note: allowed there are: `=>`, `,` or `;`

error: could not compile `playground` due to 2 previous errors

This is my code:

macro_rules! packet {
    (
        $($prop_name:ident: $prop_type:expr);*
        $(#[$outer:meta])*
        $vis:vis struct $PacketStruct:ident {
            $($field_name:ident: $field_type:ty),*
        }
    ) => {
        $($prop_name: $prop_type);*
        $(#[$outer])*
        #[derive(Copy, Clone)]
        $vis struct $PacketStruct {
            $($field: $field_type),*
        }
    };
}

fn main() {
    packet! {
        pub struct Outcome {
            name: String,
            os: u8,
            game_name: String,
        }
    }
}

I expect that code can be also like this:

packet! {
    // before struct there can be any amount of properties separated by semicolon
    opcode Opcode::LOGIN_CHALLENGE;

    size: u16;

    pub struct Outcome {
        unknown: u8,
        size: u16,
        game_name: String,
        version: [u8; 3],
        build: u16,
        platform: String,
        os: String,
        locale: String,
        unknown2: [u8; 3],
        ip: u32,
        account_length: u8,
        account: String,
    }
}

This is sandbox.

Could somebody explain what am I doing wrong ?

You have

$($prop_name:ident: $prop_type:expr);*

but you need

$( $prop_name:ident: $prop_type:expr; )*

The difference is that the first one only includes ";" separators and the second one requires a semicolon after the last item of the repetition too, which matches how you intended to use it.

There's another problem after fixing that one, but I know less about it:

error: local ambiguity when calling macro `packet`: multiple parsing options: built-in NTs ident ('prop_name') or vis ('vis').
  --> src/main.rs:26:9
   |
26 |         pub struct Outcome {
   |         ^^^
1 Like

The problem with that one lies with the fact that pub can be both ident and vis and macro_rules can't pick the proper outcome.

It's not impossible to solve that with recursive macro invocations, but then compilation time suffers.

Better to change the structure of declarations:

packet! {
    properties {
        opcode Opcode::LOGIN_CHALLENGE;
        size: u16;
    }

    pub struct Outcome {
…

This would both make it easy for the parser to accept these properties and would also visualize them for the reader.

1 Like

when I do smth like this:

macro_rules! packet {
    (
        properties {
            $(opcode $opcode_value:expr;)?
            $($prop_name:ident: $prop_type:ty;)*
        }
        
        $(#[$outer:meta])*
        $vis:vis struct $PacketStruct:ident {
            $($field_name:ident: $field_type:ty),*
        }
    ) => {
        $(#[$outer])*
        #[derive(Clone)]
        $vis struct $PacketStruct {
            $($field_name: $field_type),*
        }
    };
}


fn main() {
    packet! {
        properties {
            opcode 10;
        }
        
        #[derive(Hash)]
        pub struct Outcome {
            name: String,
            os: u8,
            game_name: String
        }
    }
}

I still got an error:

error: local ambiguity when calling macro `packet`: multiple parsing options: built-in NTs ident ('prop_name') or 1 other option.
  --> src/main.rs:25:13
   |
25 |             opcode 10;
   |             ^^^^^^

error: could not compile `playground` due to previous error

Sandbox.

Could you check on that ?

It's the exact same ambiguity, just on the next level.

The problem here is that both your $(opcode $opcode_value:expr;)? and $($prop_name:ident: $prop_type:ty;)* may accept the line.

In that case recursion may be appropriate (since it would only be one-level): write two rules, one with opcode, one without opcode, and both would call another macro (or maybe even the same one).

Similarly to how vec! handled comma before ? was added to the macro system.

1 Like

the issue here is that I need to parse property marked with opcode in another way. Could you advice probably I can use some check for this case ?

As I have said: in that case recursion can be appropriate. Something like this.

Here you clearly separate opcode from the other parts.

1 Like

Thank you for an advice ! After I applied recursion, I got another issue and created new topic for this case: Macro_rules compilation error: cannot find value `X` in this scope