extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::quote;
use std::collections::HashSet;
use syn::{parse_macro_input, Data, DeriveInput, Error, Field, Fields};
#[proc_macro_derive(FromTuple)]
pub fn from_tuple(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
if let Data::Struct(data) = &input.data {
if let Err(error) = verify_unique_field_types(&data.fields) {
return error.to_compile_error().into();
}
let mut impls = Vec::new();
permute(&data.fields, |fields| {
impls.push(impl_from_tuple(fields, &input))
});
quote! { #(#impls)* }
} else {
Error::new_spanned(input, "FromTuple currently only supports Struct").to_compile_error()
}
.into()
}
fn impl_from_tuple(fields: &[&Field], data: &DeriveInput) -> TokenStream2 {
let struct_ident = &data.ident;
let dvars = (0..fields.len())
.map(|i| Ident::new(&format!("d{}", i), Span::call_site()))
.collect::<Vec<_>>();
let idents = fields.iter().map(|&f| f.ident.as_ref());
let types = fields.iter().map(|&f| &f.ty);
let tuple_type = quote! { (#(#types),*) };
let destructed = quote! { (#(#dvars),*) };
quote! {
impl From<#tuple_type> for #struct_ident {
#[inline]
fn from(tuple: #tuple_type) -> Self {
let #destructed = tuple;
Self {
#(#idents: #dvars),*
}
}
}
}
}
fn verify_unique_field_types<'a>(fields: &syn::Fields) -> syn::Result<()> {
let mut seen = HashSet::new();
let mut error = None;
for field in fields {
if !seen.insert(field.ty.clone()) {
let new_error = Error::new_spanned(
field,
"Field types must be unique in a struct deriving `FromTuple`",
);
match error {
None => error = Some(new_error),
Some(ref mut error) => error.combine(new_error),
}
}
}
match error {
None => Ok(()),
Some(error) => Err(error),
}
}
fn permute<F>(fields: &Fields, mut callback: F)
where
F: FnMut(&[&Field]),
{
let mut data = fields.iter().collect::<Vec<_>>();
callback(&data);
let mut idx = 0;
let mut stack = vec![0; data.len()];
while idx < data.len() {
if stack[idx] >= idx {
stack[idx] = 0;
idx += 1;
} else {
if idx % 2 == 0 {
data.swap(0, idx);
} else {
data.swap(stack[idx], idx);
}
stack[idx] += 1;
idx = 0;
callback(&data);
}
}
}