Weird things when using proc macro in match pattern

In procedural macro quote!(a == b) produces a = = b.
This can be fixed by using a.eq(b) instead.

The other thing is that compiler disallows usage of if at all:

macro expansion ignores token `if` and any following

Only if used in match pattern.

// macro
#[proc_macro]
pub fn foo(input: TokenStream) -> TokenStream {
    return quote::quote!(_ if a == b).into();
}

// usage
let a = 1;
let b = 2;
match 1 {
  //expands to: _ if a =  = b
  // if fixed, gives error: "macro expansion ignores token `if` and any following"
  foo!() => {
    println!("matched");
  }
}

A macro invocation expands a macro at compile time and replaces the invocation with the result of the macro. Macros may be invoked in the following situations:

(from the reference)

In particular, guards are not part of patterns, and pattern+guard combinations are thus not something a macro can expand to.

MatchArms :
  ( MatchArm => ( ExpressionWithoutBlock , | ExpressionWithBlock , ? ) )*
  MatchArm => Expression , ?

MatchArm :
  OuterAttribute* Pattern MatchArmGuard ?

MatchArmGuard :
  if Expression

(from here)


I would assume that the observation that quote!(a == b) appear to result in two not joined tokens = = is probably only a side-effect of the above-mentioned main issue, and that in context where an expression is actually expected, a quote’d a == b should work fine and as expected.

3 Likes

How can you be so sure about that? Rust Playground

Also I tried the following code, it works as expected.

#[proc_macro]
pub fn foo(_: TokenStream) -> TokenStream {
    quote::quote!(a == b).into()
}
fn main() {
    let a = 1;
    let b = 2;
    foo!();
}

// it works fine, and the expansion is
fn main() {
    let a = 1;
    let b = 2;
    a == b;
}

I edited the question. It only happens when used inside a match pattern. Sorry for the confusion. As for how I can be sure, I used macro expansion.

Ok. Thank you for the clear explanation of what is going on.

Every time I use Rust macros I always stumble upon some unexpected limitations.

I have a enum like this:

pub enum Element {
    Str(Cow<'static, str>),
    Num(i32),
}

I want to match against a slice of such elements and also be able to capture variables. That's hard because of the Cow. But also I would like syntax to be even shorter, like ["foo", 10, Str(name), Id(id)] or ["foo", 10, $name, @id].
The initial plan was to generate code like this:

[Str(str0), Num(10), Str(name), Num(id)] if str0 == "foo"

But it's obviously impossible now.

It’s solvable if the macro expands to the whole match. Or for an even more advanced solution, an attribute macro that handles a whole block or function and re-writes all matches containing foo!().

Maybe a bit too much effort though for a small use-case; I’ve just thought a bit about what would go into a general framework (for the latter approach) for more easily defining macros like that – maybe I’ll try giving implementing a helper crate a go, in case I get back to this on the weekend.

1 Like

Yes, it looks like I can create macro function that will contain the whole match like this:

match_paths!(path_to_match, 
  ["foo", 10, $name, @id] => {
    ...some...code...
  },
  ["bar", @bar_id, ..] => {
    ...some...other...code...
  }
)

If I do not run in some other macro limitations on this way)

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.