#proc-macro #tokens #quote #syntax-tree #syn #quasi-quoting #procedural

safe-quote

基于 quote 包的一个分支,增加了 forbid(unsafe_code) 功能,并依赖于 safe-proc-macro2 而不是 proc-macro2

2个稳定版本

1.0.15 2022年2月7日
1.0.9 2021年2月25日

255过程宏 中排名

Download history 5951/week @ 2024-04-27 6462/week @ 2024-05-04 6198/week @ 2024-05-11 6097/week @ 2024-05-18 5412/week @ 2024-05-25 6020/week @ 2024-06-01 5714/week @ 2024-06-08 5615/week @ 2024-06-15 8001/week @ 2024-06-22 5724/week @ 2024-06-29 5680/week @ 2024-07-06 7053/week @ 2024-07-13 6693/week @ 2024-07-20 6688/week @ 2024-07-27 6008/week @ 2024-08-03 6236/week @ 2024-08-10

26,702 每月下载量
41 包中使用(直接使用3个)

MIT/Apache

230KB
5K SLoC

Rust近似引号

Build Status Latest Version Rust Documentation

这是一个基于 quote 的分支,增加了 forbid(unsafe_code) 功能,并依赖于 safe-proc-macro2 而不是 proc-macro2.


此包提供了将Rust语法树数据结构转换为源代码标记的 quote! 宏。

Rust的过程宏接收一系列标记作为输入,执行任意Rust代码以确定如何操作这些标记,并生成一系列标记返回给编译器以编译成调用者的包。近似引号是解决其中一部分问题的方法——生成返回给编译器的标记。

近似引号的想法是我们编写 代码,将其视为 数据。在 quote! 宏内部,我们可以编写看起来像代码的内容,供我们的文本编辑器或IDE使用。我们获得了编辑器括号匹配、语法高亮、缩进和可能自动补全的所有好处。但我们可以将其作为数据,传递它,修改它,最终将其作为标记返回给编译器以编译成宏调用者的包。

这个包的动机源于过程宏的使用场景,但它是一个通用的Rust近似引用库,并不特定于过程宏。

[dependencies]
quote = "1.0"

版本要求:Quote支持rustc 1.31及以上版本。
发布说明


语法

quote包提供了一个quote!宏,在其中你可以编写将被打包进TokenStream并作为数据的Rust代码。你应该将TokenStream视为表示Rust源代码片段。

quote!宏内部,使用#var进行插值。任何实现了safe_quote::ToTokens特质的类型都可以进行插值。这包括大多数Rust原始类型以及来自syn的大多数语法树类型。

let tokens = quote! {
    struct SerializeWith #generics #where_clause {
        value: &'a #field_ty,
        phantom: core::marker::PhantomData<#item_ty>,
    }

    impl #generics serde::Serialize for SerializeWith #generics #where_clause {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: serde::Serializer,
        {
            #path(self.value, serializer)
        }
    }

    SerializeWith {
        value: #value,
        phantom: core::marker::PhantomData::<#item_ty>,
    }
};

重复

重复使用#(...)*#(...),*来实现,类似于macro_rules!。这会遍历重复中插值的任何变量的元素,并为每个元素插入重复体的副本。插值中的变量可以是任何实现了IntoIterator的类型,包括Vec或现有的迭代器。

  • #(#var)* —— 没有分隔符
  • #(#var),* —— 星号前的字符用作分隔符
  • #( struct #var; )* —— 重复中可以包含其他内容
  • #( #k => println!("{}", #v), )* —— 甚至可以有多重插值

请注意,#(#var ,)*#(#var),*之间有区别——后者不会产生尾随逗号。这符合macro_rules!中分隔符的行为。


将标记返回给编译器

quote!的值是一个类型为safe_proc_macro2::TokenStream的表达式。同时,Rust过程宏期望返回的类型为proc_macro::TokenStream

这两种类型的区别在于,proc_macro类型完全特定于过程宏,并且永远不会存在于过程宏之外的代码中,而proc_macro2类型可能存在于任何地方,包括测试和主.rs、build.rs等非宏代码。这就是为什么过程宏生态系统在很大程度上是围绕proc_macro2构建的,因为这确保了库是可单元测试的,并且可以在非宏上下文中访问。

两种类型都存在From转换,因此从一个过程宏返回quote!的输出通常看起来像tokens.into()proc_macro::TokenStream::from(tokens)


示例

组合引用片段

通常,你不会一次性构建完整的TokenStream。不同的部分可能来自不同的辅助函数。由quote!产生的标记本身实现了ToTokens,因此可以被插入到后续的quote!调用中,以构建最终结果。

let type_definition = quote! {...};
let methods = quote! {...};

let tokens = quote! {
    #type_definition
    #methods
};

构建标识符

假设我们有一个标识符ident,它来自宏输入的某个地方,并且我们需要以某种方式修改它以供宏输出。让我们考虑在标识符前面添加一个下划线。

简单地将标识符插入到下划线旁边不会产生连接它们的行为。下划线和标识符将继续作为两个单独的标记存在,就像你写了_ x一样。

// incorrect
quote! {
    let mut _#ident = 0;
}

解决方案是构建一个具有正确值的新标识符标记。由于这种情况非常常见,format_ident!宏提供了一个方便的实用程序来完成此操作。

let varname = format_ident!("_{}", ident);
quote! {
    let mut #varname = 0;
}

或者,可以使用Syn和proc-macro2提供的API直接构建标识符。这大致等同于上面的方法,但不会处理ident是原始标识符的情况。

let concatenated = format!("_{}", ident);
let varname = syn::Ident::new(&concatenated, ident.span());
quote! {
    let mut #varname = 0;
}

进行方法调用

假设我们的宏需要宏输入中指定的一些类型具有一个名为new的构造函数。我们有一个名为field_type的变量,其类型为syn::Type,并希望调用该构造函数。

// incorrect
quote! {
    let value = #field_type::new();
}

这有时能工作。如果 field_typeString,展开后的代码包含 String::new(),这是可以的。但如果 field_type 是类似 Vec<i32> 的东西,那么展开后的代码是 Vec<i32>::new(),这是无效的语法。在手工编写的Rust中,我们通常会写 Vec::<i32>::new(),但对于宏来说,下面的方法通常更方便。

quote! {
    let value = <#field_type>::new();
}

这展开为 <Vec<i32>>::new(),表现正确。

类似的模式适用于特性方法。

quote! {
    let value = <#field_type as core::default::Default>::default();
}

卫生性

任何插值令牌都保留了由它们的 ToTokens 实现提供的 Span 信息。在 quote! 调用中产生的令牌使用 Span::call_site() 进行标记。

可以通过 quote_spanned! 宏显式提供不同的范围。


非宏代码生成器

当在 build.rs 或 main.rs 中使用 quote 并将输出写入文件时,请考虑在写入之前让代码生成器通过 rustfmt 处理令牌(通过调用 rustfmt 二进制文件或通过将 rustfmt 库作为依赖项引入)。这样,如果生成的代码中发生错误,则方便人类阅读和调试。

请注意,当令牌写入文件时,不会保留任何类型的卫生或范围信息;从令牌到源代码的转换是有损的。


许可证

根据您的选择,受Apache License,Version 2.0MIT license 许可证的约束。
除非您明确声明,否则根据 Apache-2.0 许可证定义的,您有意提交以包含在此软件包中的任何贡献,将如上所述双重许可,而无需任何其他条款或条件。

依赖关系