Search code examples
rustmacros

Rust macro not expanding recursively


I'm trying to write a macro that writes a function for me. Keep in mind that i have removed a lot of irrelevant details so i it may look a bit pointless. My macro looks like so:

macro_rules! command {
    // Vars
    (@vars, $var: ident: ?$type: ty, $($t:tt)*) => {
        $var: Option<&$type>, command!(@vars, $($t)*)
    };
    (@vars, $var: ident: $type: ty, $($t:tt)*) => {
        $var: &$type, command!(@vars, $($t)*)
    };
    (@vars, ) => {};
    // Main
    ($name: ident ($($t:tt)*) $body: block) => {
        fn $name(command!(@vars, $($t)*)) $body
    };
}

And i invoke it like so:

command!(lol1_command(text: String, t: ?String,) {
    println!("test")
});

This gives me an error. When i expand it using rust analyzer i get this which works

fn lol1_command(text: &String, t: Option<&String>) {
    println!("cool");
}

The compile error i get is error: expected one of ':' or '|', found ')' and it recommends me fn $name(_: command!(@ vars, text : String, t : ? String,)) $body which to me looks like it does not expand command recursively and insted thinks that the macro invokation is a parameter to the funktion. What is going on? I a'm quite confused. Thank in advance.

I have tried moving things around in the macro to try to move the function generation after the call the vars macro but to no avail.


Solution

  • The reference of declarative macros says:

    Macros can expand to expressions, statements, items (including traits, impls, and foreign items), types, or patterns

    Note that this list does not contain partial items such as a list of parameters, you can't produce that from a macro.

    Instead of producing parts of the final function item you can pass those parts along and create a single, complete function item at the very end:

    macro_rules! command {
        // Vars
        (
            $name:ident,
            $body:block,
            @vars ($var:ident : ? $type:ty , $($v:tt)*)
            @processed ($($p:tt)*)
        ) => {
            command!{
                $name,
                $body,
                @vars ($($v)*)
                @processed ($($p)*, $var: Option<&$type>)
            }
        };
        (
            $name:ident,
            $body:block,
            @vars ($var:ident : $type:ty , $($v:tt)*)
            @processed ($($p:tt)*)
        ) => {
            command!{
                $name,
                $body,
                @vars ($($v)*)
                @processed ($($p)*, $var: &$type)
            }
        };
        (
            $name:ident,
            $body:block,
            @vars ()
            @processed (,$($p:tt)*)
        ) => {
            fn $name($($p)*) $body
        };
        // Main
        ($name:ident ($($t:tt)*) $body: block) => {
            command!{$name, $body, @vars ($($t)*) @processed ()}
        };
    }