Crate resolution in nested macro definitions

Suppose there are three crates: crate1, crate2 and crate3.

crate1 defines a macro_rules! macro A. When called, A defines other macro_rules! macros B, C and D. Furthermore, D needs to itself call B and C.

crate2 depends on crate1 and calls A with some arguments. A takes those arguments and produces B, C and D.

crate3 depends on crate2 and needs to call D. As mentioned above, D needs to call B and C, which are defined in crate2.

When writing A in crate1, how can I refer to B and C, which are not defined until A is called in crate2?

A few things I've tried:

  • Using $crate in crate1 will always expand to ::crate1, which isn't helpful.
  • I tried using this nested macro trick to produce the tokens $crate in crate2. It does appear to work, but it seems to be interpreted as the literal tokens $crate. It doesn't expand to ::crate2.

Instead, I get an error

could not find `B` in `$crate`

As a workaround, I could use a fixed name to represent crate2 when defining the macros in crate1. Then, I could require a use alias in crate3 mapping crate2 to that fixed name. It works, but it requires user intervention, which isn't ideal.

Is there some way to refer to the calling crate in a macro?

1 Like

Alternatively, I think I might be able to combine B, C and D into one macro, which would solve the issue.

Ideally, though, I would like to feature-gate B and C in certain situations. Right now, D will unconditionally call B and C, which will give an error if they don't exist.

To achieve the same effect in a combined macro would require feature-gating specific match arms of a macro, which I don't think is possible.

I think I found a sufficient workaround, but I'm still interested to see if anyone has a better idea.

For the moment, I define B and C as macros within D. Each arm of D expands to an expression block with a macro definition (B or C) and then an immediate call of that macro. I can still feature-gate the definition of B and C, causing the immediate call to fail.

macro_rules! D {
    ( $info:tt ) => {
        {
            #[cfg(feature = "foo")]
            macro_rules! B {
                // use $info
            }

            // This will fail if feature "foo" is not used
            B!()
        }
    }
}

So indeed if you make a $ crate where the span of $ is caller-provided, but where the span of crate is callee-provided, then the resulting $crate doesn't resolve to any crate, since I guess Rust considers it ambiguous as to whether it wanted to refer to the crate of the caller, or the crate of the callee.

This is what happens, for instance, if you do:

#[macro_export]
macro_rules! A {( $_:tt ) => (
    #[macro_export]
    macro_rules! C {() => (
     // caller-provided (e.g., crate2)
     // vv
        $_ crate :: …
        // ^^^^^
        // "hard-coded", thus, callee-provided (e.g., crate1)
    )}
)}

So the fix is to have A take both $ and crate (EDIT: fixed typo):

//! crate1
#[macro_export]
macro_rules! A {( $_:tt $krate:ident ) => (
    #[macro_export]
    macro_rules! B {() => ()}

    #[macro_export]
    macro_rules! C {() => (
        $_ $krate :: B ! {}
    )}
)}
//! crate2
::crate1::A! { $crate }
//! crate3
::crate2::C! {}

Playground


Note that since procedural macros can be unhygienic (use the call_site() span for whatever they want), this includes using the call_site() span for the literal $crate invocation, which leads to exactly the wanted behavior:

1 Like

Ah, I was close. I did try to pass down $crate, but I think I tried to do it as a path rather than as a token and identifier.

In any event, struggling against this pushed me to come up with a less complex solution, where I don't need to pass down any tokens. I'll probably keep what I have. Thanks for showing me how to do it, though.

One last thing. Is this a typo?

Don't you mean A should take both $ and crate?

1 Like

Ah yeah, that was a typo

1 Like

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.