2 个版本

0.1.1 2021年12月7日
0.1.0 2020年9月4日

#1336过程宏

MIT/Apache

46KB
647

inline-proc

此crate提供在您的代码中直接编写过程宏的能力,而不是必须使用另一个crate。

仓库 - crates.io - 文档

示例

use inline_proc::inline_proc;

#[inline_proc]
mod example {
    metadata::ron!(
        edition: "2021",
        clippy: true,
        dependencies: {
            "quote": "1",
        },
        exports: (
            bang_macros: {
                "def_func": "define_function",
            },
        ),
    );

    pub fn define_function(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
        quote::quote!(
            fn macro_function() {
                println!("Hello from a proc macro!");
            }
        ).into()
    }
}

def_func!();

macro_function();
// => Hello from a proc macro!

工作原理

inline-proc 会将您的代码放入一个具有路径 {临时目录}/inline-proc-crates/{包名}-{重要包版本}-{模块名} 的crate中。例如,如果您在Linux中对位于 my-nice-crate 中的模块 my_module 调用 inline-proc,该crate的版本为 0.7.3,将在 /tmp/inline-proc-crates/my-nice-crate-0.7-my_module 中创建一个临时crate。

然后,它使用Cargo将其编译为 dylib,并将所有输出的错误转换为过程宏的错误,使其看起来与内联编写代码相同。请注意,过程宏目前在稳定版上无法发出警告,因此如果您想那样做,您必须使用nightly。

它输出 macro_rules! 宏,这些宏扩展为对私有宏 inline_proc::invoke_inline_macro! 的调用。此宏接受由 inline_proc 属性宏生成的dylib的路径、该dylib内部宏的名称、宏的类型(bang/derive/attribute)以及宏的输入。它打开dylib并调用宏,返回其结果。

使用生成的宏

#[inline_proc] 生成的宏可以直接使用。

use inline_proc::inline_proc;

#[inline_proc]
mod direct_usage {
    metadata::ron!(
        edition: "2021",
        dependencies: {},
        exports: (
            bang_macros: { "my_bang_macro": "my_bang_macro" },
            derives: { "MyDeriveMacro": "my_derive_macro" },
            attributes: { "my_attribute_macro": "my_attribute_macro" },
        ),
    );
    use proc_macro::TokenStream;

    pub fn my_bang_macro(_input: TokenStream) -> TokenStream {
        todo!()
    }
    pub fn my_derive_macro(_item: TokenStream) -> TokenStream {
        todo!()
    }
    pub fn my_attribute_macro(_attr: TokenStream, _item: TokenStream) -> TokenStream {
        todo!()
    }
}

my_bang_macro!(input tokens);
MyDeriveMacro!(struct InnerItem;);
my_attribute_macro!((attribute tokens) struct InnerItem;);

这对于“bang”宏来说效果很好,但对于属性或衍生宏来说则不是那么好。因此,这个crate提供了属性和衍生宏#[inline_attr]InlineDerive;它们可以这样使用

#
use inline_proc::{InlineDerive, inline_attr};

#[derive(InlineDerive)]
#[inline_derive(MyDeriveMacro)]
struct InnerItem;

#[inline_attr[my_attribute_macro(attribute tokens)]]
struct InnerItem;

它们扩展成与上面相同的代码。

导出宏

为了导出你的宏,你首先需要将你的宏定义改为:"macro_name": ( function: "macro_function", export: true )。这将做三件事

  1. 使用macro_rules!生成的内容标注#[macro_export]#[doc(hidden)]
  2. 让宏接受inline_proc::invoke_inline_macro的路径。
  3. 在宏的名称后添加_inner后缀。

然后你创建一个类似这样的包装器

// At the crate root

#[doc(hidden)]
pub use inline_proc::invoke_inline_macro;

// Where your #[inline_proc] is

/// This macro does XYZ.
#[macro_export]
macro_rules! my_macro {
    ($($tt:tt)*) => {
        $crate::my_macro_inner!($crate::invoke_inline_macro, $($tt)*);
    }
}

这种间接级别是必要的,因为过程宏没有像MBEs那样获取当前crate的方式($crate),所以你必须通过MBE方法提供。

crate属性

内联过程宏支持内部crate属性。

use inline_proc::inline_proc;

#[inline_proc]
mod crate_attributes {
   #![feature(box_syntax)]

    metadata::ron!(
        edition: "2021",
        dependencies: {},
        exports: (bang_macros: { "my_bang_macro": "my_bang_macro" }),
    );

    use proc_macro::TokenStream;

    pub fn my_bang_macro(_input: TokenStream) -> TokenStream {
       *box TokenStream::new()
    }
}
my_bang_macro!(input tokens);

注意事项

这种方法和常规过程宏相比有几个注意事项

  • 编译速度较慢,因为必须调用第二个Cargo实例。
  • 无法使用TOML来定义依赖项。
  • 导出宏很麻烦。
  • 宏只能定义在一个文件中。
  • 错误信息不够有帮助。Nightly做了一些改进,但仍然不像本地过程宏错误那样好。
  • 不支持衍生辅助属性。InlineDerive宏预留了helper辅助属性,所以你可以用例如将#[my_helper]替换为#[helper[my_helper]]

许可证:MIT OR Apache-2.0

依赖项

~1.3–2.4MB
~48K SLoC