如何解析自定义 rust proc_macro 属性中的其他属性?
How to parse other attributes in custom rust proc_macro attribute?
我正在编写一个 proc_macro 属性,该属性将字段添加到结构中,并且还实现了我的特征(以及通过添加 #[derive(...)]
实现的特征)到扩展结构
这是我想做的事情的简化版本:
#[foo("some value")]
#[derive(Debug)]
struct A {
bar: u32,
}
展开后:
#[derive(Debug, Default, Serialize)]
struct A {
foo: u64
bar: u32,
}
impl FooTrait for A {
...
}
如何解析派生属性,以便将 Debug
特征与我用 foo
proc_macro 添加的所有其他特征一起添加?
使用 syn
,检查并更改 ItemStruct::attrs
:
use std::collections::HashSet;
use proc_macro2::{Delimiter, TokenTree};
use quote::ToTokens;
use syn::parse::Parser;
use syn::punctuated::Punctuated;
#[proc_macro_attribute]
pub fn foo(
attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let mut input = syn::parse_macro_input!(input as syn::ItemStruct);
let mut all_derived_traits = HashSet::new();
for i in 0..input.attrs.len() {
if !input.attrs[i].path.is_ident("derive") {
continue;
}
let derive = input.attrs.remove(i);
let mut tokens = derive.tokens.clone().into_iter();
match [tokens.next(), tokens.next()] {
[Some(TokenTree::Group(group)), None]
if group.delimiter() == Delimiter::Parenthesis =>
{
match Punctuated::<syn::Path, syn::Token![,]>::parse_terminated
.parse2(group.stream())
{
Ok(derived_traits) => all_derived_traits.extend(derived_traits),
Err(e) => return e.into_compile_error().into(),
}
}
_ => {
return syn::Error::new_spanned(derive, "malformed derive")
.into_compile_error()
.into()
}
}
}
all_derived_traits.extend([
syn::parse_quote!(Default),
syn::parse_quote!(Serialize),
]);
let all_derived_traits = all_derived_traits.into_iter();
input.attrs.push(syn::parse_quote! {
#[derive( #(#all_derived_traits),* )]
});
input.into_token_stream().into()
}
首先,我们将所有 derive()
属性收集到哈希集中,因此如果它们已经存在,我们将不会生成派生。这个逻辑并不完美:例如,我们不会将 std::default::Default
等同于 Default
。但它应该足够了。然后我们添加我们的特征和 re-generate 派生。注意,多个 #[derive()]
行是统一的,特征可能是有序的,但没关系。
我正在编写一个 proc_macro 属性,该属性将字段添加到结构中,并且还实现了我的特征(以及通过添加 #[derive(...)]
实现的特征)到扩展结构
这是我想做的事情的简化版本:
#[foo("some value")]
#[derive(Debug)]
struct A {
bar: u32,
}
展开后:
#[derive(Debug, Default, Serialize)]
struct A {
foo: u64
bar: u32,
}
impl FooTrait for A {
...
}
如何解析派生属性,以便将 Debug
特征与我用 foo
proc_macro 添加的所有其他特征一起添加?
使用 syn
,检查并更改 ItemStruct::attrs
:
use std::collections::HashSet;
use proc_macro2::{Delimiter, TokenTree};
use quote::ToTokens;
use syn::parse::Parser;
use syn::punctuated::Punctuated;
#[proc_macro_attribute]
pub fn foo(
attr: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let mut input = syn::parse_macro_input!(input as syn::ItemStruct);
let mut all_derived_traits = HashSet::new();
for i in 0..input.attrs.len() {
if !input.attrs[i].path.is_ident("derive") {
continue;
}
let derive = input.attrs.remove(i);
let mut tokens = derive.tokens.clone().into_iter();
match [tokens.next(), tokens.next()] {
[Some(TokenTree::Group(group)), None]
if group.delimiter() == Delimiter::Parenthesis =>
{
match Punctuated::<syn::Path, syn::Token![,]>::parse_terminated
.parse2(group.stream())
{
Ok(derived_traits) => all_derived_traits.extend(derived_traits),
Err(e) => return e.into_compile_error().into(),
}
}
_ => {
return syn::Error::new_spanned(derive, "malformed derive")
.into_compile_error()
.into()
}
}
}
all_derived_traits.extend([
syn::parse_quote!(Default),
syn::parse_quote!(Serialize),
]);
let all_derived_traits = all_derived_traits.into_iter();
input.attrs.push(syn::parse_quote! {
#[derive( #(#all_derived_traits),* )]
});
input.into_token_stream().into()
}
首先,我们将所有 derive()
属性收集到哈希集中,因此如果它们已经存在,我们将不会生成派生。这个逻辑并不完美:例如,我们不会将 std::default::Default
等同于 Default
。但它应该足够了。然后我们添加我们的特征和 re-generate 派生。注意,多个 #[derive()]
行是统一的,特征可能是有序的,但没关系。