Skip to content

Instantly share code, notes, and snippets.

@yutakahashi114
Last active August 23, 2020 12:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yutakahashi114/3dc523e2dee7610efad5571bf12eedb5 to your computer and use it in GitHub Desktop.
Save yutakahashi114/3dc523e2dee7610efad5571bf12eedb5 to your computer and use it in GitHub Desktop.
extern crate proc_macro;
extern crate syn;
use anyhow::{anyhow, Result};
use syn::Data;
use syn::Fields;
#[macro_use]
extern crate quote;
use proc_macro::TokenStream;
#[proc_macro_derive(Mapper)] // #[proc_macro_derive(Mapper)] を書くことで、#[derive(Mapper)] を指定した時にderive_mapper() 関数が呼び出されるようになる
pub fn derive_mapper(input: TokenStream) -> TokenStream { // input: TokenStream に #[derive(Mapper)] を指定した型情報が入ってくる
let input = syn::parse_macro_input!(input as syn::DeriveInput); // inputをparseする
impl_mapper_macro(&input).unwrap()
}
fn impl_mapper_macro(input: &syn::DeriveInput) -> Result<TokenStream> {
let data_struct = match &input.data { // #[derive(Mapper)] を指定した型がstructであるかを判定
Data::Struct(data_struct) => data_struct,
Data::Enum(_) => Err(anyhow!("invalid type: Enum"))?,
Data::Union(_) => Err(anyhow!("invalid type: Union"))?,
};
let fields_named = match &data_struct.fields { // structのfield名一覧を取得する
Fields::Named(fields_named) => fields_named,
Fields::Unnamed(_) => Err(anyhow!("invalid type: Unnamed"))?,
Fields::Unit => Err(anyhow!("invalid type: Unit"))?,
};
let to_field_value_token_streams: Vec<proc_macro2::TokenStream> = fields_named
.named
.iter()
.enumerate()
.map(|(i, field)| {
let name = match &field.ident {
Some(ident) => syn::Member::Named(ident.clone()),
None => syn::Member::Unnamed(i.into()),
};                                  // Converterを実装した型のto_field_valueを呼び、FieldValueに変換するためのmacroを書く
return quote! {
result.insert(stringify!(#name).to_string(), rust_map_macro::mapper::Converter::to_field_value(&self.#name));
};
})
.collect();
let to_primitive_token_streams: Vec<proc_macro2::TokenStream> = fields_named
.named
.iter()
.enumerate()
.map(|(i, field)| {
let name = match &field.ident {
Some(ident) => syn::Member::Named(ident.clone()),
None => syn::Member::Unnamed(i.into()),
};
let ty = &field.ty;
return quote! { // Converterを実装した型のto_primitiveを呼び、primitive型に変換するためのmacroを書く
let mut #name: Option<#ty> = None;
if let Some(value) = __optional_map__.get_mut(stringify!(#name)) {
if let Some(value) = std::mem::replace(value, None) {
#name = Some(rust_map_macro::mapper::Converter::to_primitive(value)?);
} else {
return Err(anyhow::anyhow!("invalid type: {}", stringify!(#ty)));
}
}
let #name = #name.ok_or(anyhow::anyhow!("invalid type: {}", stringify!(#ty)))?;
};
})
.collect();
let to_struct_token_streams: Vec<proc_macro2::TokenStream> = fields_named
.named
.iter()
.enumerate()
.map(|(i, field)| {
let name = match &field.ident {
Some(ident) => syn::Member::Named(ident.clone()),
None => syn::Member::Unnamed(i.into()),
};
return quote! { // 変換後の変数を構造体にまとめるためのmacroを書く
#name,
};
})
.collect();
let name = &input.ident; // 構造体名
let (im_generics, ty_generics, _) = input.generics.split_for_impl(); // 構造体のgenerics引数
Ok(quote! {
impl#im_generics Mapper for #name#ty_generics { // 構造体にMapperを実装する
fn to_map(&self) -> std::collections::HashMap<String, rust_map_macro::mapper::FieldValue> {
let mut result = std::collections::HashMap::new();
#(#to_field_value_token_streams)* // 上で書いたmacro(to_field_value_token_streams)をバインドする
result
}
fn from_map(__map__: std::collections::HashMap<String, rust_map_macro::mapper::FieldValue>) -> anyhow::Result<Self> {
let mut __optional_map__ = std::collections::HashMap::with_capacity(__map__.len());
for (key, val) in __map__ {
__optional_map__.insert(key, Some(val));
}
#(#to_primitive_token_streams)* // 上で書いたmacro(to_primitive_token_streams)をバインドする
Ok(#name { #(#to_struct_token_streams)* }) // 上で書いたmacro(to_struct_token_streams)をバインドする
}
}
}
.into())
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment