extern crate syn;
use quote::*;
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_error::{abort, proc_macro_error};
use syn::{
punctuated::Punctuated, token::Comma, Field, GenericParam, Generics, Ident, Item, TraitBound,
TraitBoundModifier, TypeParamBound,
};
fn do_enum_gen(i: usize, var_id: &Ident, field_data: &Punctuated<Field, Comma>) -> TokenStream2 {
let mut named_fields = false;
let names: Vec<TokenStream2> = field_data
.iter()
.map(|field_gen| {
let field_name = &field_gen.ident;
let field_type = &field_gen.ty;
if let Some(field_name) = field_name {
named_fields = true;
quote! {
#field_name: RandomVariant::random_variant(rng)
}
} else {
quote! {
<#field_type>::random_variant(rng)
}
}
})
.collect();
let enum_gen = if !named_fields {
quote! {
#i => {
Self :: #var_id (
#( #names ),*
)
},
}
} else {
quote! {
#i => {
Self :: #var_id {
#( #names ),*
}
},
}
};
enum_gen
}
fn do_bound_gen(generics: &Generics) -> Generics {
let mut generics = generics.clone();
fn make_bound(name: &str) -> TypeParamBound {
TypeParamBound::Trait(TraitBound {
paren_token: None,
modifier: TraitBoundModifier::None,
lifetimes: None,
path: Ident::new(name, Span::call_site()).into(),
})
}
let everyvariant_bound = make_bound("RandomVariant");
for param in &mut generics.params {
match param {
GenericParam::Type(some_type) => {
some_type.bounds.push(everyvariant_bound.clone());
}
_ => {}
}
}
generics
}
#[proc_macro_error]
#[proc_macro_derive(RandomVariant)]
pub fn derive_every_variant(item: TokenStream) -> TokenStream {
let item: syn::Item = syn::parse(item).expect("Failed to parse input item ");
match item {
Item::Enum(ref it) => {
let _attrs = &it.attrs;
let name = &it.ident;
let variants = &it.variants;
let mut variant_generators = Vec::new();
for (i, var) in variants.iter().enumerate() {
let varid = &var.ident;
match var.fields {
syn::Fields::Unnamed(ref fields) => {
let variant_gen = do_enum_gen(i, &varid, &fields.unnamed);
variant_generators.push(variant_gen);
}
syn::Fields::Unit => {
let variant_gen = quote! {
#i => Self::#varid,
};
variant_generators.push(variant_gen);
}
syn::Fields::Named(ref fields) => {
let variant_gen = do_enum_gen(i, &varid, &fields.named);
variant_generators.push(variant_gen);
}
}
}
let bounded_generics = do_bound_gen(&it.generics);
let (impl_generics, ty_generics, where_clause) = bounded_generics.split_for_impl();
let number_variants: usize = variant_generators.len();
let out = quote! {
impl #impl_generics RandomVariant for #name #ty_generics #where_clause {
fn random_variant<R: random_variant::rand::Rng>(rng: &mut R) -> Self {
let u: usize = rng.gen_range(0..#number_variants);
match u {
#( #variant_generators )*
_ => {
panic!("RandomVariant macro calculated to many variants ");
}
}
}
}
};
out.into()
}
Item::Struct(ref it) => {
let name = &it.ident;
#[allow(unused_assignments)]
let mut structgen = quote! {};
let mut use_brackets = false;
if it.fields.iter().any(|f| f.ident.is_none()) {
let mut variants: Vec<TokenStream2> = Vec::new();
for field in it.fields.iter() {
if let syn::Type::Path(_path) = field.ty.clone() {
variants.push(quote! {
RandomVariant::random_variant(rng),
})
} else {
abort!(field, "Ident is missing ");
}
}
structgen = quote!(
#( #variants )*
)
} else {
let fieldgens: Vec<Ident> = it
.fields
.iter()
.filter_map(|field| field.ident.clone())
.collect();
let mut variants: Vec<TokenStream2> = Vec::new();
for field in fieldgens {
variants.push(quote! {
#field: RandomVariant::random_variant(rng),
});
}
use_brackets = true;
structgen = quote!(
#( #variants )*
)
}
let bounded_generics = do_bound_gen(&it.generics);
let (impl_generics, ty_generics, where_clause) = bounded_generics.split_for_impl();
let out = if use_brackets {
quote! {
impl #impl_generics RandomVariant for #name #ty_generics #where_clause {
fn random_variant<R: random_variant::rand::Rng>(rng: &mut R) -> Self {
Self {
#structgen
}
}
}
}
} else {
quote! {
impl #impl_generics RandomVariant for #name #ty_generics #where_clause {
fn random_variant<R: random_variant::rand::Rng>(rng: &mut R) -> Self {
Self (
#structgen
)
}
}
}
};
out.into()
}
_ => {
abort!(item, "Only has an effect on enums and structs ");
}
}
}