14

I'm looking for the equivalent of file!() & module_path!() in a procedural macro context.

For example, the following doesn't work:

file.rs:

#[some_attribute]
const A: bool = true;

macro.rs:

#[proc_macro_attribute]
pub fn some_attribute(attr: TokenStream, input: TokenStream) -> TokenStream {
    println!("{}", file!());

    input
}

This prints macro.rs which makes sense, but what I want is file.rs. Is there a way to achieve this? Is there also a similar way for module_path!()?

A requirement of this is that has to happen at compile-time.

I'm trying to create a file in the OUT_DIR containing constant values where the attribute is added with the module and the file that they are in.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
tversteeg
  • 4,717
  • 10
  • 42
  • 77
  • 3
    There's a nightly API to [get the source file for a `Span`](https://doc.rust-lang.org/proc_macro/struct.Span.html#method.source_file) (which you can get from a `TokenTree` using the `span()` method), but at a glance I didn't find a way to access the module path, nor did I find a way that works on stable. – Sven Marnach Mar 18 '20 at 11:34
  • This is currently unstable as per @SvenMarnach's comment, however if you could give us some details about your use case, we might help with alternatives. – mcarton Mar 18 '20 at 12:53
  • My use case is a bit complicated, but basically what I'm trying to achieve is to create a file in `OUT_DIR` during compile-time of all the `const` values where the attribute is added with the module and the file that they are in. – tversteeg Mar 18 '20 at 13:42

2 Answers2

8

I had the same problem and found out that Rust added a new experimential API to Rust macros (#54725) which allows exaclty what you want:

#![feature(proc_macro_span)]

#[proc_macro]
pub(crate) fn do_something(item: TokenStream) -> TokenStream {
    let span = Span::call_site();
    let source = span.source_file();
    format!("println!(r#\"Path: {}\"#)", source.path().to_str().unwrap())
        .parse()
        .unwrap()
}
use my_macro_crate::*;

fn main() {
    println!("Hello, world!");
    do_something!();
}

Will output:

Hello, world!
Path: src\main.rs

Important

Apart from this API being experimential, the path might not be a real OS path. This can be the case if the Span was generated by a macro. Visit the documentation here.

tshepang
  • 12,111
  • 21
  • 91
  • 136
B. Colin Tim
  • 293
  • 5
  • 13
  • I use this in a macro of mine (I want the path to the source file where the macro is used), and I would really like to get off nightly and back on to stable - and this is my only need for nightly in a large project. :-( I'm asking for help on Discord #macros channel also, but if anyone here knows that would be much appreciated. – Andrew Mackenzie Aug 18 '22 at 18:03
  • Yes. This does not work on stable, even enabling the source_file() method via semver flags, so you (we :-( ) are stuck on nightly to get the source file name. Discord users help and said I need this done first: https://github.com/rust-lang/rfcs/pull/3200 FYI – Andrew Mackenzie Oct 14 '22 at 16:02
1

The problem here is that println!("{}", file!()); is executed at compile time and not at runtime. Similar to an answer that was recently given here, you can edit the original function and insert your code at the beginning of it, which will be executed at runtime this time. You can still use the procedural macros file!() and module_path!(). Here is a macro.rs with this approach:

#[proc_macro_attribute]
pub fn some_attribute(_attr: TokenStream, input: TokenStream) -> TokenStream {

    // prefix to be added to the function's body
    let mut prefix: TokenStream = "
        println!(\"Called from {:?} inside module path {:?}\",
            file!(), module_path!());
    ".parse().unwrap();

    // edit TokenStream
    input.into_iter().map(|tt| { 
        match tt { 
            TokenTree::Group(ref g) // match function body
                if g.delimiter() == proc_macro::Delimiter::Brace => { 

                    // add logic before function body
                    prefix.extend(g.stream()); 

                    // return new function body as TokenTree
                    TokenTree::Group(proc_macro::Group::new(
                        proc_macro::Delimiter::Brace, prefix.clone()))
            },
            other => other, // else just forward
        }
    }).collect()
} 

You can use it like this in your main.rs:

use mylib::some_attribute;

#[some_attribute]
fn yo() -> () { println!("yo"); }

fn main() { yo(); }

Note that the code is added before what's inside of the function's body. We could have inserted it at the end, but this would break the possibility of returning a value without a semicolon.

EDIT: Later realized that the OP wants it to run at compile time.

Victor Deleau
  • 875
  • 5
  • 17
  • 2
    I was assuming the OP tried to access the information at compile time rather than at runtime, but reading the question again, this answer probably is what they want. – Sven Marnach Mar 18 '20 at 13:22
  • 1
    @SvenMarnach is right, I do want the information at compile time. – tversteeg Mar 18 '20 at 13:38
  • 1
    My bad ! I will leave my answer nevertheless, as I think it contributed to the understanding of the question – Victor Deleau Mar 18 '20 at 16:55