推断调用包的名称以在过程宏中填充一个 doctest

Infer the name of the calling crate to populate a doctest in a procedural macro

我正在创建一个程序宏,它从一些配置文件自动生成一个库(这是一个寄存器布局,但这对问题并不重要)。

我希望图书馆自动生成自动图书馆附带的文档,并包括应该 运行 和 cargo test 的文档测试。现在,我已经实现了其中的大部分,但是有一个问题我看不到解决方案。

假设我们有一个名为 my_lib 的库,我们在其中调用宏来填充它:

use my_macro_lib::hello;

hello!();

扩展为:

/// `foo` will always return `true`
/// ```
/// use my_lib;
/// assert!(my_lib::foo());
/// ```
pub fn foo() -> bool {
    true
}

这将 运行 如预期的那样 - cargo doc 将做正确的事情并且 cargo test 将 运行 如预期的那样进行 doctests。

问题是,在这个例子中,use my_lib 被硬编码到 my_macro_lib 中,这显然是不可取的。

我如何创建一个宏来推断执行调用的 crate 的名称?

我尝试在程序宏中使用 macro_rules! 来扩展 $crate,但这违反了卫生规则。

您可以通过读取 CARGO_PKG_NAME 环境变量来获取正在使用您的宏的 crate 的名称。请注意,您必须通过阅读它 std::env (at "runtime" of your macro) and not via env!(当你的 proc macro crate 被编译时)。

#[proc_macro]
pub fn hello(input: TokenStream) -> TokenStream {
    let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
    let use_statement = format!("use {}::foo;", crate_name);

    let output = quote! {
        /// `foo` will always return `true`
        /// ```
        #[doc = #use_statement]
        /// assert!(foo());
        /// ```
        pub fn foo() -> bool {
            true
        }
    };

    output.into()
}

这里有一些关于在文档注释中插入内容的复杂问题。像 /// #an_ident 这样的插值不起作用,因为文档注释以特殊方式解析。我们可以做到这一点的唯一方法是创建一个字符串并使用 #[doc = ...] 语法。这有点烦人,因为您必须在 quote! 调用之前创建字符串,但它有效。

但是,我认为不能保证这有效。目前 proc 宏可以访问所有环境(包括文件系统、网络...)。据我所知,proc 宏不能保证此访问权限,并且 proc 宏将来可能会被沙盒化。所以这个解决方案还不完美,但它现在有效(可能还会持续一段时间)。


另一种方法是让用户将 crate 名称传递给您的宏:

hello!(my_lib);

如果您的宏在每个 crate 中只被调用一次,这可能是首选的解决方案。如果您的宏被多次调用,那么重复 crate 名称可能会很烦人。