Search code examples
rustrust-macros

Why isn't a macro arm matching a macro seen?


This is MVP I could create but it's based off of a piece of real code:

fn testmatch() -> i32 {
    let dependant = 4;
    
    macro_rules! testmacro {
        () => { return 0 };
    
        ({ $cond:expr => $res:literal } $(, $rest:tt)*) => {
            if $cond {
                return $res;
            } else {
                return testmacro!($($rest)*);
            }
        };
        
        (unfurl!($furled:ident) $(, $rest:tt)*) => {
            furls!($furled);
            testmacro!($($rest)*);
        };
    }
    
    macro_rules! furls {
        (test_furl) => { testmacro! {
            { dependant == 4 => 4 },
            { dependant == 5 => 5 }
        } };
    }

    match 1 {
        1 => testmacro! {
            { dependant == 2 => 2 },
            { dependant == 3 => 3 },
            unfurl!(test_furl)
        },
        _ => 4000000
    }
}

fn main() {
    let x = testmatch();
    println!("{}", x);
}

I put it in a match statement as this is how my actual code is structured but I don't believe it makes a difference. I understand that in this example it would be easier to simply use a selection of inline if statements but if repetitive logic needs to be done to each condition and result, I believe this to be the most efficient method of doing so.

The error is as follows:

error: no rules expected the token `!`
  --> src/main.rs:32:19
   |
4  |     macro_rules! testmacro {
   |     ---------------------- when calling this macro
...
32 |             unfurl!(test_furl)
   |                   ^ no rules expected this token in macro call
   |
   = note: while trying to match sequence end

warning: unused macro definition: `furls`
  --> src/main.rs:21:18
   |
21 |     macro_rules! furls {
   |                  ^^^^^
   |
   = note: `#[warn(unused_macros)]` on by default

Not only is unfurl! not matched by the macro, but it also seems to not be using the furls! macro later defined. Why is unfurl not even seen? And how could you expand the furl in this syntax?


Solution

  • As @kmdreko explained, tt matches a token tree - a pair of {}, () or [] and whatever inside, or any other single token. The conditions are matched because they're enclosed in {}; but the result is not, and it's also not a single token, so it errors.

    The simplest way to fix that is to wrap it in parentheses too. You also need to fix two other mistakes: putting the commas back when expanding the other conditions and enclosing the last arm in a block since it has two statements:

    fn testmatch() -> i32 {
        let dependant = 4;
        
        macro_rules! testmacro {
            () => { return 0 };
        
            ({ $cond:expr => $res:literal } $(, $rest:tt)*) => {
                if $cond {
                    return $res;
                } else {
                    return testmacro!($($rest),*);
                }
            };
            
            ((unfurl!($furled:ident)) $(, $rest:tt)*) => {{
                furls!($furled);
                testmacro!($($rest)*);
            }};
        }
        
        macro_rules! furls {
            (test_furl) => { testmacro! {
                { dependant == 4 => 4 },
                { dependant == 5 => 5 }
            } };
        }
    
        match 1 {
            1 => testmacro! {
                { dependant == 2 => 2 },
                { dependant == 3 => 3 },
                (unfurl!(test_furl))
            },
            _ => 4000000
        }
    }
    

    The second most easiest approach is to put the comma after the previous condition, instead of before the next condition. This requires a trailing comma in the case of only conditions (no result). Also, another thing we need to do is to omit the comma after the result, even if we have conditions after it:

    fn testmatch() -> i32 {
        let dependant = 4;
        
        macro_rules! testmacro {
            () => { return 0 };
        
            ({ $cond:expr => $res:literal }, $($rest:tt)*) => {
                if $cond {
                    return $res;
                } else {
                    return testmacro!($($rest)*);
                }
            };
            
            (unfurl!($furled:ident) $($rest:tt)*) => {{
                furls!($furled);
                testmacro!($($rest)*);
            }};
        }
        
        macro_rules! furls {
            (test_furl) => { testmacro! {
                { dependant == 4 => 4 },
                { dependant == 5 => 5 },
            } };
        }
    
        match 1 {
            1 => testmacro! {
                { dependant == 2 => 2 },
                { dependant == 3 => 3 },
                unfurl!(test_furl)
            },
            _ => 4000000
        }
    }
    

    The hardest thing to do is to keep the syntax as-is, but I'll leave that as a challenge to you :) Hint: this requires TT munching.