准备

解析宏通过两个 crate 进行:

Derive 属性宏

探讨 Rust 宏系统中带属性(Attributes)的 Derive 宏的几种变体,以及如何进行解析。

属性宏的变体

  1. 函数调用

    #[derive(Custom)]
    struct Demo {
      #[attr(arg)]
      a: i8,
    }
    
  2. 关键字参数调用

    #[derive(Custom)]
    struct Demo {
      #[args(name = "val")]
      b: i8,
    }
    
  3. 直接赋值

    #[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)
}