How compiler parse nested attribute?

#[A]
pub struct Foobar {
    #[B]
    foo,
    #[C]
    bar,
}

What's the macro execute order for A, B and C?
Or the attributes on struct fields do not get executed at all?

Another side question, does procedual macro only work for struct, enum, and function?

2 Likes

First #[A] is evaluated (with the exception of A being derive and B being a cfg, in that case the cfg gets resolved first before feeding into the proc-macro attributes).

This means that #[A] gets to see #[B] (& #[C]), and can thus "process" those (and remove them), or just ignore them (and "re"-emit them).

That being said, besides #[cfg(…)] and #[doc = "…"] (i.e., /// …), there is no way to have an attribute that directly interacts with a field (at least on stable Rust). It's always rather #[A] which will manually take care of looking for #[B] and acting accordingly.

A procedural macro is just a macro that happens to be implemented using Rust code (vs. the special language for macro_rules! macros).

On the other hand, there are three flavors of macro "call-sites":

  • function-like (currently implementable with both macro_rules! macros and procedural macros (#[proc_macro]-annotated) (and macro macros));

    • They can receive any arbitrary sequence of tokens as input, but can "only" be called in item, expression, statement, pattern or type position.
  • derives (currently only implementable with procedural macros (#[proc_macro_derive(…)]-annotated);

    • They can only be fed to the special #[derive(…)] attribute, which can only be applied to a struct, enum or union definition.
  • attributes (same, #[proc_macro_attribute] only).

    • They can generally only be applied to an item [definition], such as:

      • the input of a #[derive(…)] (struct/enum/union) or even a type (alias) definition;

      • a function;

      • an impl block;

      • a(n inline!) mod (e.g., #[attr] mod foo { … });

      • a use statement;

      • a function-like macro invocation;

      • an extern block;

      • a const or static definition;

    • But they are planned to also be applicable to an expression, a statement, or even some types or some function parameters list (you can experiment with these things on nightly rust).

10 Likes

B and C can only be helper attributes, not macros, which means they're never executed but are instead read by other macros.

They work on any item

2 Likes

How to remove or re-emit the nested attributes?
You mean the A would change the original codes?

Yes, that's the whole point of (non-derive) attribute macros.

2 Likes

For example, the A processing would change the definition of struct Foobar? And even it would remove nested attribute on fields?

It's completely up to the implementation of the macro. It does whatever it wants with its imput, and emits whatever code it wants to emit.

Yes, that's the difference between using #[derive(SomeDeriveMacro)] and #[some_proc_macro_attr]: while the former only gets to peek / read at the input struct/enum/union definition (including the #[B] and #[C] attrs), a proc-macro attribute acts like a function-like macro in that regard:

#[some_macro_attr]
… some item …

could always be written as:

some_function_like_macro! {
    … some item …
}
  • in that regard, by the way, there is a proc-macro attribute that does exactly that: it takes a macro_rules! macro parameter, and just expands to a call to that macro with the item as input:

    #[macro_rules_attribute(some_macro!)]

That is, the item definition is just an input given to the macro, which gets to emit whatever it wants. When you thing about this, by the way, that power is already showcased by the #[cfg(…)] attributes: they're either the identity macro (re-emitting the input as-is), or they are a "nullifying" macro which ignores its input and expand to nothingness.

In practice, however, a proc-macro will likely interact with its input, often leaving it be, or sometimes mutating it a bit, but it is important to be aware of this: that function-like and attribute macros can emit whatever they want, disregarding or mutating their input.

1 Like

And, if the struct has multiple attributes, then what's the processing order of them? And the latter one would see the changed token stream from previous one?

#[A]
#[B]
pub struct Foobar {
...
}

First A would be fed with

#[B]
pub struct Foobar {
...
}

then it depends on whatever A produces. For example A may be a macro that removes other attribute macros, in which case nothing else will be processed.

Attributes (applied to the same item) are processed in an outer-most inner-most fashion, with the attributes at the top being considered the outer ones:

#[A]
#[B]
some_item…

would be, using my macro_rules comparison:

A! {
    #[B]
    some_item…
}

/* if `A!` re-emits its input, then we get:
B! {
    some_item…
}
*/

Here is a demo showcasing that: Rust Playground

How about the derive macro?
For example, #[derive(A,B,C)], what's the processing order among A, B, C?

Another question:

#[A]
#[derive(A,B,C)]
#[B]
struct Foo;

what's the processing order?

Since derive macro can't change its input (only append to it), this order won't matter unless the macro has side-effects, which are AFAIK discouraged anyway.

The same as above: first A is run, then compiler looks on what was emitted and takes the first remaining attribute, if there is any.

1 Like

Since you are asking questions about the rules of procedural macros, you may also want to read the relevant section of reference:
https://doc.rust-lang.org/stable/reference/procedural-macros.html

1 Like

What about attributes on function,?

#[A]
fn echo(#[B] a, b) {}

Is it similiar to the struct case,: A get processed first, and looking for B? That is, the B will not be processed?

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.