Why cannot the punctuator `#` eliminate the ambiguity in a declarative macro?

Consider this example:

macro_rules! test_p{
    ($(#head $e2:expr),*, $e3:expr)=>{
        
    }
}
fn main(){
    test_p!(#head 2,#head 4, 5);
}

This code has a compiled error that:

error: local ambiguity when calling macro test_p: multiple parsing options: built-in NTs expr ('e3') or 1 other option.

However, if change the punctuator # to any one of ~, @, :, ?, then it will be compiled. IIUC, no token sequence that has the form #identifier can compose a valid expression, which should have the same reason as @identifier to make the code compile. Why does the # not work here?

Not a macro expert, but attributes is my guess.

fn main() {
    let arr = [
        1,
        2,
        #[cfg(target_os = "windows")] 3,
        4,
        #[cfg(not(target_os = "windows"))] 5,
        6,
    ];
    
    println!("{arr:?}");
}

https://doc.rust-lang.org/nightly/reference/expressions.html#expression-attributes

1 Like

To add to @quinedot's comment: I don't believe the macro_rules processor looks ahead more than one token when trying to resolve these sorts of branches. So, because # could start an expression, it assumes it's ambiguous. Putting extra tokens after it that "disqualifies" that part of the input from being an expression doesn't help, because the processor has to have already made its decision before it even sees them.

3 Likes

How about ...identifier? Is this considered to be an expression? This punctuator still cannot be used to eliminate the ambiguity.

I assume it's parsing that as (..).identifier, which is an expression. Semantically nonsense, but syntactically correct.

But it doesn't:

use std::ops::RangeBounds;
fn main() {
    // fails with "unexpected token"
    // assert!(...contains(&42u8));
    // compiles and passes
    assert!((..).contains(&42u8));
}

Hmm... well, without spending the rest of the night diving through the rustc source code, I have two remaining guesses:

A. there's a whitelist for what tokens can't start an expression and it doesn't include ..., or:

B. ... isn't currently valid as the start of an expression, but it's reserved in case Rust grows unpack/splat syntax in the future.

I'd go look, but it's been ages since I dug through the macro_rules code and I don't have the energy to go spelunking at 21:30 at night. :stuck_out_tongue:

1 Like