Proc_macro expansion order and name resolution

Hey guys, I was working on a project which involves proc-macro and I got confused about how the macro name resolution is done.

If I have custom proc-macro attribute in one file swap.rs and include that file as a module in main.rs. When I call cargo build, will the name resolution of main.rs happen before or after the macro expansion of swap.rs?

Moreover, my custom attribute will be expanded to some new macros which are exported, I would like to call those macros.

Name resolution and macro expansion happens in a loop, until no more progress can be made. There are generally no hard guarantees about resolution and expansion order, which is why procedural macros should not retain state between invocations.

To get more concrete advice you'll probably have to provide a code example.

I have a custom macro attribute used in swap.rs:

#![feature(type_ascription)]
#![allow(unused_variables)]
#![allow(dead_code)]
#![allow(unused_must_use)]

extern crate prusti_contracts;
use prusti_contracts::*;

#[extern_spec]
mod std {
    mod mem {
        use prusti_contracts::*;

        #[ensures(*a == old(*b) && *b == old(*a))]
        pub fn swap(a: &mut i32, b: &mut i32);
        // pub fn swap<T: std::cmp::PartialEq + Copy>(a: &mut T, b: &mut T);
    }
}

The attribute will be expanded to some macros definition and get exported:

#[macro_use]
pub mod std_14549694944180133602 {
    #[macro_export]
    macro_rules! swap {
        () =>
        {
            #[ensures(* a == old(* b) && * b == old(* a))]
            #[prusti :: extern_spec] #[trusted] pub fn
            swap(a : & mut i32, b : & mut i32)
            {
                std :: mem :: swap(a : & mut i32, b : & mut i32) ;
                unimplemented! ()
            }
        } ;
    }
    #[macro_export]
    macro_rules! mem {
        ($ i : ident $ (:: $ rest : tt) ?) =>
        {
            mod mem841f06ca5cb649cfa888f79809a9e10f
            { use prusti_contracts :: * ; $ i! ($ ($ rest) ?) ; }
        } ; () =>
        {
            mod mem841f06ca5cb649cfa888f79809a9e10f
            { use prusti_contracts :: * ; swap! () ; }
        } ;
    }
    #[macro_export]
    macro_rules! std {
        ($ i : ident $ (:: $ rest : tt) ?) =>
        { mod stdf8d37ff9f8a645df934445936e0d7a0a { $ i! ($ ($ rest) ?) ; } }
        ; () => { mod stdf8d37ff9f8a645df934445936e0d7a0a { mem! () ; } } ;
    }
}

Then I try to call these macros in another file main.rs:

extern crate prusti_contracts;
use prusti_contracts::*;

#[macro_use]
mod swap;

// This macro invocation will expands to simply call swap::std!(mem::swap)
prusti_use!(swap::std::mem::swap);
fn main() {
    let mut x = 5;
    let mut y = 42;

    std::mem::swap(&mut x, &mut y);

    assert!(42 == x);
    assert!(5 == y);
}

I got error in macro expansion process:

Looks like the inner attribute type-ascription doesn't work here and since the std macro is not found, it looks like the error is reported before my custom attribute got expanded

Appreciate any help

This error happens because pub fn swap(a: &mut i32, b: &mut i32); is not valid syntax inside a module. You need to provide a function body.

1 Like

Hard to diagnose without an actual reproductible example, especially given that you are talking about a very "experimental" / unstable framework:

(next time link directly to it, since it is very unique / unlike other crates).

unlike other crates

From skimming the documentation, prusti true semantics seem to be enabled by using a special and tweaked toolchain on the code having the prusti annotations (prusti-rustc, cargo-prusti).

In order to reduce breakage with a non-tweaked toolchain, I suspect they have also made it so their special attributes are also implemented as no-op proc-macro attributes, for when they are not processed by their special toolchain. This file indeed looks suspiciously trivial.

So, if you happen to be trying rusti special attributes on a normal compiler, the attributes may not be expanding to the expected code, and, instead, may be acting as the transparent / identity / forward-its-input proc-macro.

If that's the case, then

may end up expanded as:

mod std {
    mod mem {
        use prusti_contracts::*;

        pub fn swap(a: &mut i32, b: &mut i32);

hence the error.


Note that I may be wrong, but we'll then need to know exactly which commit / version of the prusti framework you are working with, which commands you are running to test it, and, ideally, a repository with code enough to reproduce the error

Sorry about the confusion caused by the code example, I guess I am trying to ask something more general than the context of prusti.

What I am really confused is this: if I have a.rs and b.rs, and I have attribute in b.rs which expands to a macro def, can I call it in a.rs:

In b.rs:

#[attribute]
mod b { ... }

The attribute then simply expands to:

#[macro_export]
macro_rules! b {
   ....
}

In a.rs:

#[macro_use]
mod b;

// Can I call it like that?
b!();

Basically, what happens first when cargo invokes compiler to take care of in this scenario? The resolution of macro calls in a.rs, or the expansion of proc-macro in b.rs? And is it possible to use the macro in the way I have in the later example?

Yes, that should work. Macro expansion and name resolution happens in a loop, until no more progress can be made, so the compiler will always expand macros in an order that works (if one exists).

Note that #[macro_export] puts a macro at the root of the crate, and exports it for downstream crates. I don't believe this is what you want; instead, you may want to write:

macro_rules! b { … }
pub(crate) use b;

and then, in a.rs, you can refer to b::b!; if you use b::*;, for instance, you'd be able to call b! afterwards.


Back to the OP, the TL,DR is: if your macro expands to the correct code, then other code will be able to refer to it, even nested macro invocations. In practice, it means you don't have to worry about the interactions between macro expansion and name resolution, it Just Works™ (there is one very specific exception if the new macro call (e.g., b! in your example), happens in a eager-expansion site, such as concat!, env!, include…!, the format string given to print…format…!).

Note that this means that if a macro invocation expands to code that affects name resolution so that that very macro invocation to begin becomes ambiguous, an error is produced (as expected, I'd say, hence maintaining the Just Works™ property):

#![allow(unused)]
macro_rules! m {() => ( use a::*; )}

mod a {
    macro_rules! m {() => ( use b::m; )}
    pub(in crate) use m;
}

mod b {
    macro_rules! m {() => ( )}
    pub(in crate) use m;
}

m!(); // <- Which `m!`?
  1. First invocation of m! resolves to the one defined at line 2.

  2. This emits a use a::*;, so that a::m! is potentially brought into scope. The m! invocation could then refer to that one => Error (already!)

  3. The invocation of a::m! would emit a use b::m;, so that b::m! would definitely be brought into scope. The m! invocation would then refer to that one => Second error.

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.