有没有一种方法可以在编译时获取过程宏所附加的文件和模块路径?

15

我在寻找在过程宏上下文中等效于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中创建一个文件,其中包含具有模块和它们所在的文件添加了属性的常量值。


3
有一个夜间 API 可以 获取 Span 的源文件(你可以使用 span() 方法从 TokenTree 中获取它),但一眼望去我没有找到访问模块路径的方法,也没有找到在稳定版上可用的方法。 - Sven Marnach
目前根据@SvenMarnach的评论,这个还不稳定,但是如果您能提供一些关于您使用情况的细节,我们可能会提供一些替代方案。 - mcarton
我的用例有点复杂,但基本上我想要实现的是在编译时创建一个文件,其中包含所有添加了模块和它们所在文件属性的const值,该文件位于OUT_DIR中。 - tversteeg
2个回答

9

我曾经遇到同样的问题,后来发现 Rust 在 Rust 宏中添加了一个新的实验性 API(#54725),可以实现你想要的功能:

#![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是实验性的之外,路径可能不是真正的操作系统路径。如果Span是由宏生成的,则可能会出现这种情况。请访问此处的文档(链接)


我在我的宏中使用这个(我想要宏所使用的源文件路径),我真的很想从夜间版本回到稳定版本 - 这是我在一个大项目中唯一需要夜间版本的原因。 :-( 我也在 Discord 的 #macros 频道上寻求帮助,但如果这里有人知道那将不胜感激。 - Andrew Mackenzie
是的。即使通过semver标志启用source_file()方法,它也无法在稳定版上工作,因此您(我们 :-( )必须使用nightly才能获取源文件名。Discord用户提供帮助并表示我需要先完成以下操作:https://github.com/rust-lang/rfcs/pull/3200。FYI - Andrew Mackenzie

1
这里的问题是println!("{}", file!());在编译时而不是运行时执行。类似于最近给出的答案此处,您可以编辑原始函数并在其开头插入代码,这将在运行时执行。仍然可以使用过程宏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 希望它在编译时运行。

2
我原本以为OP试图在编译时访问信息而不是运行时,但再次阅读问题后,这个答案可能是他们想要的。 - Sven Marnach
1
@SvenMarnach是正确的,我确实希望在编译时获取信息。 - tversteeg
1
我的错!不过我还是会留下我的回答,因为我认为它有助于理解问题。 - Victor Deleau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接