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

However, the allowed context does not comprise macro-invocation.

Outer attributes before an expression are allowed only in a few specific cases:

I'm not sure whether the macro-invocation considers a token sequence to be an expression if the sequence is a valid expression in the macro-invocation context or if it just considers whether it can be an expression as long as it is permitted in some contexts.

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.