准备
解析宏通过两个 crate 进行:
Derive 属性宏
探讨 Rust 宏系统中带属性(Attributes)的 Derive 宏的几种变体,以及如何进行解析。
属性宏的变体
-
函数调用
#[derive(Custom)] struct Demo { #[attr(arg)] a: i8, }
-
关键字参数调用
#[derive(Custom)] struct Demo { #[args(name = "val")] b: i8, }
-
直接赋值
#[derive(Custom)] struct Demo { #[meta = "val"] c: i8, }
函数调用
关键字参数调用
可以从 Struct 解析出各个字段,通过解析各个字段的 attrs 属性,并对 attrs 进行遍历,使用 attr.parse_args()?
即可解析出对应的关键字参数,咱们以前面的代码为例:
#[derive(Custom)]
struct Demo {
#[args(name = "val")]
b: i8,
}
对应的解析代码为:
use proc_macro::TokenStream;
use quote::quote;
use syn::{self, parse, DeriveInput};
#[proc_macro_derive(Custom, attributes(args))]
pub fn derive(input: TokenStream) -> TokenStream {
let ast: DeriveInput = parse(input).unwrap();
let name = &ast.ident;
let mut debug_fields = vec![];
if let syn::Data::Struct(ds) = ast.data {
if let syn::Fields::Named(fields) = ds.fields {
for field in fields.named.iter() {
let field_name = field.ident.clone().unwrap();
if let Some(attr) = field.attrs.clone().iter().next() {
match parse_args_attr_value(&attr) {
Ok(v) => {
// TODO
},
Err(err) => return err.to_compile_error().into(),
}
}
}
}
}
let tokens = quote!{
};
tokens.into()
}
fn parse_args_attr_value(attr: &syn::Attribute) -> Result<Option<syn::LitStr>, syn::Error> {
if let Some(seg) = attr.path.segments.first() {
if seg.ident == "args" {
let args = attr.parse_args()?;
if let syn::Meta::NameValue(values) = args {
let arg_name = &values.path.segments.first().unwrap().ident;
if arg_name == "name" {
if let syn::Lit::Str(name) = values.lit {
return Ok(Some(name));
}
} else {
return Err(syn::Error::new(attr.bracket_token.span, "expected `args(name = \"...\")`".to_owned()));
}
}
}
}
return Ok(None)
}
直接赋值
直接赋值类似于上面的关键字解析调用,但是对应的 attr.parse_args
需要替换为 attr.parse_meta
,比如解析:
#[derive(Custom)]
struct Demo {
#[meta = "val"]
c: i8,
}
对应的代码如下
use proc_macro::TokenStream;
use quote::quote;
use syn::{self, parse, DeriveInput};
#[proc_macro_derive(Custom, attributes(args))]
pub fn derive(input: TokenStream) -> TokenStream {
let ast: DeriveInput = parse(input).unwrap();
let name = &ast.ident;
let mut debug_fields = vec![];
if let syn::Data::Struct(ds) = ast.data {
if let syn::Fields::Named(fields) = ds.fields {
for field in fields.named.iter() {
let field_name = field.ident.clone().unwrap();
if let Some(attr) = field.attrs.clone().iter().next() {
match parse_meta_attr_value(&attr) {
Ok(v) => {
// TODO
},
Err(err) => return err.to_compile_error().into(),
}
}
}
}
}
let tokens = quote!{
};
tokens.into()
}
fn parse_meta_attr_value(attr: &syn::Attribute) -> Result<Option<syn::LitStr>, syn::Error> {
if let Some(seg) = attr.path.segments.first() {
if seg.ident == "meta" {
let args = attr.parse_meta()?;
if let syn::Meta::NameValue(values) = args {
let arg_name = &values.path.segments.first().unwrap().ident;
if arg_name == "meta" {
if let syn::Lit::Str(name) = values.lit {
return Ok(Some(name));
}
} else {
return Err(syn::Error::new(attr.bracket_token.span, "expected `meta = \"...\"`".to_owned()));
}
}
}
}
return Ok(None)
}