proc_macro2

proc_macro2 是对 proc_macro 的包装,因为后者只能用在 proc-macro crate 中。

这两个的库的大多类型都相互实现了 From,所以可以相互转化。但出于习惯1,我们常常使用 from 方法,而不使用 into 方法(当然,你要是想使用 into 方法,也没有人会阻止你)。

1

仅限于过程宏范畴,因为这一说法来自于我对 dtolnay/syn#35b498 的观察。

我们可以在内部使用 proc_macro2 的类型,而在最后的过程宏函数中把 proc_macro2::TokenStream 转化成 proc_macro::TokenStream

// src: https://github.com/zjp-CN/proc-macro-workshop/blob/master/bitfield/impl/src/lib.rs
use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn bitfield(_: TokenStream, input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as syn::Item);
    TokenStream::from(bit::expand(input))
}

#[proc_macro]
pub fn gen(_: TokenStream) -> TokenStream {
    TokenStream::from(gen::generate()) 
}

#[proc_macro_derive(BitfieldSpecifier)]
pub fn derive_bitfield_specifier(input: TokenStream) -> TokenStream {
    let input = syn::parse_macro_input!(input as syn::ItemEnum);
    TokenStream::from(spe::derive_bitfield_specifier_for_enum(input))
}

接下来讨论 TokenStreamTokenTree

TokenStream

TokenStreaminherent 方法很少,只有 fn new() -> Selffn is_empty(&self) -> bool。更重要的是其 trait 实现:

  • IntoIterator:从 TokenStreamTokenTree 只需要 .into_iter()
  • FromIterator<TokenStream>:把多个 TokenStream 汇聚成一个 TokenStream
  • FromIterator<TokenTree>:把多个 TokenTree 汇聚成一个 TokenStream
  • Extend<TokenStream>:把多个 TokenStream 添加进来
  • Extend<TokenTree>:把多个 TokenTree 添加进来

构造 TokenStream 并不难,最常用 quote::quote!

借助 Iterator 相关的泛型实现,不难理解下面的代码 —— 把多个 TokenStream 汇总成一个 TokenStream

use proc_macro2::TokenStream;

fn main() {
    {
        // `Extend<TokenStream>`
        let mut ts = TokenStream::new();
        ts.extend(iter());
        assert_eq!(count(ts), TS);
    }
    {
        // `FromIterator<TokenStream>` + impl<I: Iterator> IntoIterator for I
        let ts = TokenStream::from_iter(iter());
        assert_eq!(count(ts), TS);
    }
    {
        // `FromIterator<TokenStream>` + `Iterator::collect()`
        let ts: TokenStream = iter().collect();
        assert_eq!(count(ts), TS);
    }
    {
        // quote! 的反复插值
        let iter = iter();
        assert_eq!(count(quote::quote! {#(#iter)*}), TS);
    }
}

const N: usize = 10;
const TT: usize = 7;
const TS: usize = N * TT;

fn f(i: usize) -> TokenStream {
   // 这里有 7 个 TokenTree
   quote::quote! { const _: usize = #i; }
}

fn iter() -> impl Iterator<Item = TokenStream> { (0usize..N).map(f) }

fn count(ts: TokenStream) -> usize { ts.into_iter().count() }

TokenTree

TokenTree 是一个枚举体,它描述一个标记的类别:

pub enum TokenTree {
    Group(Group),
    Ident(Ident),
    Punct(Punct),
    Literal(Literal),
}
类型含义
Group{}()[]分隔出来的标记(包括两侧的括号分隔符)
Ident一个标识符,比如 ABC_let
Punct一个标点,比如 +,$
Literal一个字面值,比如字符 'a'、字符串 "hello"、数字 2.3f64

你很少自己通过 TokenTree 解析标记,因为 syn 提供大量基础而通用的节点类型,它们都实现了 Parse trait。

一个很好的必须使用 TokenTree 的案例是 proc-macro-workshop 的 Seq,你可以参考我的解答