[Solved] Compiler information from custom attribute handler (proc macro)?


#1

Consider one is implementing a custom attribute to apply to impl block. Let’s say they want to auto-implement the add_assign fn:

use std::ops;

struct Foo(usize);

#[impl_add_assign]
impl ops::AddAssign<usize> for Foo {

}

Is it possible to retrieve compiler information (I’m not sure how it’s called; e.g. full path to the implementing trait (std::ops::AddAssign<Rhs = usize>), and list of methods the trait has) from a custom attribute handler?

#[proc_macro_attribute]
fn impl_add_assign(args: TokenStream, input: TokenStream) -> TokenStream {
    // ?
}

Maybe there’re some tutorials, or crates that do the thing, which source code I can look?


#2

To directly answer your question: That’s not possible. You only receive the exact source code as written below the attribute. The token stream only carries syntactic and span information. The responsibility for defining semantic meaning is the job of the macro programmer (the Syn crate helps with this).

The example is weird because you use an attribute for implementing traits on your type. This is the use case for derive macros.
A derive macro is typically implemented as a reduction. You require your fields to implement a certain trait and delegate the implementation logic through those fields.

#[derive(MyAddAssign)]
struct Foo(usize);

The macro could expand to the following.

mod __impl_addassign_foo {
    use std::ops::AddAssign;

    impl AddAssign for Foo 
    where usize: AddAssign
    {
        type Rhs = Foo; // Optional
        fn add_assign(&mut self, rhs: Foo) {
            *self.0 += rhs.0
        }
    }
}

*The core traits are (almost) always implemented for primitive types.

I’m unsure what you’re trying to achieve with the absolute path within the macro. Could you explain your use case(s)?
What’s the point to make the macro dynamic if you already need a specific attribute per use case?


#3

Thanks for the reply!

Yeah, but I was thinking maybe there’s API that provides some context about where the macro is expanding.

So, about the weirdness of the example and the use case. There is RFC for implementation delegation. What I was interested in is it possible, in principle, to implement delegation with a macro. A bit less weird example would look like:

struct AddOnlyCounter(u32);

impl AddOnlyCounter {
    fn new() -> Self {
        Self { 0 }
    }
}

#[delegate("* to self.0")]
// or #[delegate("fn add_assign to self.0")]
impl ops::AddAssign<u32> for AddOnlyCounter { }

Note: this may sound like the XY problem, but I’d have liked to also know whether the semantics are available in macros, thus my question.


#4

You could write a compiler plugin, which is more powerful than macros. I’ve never done this because there are no guarantees about stability of internal compiler API and there probably never will be -> This is actually the reason for stabilizing procedural macros as is.
There is one example that i know of: https://github.com/SkylerLipthay/interpolate_idents

I understand the need for delegation. There is currently no way, other than having compiler context, to retrieve the necessary information.

Related to this topic, a crate passed by a few weeks earlier which (partly) solved delegation utilizing associated methods: Delegate: method delegation with less boilerplate.
There is also remote derive implemented in Serde, which could be of interest.


#5

This fully answers my question. Thank you again!