I don't think this "local ambiguity when calling macro" is ambiguous

Hi,

I'm working on a macro and I'm getting the error local ambiguity when calling macro, but I think this is not ambiguous. Let me explain:

macro_rules! my_macro {
    ($($field:ident : $value:expr),*, $msg:expr $(, $part:expr)* $(,)?) => {{
        println!("Options:");
        $(
            println!("  - {}: {}", stringify!($field), $value);
        )*
        println!("Message:");
        println!("  {}", format!($msg, $($part),*));
        println!();
    }};
}

fn main() {
    // This is ok
    my_macro!(foo: "bar", "Hello!");
    my_macro!(foo: "bar", "Hello {}!", "world");
    my_macro!(foo: "bar", "Hello {} {}!", "fellow", "rusticians");

    // This is not
    my_macro!(foo: "bar", bu: "baz", "Hello!");
    my_macro!(foo: "bar", bu: "baz", "Hello {}!", "world");
    my_macro!(foo: "bar", bu: "baz", "Hello {} {}!", "fellow", "rusticians");
}

The error (only for the first failing line):

error: local ambiguity when calling macro `my_macro`: multiple parsing options: built-in NTs expr ('msg') or ident ('field').
  --> src/main.rs:20:27
   |
20 |     my_macro!(foo: "bar", bu: "baz", "Hello!");
   |                           ^^

So I can see that bu can be seen as an expression and that it might be the msg but then there is the : so it should be obvious, that that it can't be the msg because there is no : after msg.

Is this a limitation of the compiler or does it in fact see bu: "baz" as an expression? (If so, why is only bu highlighted?)

Is there a way around it without using => instead of :? I'm trying to improve a macro that already has one such option named (cwd: ".") and I want to add other (optional) options in the same style.

1 Like

You could say so; it's rather a deliberate design decision regarding the parsing "implementation / algorithm" of the macros: they should not have an arbitrarily long backtracking behavior: as you mentioned, once the : was encountered, it could have decided that what it had started seeing matched the $:ident : … repeated pattern rather than the $:expr one[1]. But doing so would have meant "accumulating" tokens since bu until finding something to disambiguate, etc., rather than being able to tell from bu, and bu alone, and that's where the implementation of macros draws the line.

Macros do have kind of a way to "backtrack", though, through the multiple rules / "match arms" of a macro:

macro_rules! single {
    ( $field:ident : $value:expr … ) => (…);
    ( $msg:expr … ) => (…);
}

So all you have left to do is rewrite a bit the macro so as to bring yourself to one such situation. This is called munching.

In this instance, we get:

macro_rules! my_macro {( $($input:tt)* ) => (
    my_helper! {
        [options: ]
        $($input)*
    }
)}

macro_rules! my_helper {
    (
        [options: $($options:tt)*]
        $field:ident : $value:expr,
        $($rest:tt)*
    ) => (my_helper! {
        [options: $($options)* $field $value]
        $($rest)*
    });
    // If we reach this rule, we *know* the start of the input is not
    // a `$:ident : $:expr`
    (
        [options: $( $field:tt $value:tt )*]
        $msg:expr $(,
        $part:expr )* $(,)?
    ) => (
        /* We've finished munching the options: time to output: */
        println!("Options:");
        $(
            println!("  - {}: {}", stringify!($field), $value);
        )*
        println!("Message:");
        println!("  {}", format!($msg, $($part),*));
        println!();
    );
}

  1. aside: in the future bu : … could be a legal expression, but with bu => … what I've mentioned still stands ↩︎

6 Likes

This is beautifully explained, thank you so much @Yandros! :smiling_face_with_three_hearts: