Permissible proc macro input

Hello all,
I've been working on a project making heavy use of procedural macros, and I wanted to extend the syntax within an annotated function so this would be valid:

#[my_attr]
fn test() -> i32 {
    let $a = 42;
    $a
}

Essentially, I want to allow identifiers to be prefixed with $, so I can do some special processing on them.

However, rustc doesn't seem to accept this syntax, even when my proc_macro returns an empty token stream, giving this error:

error: expected pattern, found `$`
  --> avalanche-macro-test\src\tests.rs:10:9
   |
10 |     let $a = 42;
   |         ^ expected pattern

Why exactly is this occurring? It seems to me that the compiler is parsing and rejecting the original code, even though my proc_macro returns a completely different token stream. This same problem occurred when returning the tokens of a valid dummy item instead. Is there a way to avoid this pitfall or turn off whatever check rustc is performing? If I cannot implement this syntax extension, what are the general limits on proc macro input syntax?

Thanks in advance for your help.

Attribute proc macros require the item they are attached to to successfully parse. When parsing the item is parsed as normally. Only during macro expansion does the parsed item get turned into a tokenstream and passed to your attribute proc macro. If you want to support a custom syntax, you will need to use a proc macro of the form my_macro! { /* ... */ }.

5 Likes

More generally, a nice trick to see these differences, is to check a function definition annotated with #[cfg(FALSE)].

Indeed, such attribute could be seen as a proc-macro that always emits an empty TokenStream (which is a valid output). You can then notice that, indeed, there are some requirements on the input given to any attribute / proc-macro, which may seem like semantic issues but which rust has currently implemented as syntactical issues:

#[cfg(FALSE)]
fn foo () {
    let $foo = 42; // Error, pattern expected, got `$` instead.
    yield 42; // Error, `yield` syntax is experimental
}

The only solution is indeed to use a function-like macro macro_name! { … }, since the input to such a macro only needs to "lex" (actually, parse, but a very basic level: the TokenStream level, whereby parens, braces and brackets need to match).

  • my_proc_macro! {
        fn foo ()
        {
            let $x = 42;
        }
    }
    
  • If you don't like the churn around the function signature, you can alleviate it a bit by wrapping the function's body instead:

    #[my_proc_macro]
    fn foo ()
    {
        fake_macro! {
            let $x = 42;
        }
    }
    

And for the cases where lexing is too restrictive (e.g. imagine wanting to be whitespace sensitive, or to accept the symbol or the symbol ` among the macro input), you'd then even need to use a stringified input API:

some_macro!(r#"
   fn foo(args…) {}
"#);
2 Likes

Thanks for the responses! It's unfortunate that proc macro attribute input is so limited, but I assume that's to improve errors in things like rust_analyzer. I guess I'll have to either rethink my approach or use a function-like macro.