extern crate proc_macro;
extern crate syn;
extern crate quote;
extern crate heck;
use std::iter::FromIterator;
use proc_macro::TokenStream;
use syn::{
DeriveInput, Ident, Type, Attribute, Fields, Meta,
export::{Span, TokenStream2}
};
use quote::{quote, ToTokens};
use heck::CamelCase;
#[proc_macro_derive(FieldType, attributes(field_types, field_type, field_types_derive, field_type_derive))]
pub fn field_type(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
let (vis, ty, generics) = (&ast.vis, &ast.ident, &ast.generics);
let enum_ty = Ident::new(&(ty.to_string() + "FieldType"), Span::call_site());
let derive = get_enum_derive(&ast.attrs, &["field_types_derive", "field_type_derive"], quote! {});
let fields = filter_fields(match ast.data {
syn::Data::Struct(ref s) => &s.fields,
_ => panic!("FieldType can only be derived for structures"),
}, "field_type");
if fields.is_empty() {
panic!("FieldType can only be derived for non-empty structures");
}
let field_type_variants = fields.iter()
.map(|(_, field_ty, variant_ident)| {
quote! {
#variant_ident(#field_ty)
}
});
let field_type_constructs = fields.iter()
.map(|(field_ident, _, variant_ident)| {
quote! {
#enum_ty::#variant_ident(#field_ident)
}
});
let from_field_type_constructs = field_type_constructs.clone();
let fields_idents = fields.iter()
.map(|(field_ident, _, _)| {
quote! {
#field_ident
}
});
let destructuring = quote! { #ty { #(#fields_idents,)* .. } };
let fields_count = fields.len();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let converter = if generics.params.is_empty() {
quote! {
impl From<#ty> for [#enum_ty; #fields_count] {
fn from(source: #ty) -> Self {
let #destructuring = source;
[#(#from_field_type_constructs),*]
}
}
}
} else {
quote! {
impl #impl_generics Into<[#enum_ty #ty_generics; #fields_count]> for #ty #ty_generics
#where_clause
{
fn into(self) -> [#enum_ty #ty_generics; #fields_count] {
let #destructuring = self;
[#(#from_field_type_constructs),*]
}
}
}
};
let tokens = quote! {
#derive
#vis enum #enum_ty #generics
#where_clause
{
#(#field_type_variants),*
}
#converter
impl #impl_generics #ty #ty_generics
#where_clause
{
#vis fn into_field_type_array(self) -> [#enum_ty #ty_generics; #fields_count] {
let #destructuring = self;
[#(#field_type_constructs),*]
}
}
};
tokens.into()
}
#[proc_macro_derive(FieldName, attributes(field_types, field_name, field_types_derive, field_name_derive))]
pub fn field_name(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
let (vis, ty, generics) = (&ast.vis, &ast.ident, &ast.generics);
let enum_ty = Ident::new(&(ty.to_string() + "FieldName"), Span::call_site());
let derive = get_enum_derive(&ast.attrs, &["field_types_derive", "field_name_derive"],
quote! { #[derive(Debug, PartialEq, Eq, Clone, Copy)] });
let fields = filter_fields(match ast.data {
syn::Data::Struct(ref s) => &s.fields,
_ => panic!("FieldName can only be derived for structures"),
}, "field_name");
if fields.is_empty() {
panic!("FieldName can only be derived for non-empty structures");
}
let field_name_variants = fields.iter()
.map(|(_, _, variant_ident)| {
quote! {
#variant_ident
}
});
let field_name_to_strs = fields.iter()
.map(|(field_ident, _, variant_ident)| {
let field_name = field_ident.to_string();
quote! {
#enum_ty::#variant_ident => #field_name
}
});
let field_name_by_strs = fields.iter()
.map(|(_, _, variant_ident)| {
quote! {
if #enum_ty::#variant_ident.name() == name { return Some(#enum_ty::#variant_ident) }
}
});
let field_name_constructs = fields.iter()
.map(|(_, _, variant_ident)| {
quote! {
#enum_ty::#variant_ident
}
});
let from_field_name_constructs = field_name_constructs.clone();
let fields_count = fields.len();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let from_lifetime = quote! { 'field_name_from_lifetime__ };
let mut impl_generics_tokens = TokenStream2::new();
impl_generics.to_tokens(&mut impl_generics_tokens);
if impl_generics_tokens.is_empty() {
impl_generics_tokens = quote! { <#from_lifetime> };
} else {
let mut tokens: Vec<_> = quote! { #from_lifetime, }.into_iter().collect();
let mut gen_iter = impl_generics_tokens.into_iter();
if let Some(token) = gen_iter.next() {
tokens.insert(0, token);
}
tokens.extend(gen_iter);
impl_generics_tokens = TokenStream2::from_iter(tokens);
}
let tokens = quote! {
#derive
#vis enum #enum_ty {
#(#field_name_variants),*
}
impl #enum_ty {
#vis fn name(&self) -> &'static str {
match *self {
#(#field_name_to_strs),*
}
}
#vis fn by_name(name: &str) -> Option<Self> {
#(#field_name_by_strs)*
None
}
}
impl #impl_generics #ty #ty_generics
#where_clause
{
#vis fn as_field_name_array() -> [#enum_ty; #fields_count] {
[#(#field_name_constructs),*]
}
}
impl #impl_generics_tokens From<& #from_lifetime #ty #ty_generics> for [#enum_ty; #fields_count] {
fn from(_source: & #from_lifetime #ty #ty_generics) -> Self {
[#(#from_field_name_constructs),*]
}
}
};
tokens.into()
}
fn get_enum_derive(attrs: &[Attribute], derive_attr_names: &[&str], default: TokenStream2) -> TokenStream2 {
attrs.iter()
.filter_map(|attr| attr.interpret_meta()
.and_then(|meta| {
for attr_name in derive_attr_names {
if meta.name() == attr_name {
if let Meta::List(mut meta_list) = meta {
meta_list.ident = Ident::new("derive", Span::call_site());
return Some(meta_list);
}
}
}
None
})
)
.next()
.map(|meta_list| quote! { #[#meta_list] })
.unwrap_or(default)
}
fn filter_fields(fields: &Fields, skip_attr_name: &str) -> Vec<(Ident, Type, Ident)> {
fields.iter()
.filter_map(|field| {
if field.attrs.iter()
.find(|attr| has_skip_attr(attr, &["field_types", skip_attr_name]))
.is_none() && field.ident.is_some()
{
let field_ty = field.ty.clone();
let field_ident = field.ident.as_ref().unwrap().clone();
let field_name = field.ident.as_ref().unwrap().to_string();
let variant_ident = Ident::new(&field_name.to_camel_case(), Span::call_site());
Some((field_ident, field_ty, variant_ident))
} else {
None
}
})
.collect::<Vec<_>>()
}
fn has_skip_attr(attr: &Attribute, attr_names: &[&str]) -> bool {
attr.interpret_meta()
.and_then(|meta| {
for attr_name in attr_names {
if meta.name() == attr_name {
return Some(meta);
}
}
None
})
.map(|meta| {
let value = match meta {
Meta::List(ref list) => list.nested.first()
.expect("Attribute value can't be empty")
.into_value()
.clone()
.into_token_stream()
.to_string(),
Meta::NameValue(ref name_value) => name_value.lit
.clone()
.into_token_stream()
.to_string(),
_ => panic!("Unknown attribute value, only `skip` allowed."),
};
if value != "skip" && value.find("\"skip\"").is_none() {
panic!("Unknown attribute value `{}`, only `skip` allowed.", value);
}
})
.is_some()
}