Are attribute macros visible to each other?

I'm trying to make proc-macro-error work with proc-macro-hack. Both use attribute proc-macros and both are supposed to be used on the same function. Unfortunately, they don't work well together, yet. The problem is found and I believe I can fix it, but to do that I need to detect #[proc_macro_hack] attribute.

First I thought this should work:

let is proc_macro_hack = input.attrs.iter().any(|attr| attr.is_ident("proc_macro_hack"));

But my tests show me it doesn't. It looks like attribute proc-macros aren't really listed as attributes. Are they?

Have you tried using cargo expand to check if there are any attributes left after expansion. My guess is that because of resolution/generation order they can not see each other. I forked proc-macro-error but wondered if ther was a test case or something, or if in test-crate I could just add proc-macro-hack and an abort!?

I was curious about this too, so I've created a simple test: https://github.com/Cerberuser/multiple-attrs
In this repo, I'm creating two attribute macros which expect a function definition, print all attributes they see on it to the stderr, and return the input unchanged. Here's the code:

use attr1;
use attr2;

#[attr1::attr]
#[attr2::attr]
fn _test() {}

And here's the output:

attr1 seen: [Attribute { pound_token: Pound, style: Outer, bracket_token: Bracket, path: Path { leading_colon: None, segments: [PathSegment { ident: Ident { ident: "attr2", span: #0 bytes(0..0) }, arguments: None }, Colon2, PathSegment { ident: Ident { ident: "attr", span: #0 bytes(0..0) }, arguments: None }] }, tokens: TokenStream [] }]
attr2 seen: []

So, it looks like that every attribute can see the ones which are after it (and modify them if necessary), but can't see anything that is before - because these attributes have already done their work.

5 Likes

Interesting result, but can we actually rely on this order? I think macro expansion order is not specified.

cargo expand won't help because it will expand all macros, including the attributes

Not 100% sure, but I think I've read somewhere that the "outer to inner" order is guaranteed.

As far as I can tell, all this attributes are outer, the question is about order among them

Attributes will always expand in source order. This is already stable in that earlier attributes can modify later attributes (or even just remove them!) so the compiler team can't change it without breaking stability.

There is ongoing work to make this work properly including derive macros (which currently are required to be after any attribute macros and may or may not behave weirdly) and then document this fact as guaranteed.

3 Likes

OK, here is a summary:

  • Attributes can be "inert" or "active". Active attributes get removed once they're expanded, inert ones do not. Proc-macro attributes are always active.
  • Attributes' expansion order is "left to right". This behavior is stable and people, including a member of rust compiler team, believe this can be relied upon.

So, the answer is: proc-macro attributes can see other attribute macros that are below them and cannot see ones above.

2 Likes