有没有办法在编译时获取程序宏所在的文件和模块路径?

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

我正在寻找程序宏上下文中 file!() & module_path!() 的等价物。

例如,以下内容不起作用:

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
}

这会打印出 macro.rs,这是有道理的,但我想要的是 file.rs。有没有办法做到这一点? module_path!()也有类似的方法吗?

此要求必须在编译时发生。

我正在尝试在 OUT_DIR 中创建一个包含常量值的文件,其中属性与模块和它们所在的文件一起添加。

这里的问题是 println!("{}", file!()); 编译时 而不是在 运行 时执行。类似于最近给出的答案,您可以编辑原始函数并在其开头插入您的代码,该代码将在运行时间[=30=执行] 这次。您仍然可以使用程序宏 file!()module_path!()。这是使用这种方法的 macro.rs

#[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()
} 

您可以在 main.rs:

中像这样使用它
use mylib::some_attribute;

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

fn main() { yo(); }

请注意,代码添加在之前函数主体的内容。我们可以将它插入到末尾,但这会破坏返回不带分号的值的可能性。

编辑:后来意识到OP希望它在编译时运行。

我遇到了同样的问题,发现 Rust 向 Rust 宏 (#54725) 添加了一个新的实验性 API,它可以满足您的需求:

#![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!();
}

将输出:

Hello, world!
Path: src\main.rs

重要

除了这个 API 是实验性的,该路径可能不是真正的 OS 路径。如果 Span 是由宏生成的,就会出现这种情况。访问文档 here.