Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to get the file and the module path of where a procedural macro is attached at compile-time?

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.

like image 344
tversteeg Avatar asked Mar 18 '20 11:03

tversteeg


2 Answers

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.

like image 121
B. Colin Tim Avatar answered Sep 29 '22 13:09

B. Colin Tim


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.

like image 28
Victor Deleau Avatar answered Sep 29 '22 13:09

Victor Deleau