crate::ix!();
#[derive(Getters)]
#[getset(get = "pub")]
pub struct FieldGenerationTokens {
random: TokenStream2,
uniform: TokenStream2,
#[cfg(feature="env")]
random_env: TokenStream2,
#[cfg(feature="env")]
uniform_env: TokenStream2,
provider_types: Vec<Type>,
rand_bound: TokenStream2,
}
#[derive(Getters)]
#[getset(get = "pub")]
pub struct ParsedFieldSpec {
ident: Option<Ident>,
ty: Type,
is_option: bool,
inner_ty: Option<Type>,
some_prob: f64,
min_max: (Option<f64>, Option<f64>),
}
impl ParsedFieldSpec {
pub fn from_syn_field(field: &Field) -> Self {
let ty = field.ty.clone();
let (is_option, inner_ty) = match is_option_type(&ty) {
Some(inner) => (true, Some(inner)),
None => (false, None),
};
ParsedFieldSpec {
ident: field.ident.clone(),
ty: ty.clone(),
is_option,
inner_ty: inner_ty.cloned(),
some_prob: parse_some_probability(&field.attrs).unwrap_or(0.5),
min_max: parse_min_max(&field.attrs),
}
}
pub fn build_generation_tokens(&self) -> FieldGenerationTokens {
if self.is_option {
self.make_tokens_for_option_field()
} else {
self.make_tokens_for_plain_field()
}
}
fn make_tokens_for_plain_field(&self) -> FieldGenerationTokens {
let ty = &self.ty;
let clamp = clamp_code(ty, self.min_max.0, self.min_max.1);
FieldGenerationTokens {
random: quote!({ let mut v = <#ty>::random(); #clamp v }),
uniform: quote!({ let mut v = <#ty>::uniform(); #clamp v }),
#[cfg(feature="env")]
random_env: quote!({ let mut v = <#ty>::random_with_env::<ENV>(); #clamp v }),
#[cfg(feature="env")]
uniform_env: quote!({ let mut v = <#ty>::random_uniform_with_env::<ENV>(); #clamp v }),
provider_types: vec![ty.clone()],
rand_bound: quote! { #ty : RandConstruct },
}
}
fn make_tokens_for_option_field(&self) -> FieldGenerationTokens {
let inner = self.inner_ty.as_ref().expect("inner_ty exists");
let clamp = clamp_code(inner, self.min_max.0, self.min_max.1);
let build_branch = |expr: TokenStream2, prob: TokenStream2| quote! {
{
if rand::random::<f64>() < #prob {
let mut v = #expr;
#clamp
Some(v)
} else { None }
}
};
let prob_lit = proc_macro2::Literal::f64_unsuffixed(self.some_prob);
FieldGenerationTokens {
random: build_branch(quote! { <#inner>::random() }, quote! { #prob_lit }),
uniform: build_branch(quote! { <#inner>::uniform() }, quote! { 0.5 }),
#[cfg(feature="env")]
random_env: build_branch(quote! { <#inner>::random_with_env::<ENV>() }, quote! { #prob_lit }),
#[cfg(feature="env")]
uniform_env: build_branch(quote! { <#inner>::random_uniform_with_env::<ENV>() }, quote! { 0.5 }),
provider_types: vec![inner.clone()],
rand_bound: quote! { #inner : RandConstruct },
}
}
}
pub fn provider_types_contain_primitive(provider_types: &[Type]) -> bool {
contains_primitive_type(provider_types)
}
#[cfg(test)]
mod parsed_field_spec_tests {
use super::*;
use quote::quote;
use syn::{parse_quote, Field};
fn parse_field(ts: proc_macro2::TokenStream) -> Field {
let st: syn::ItemStruct = syn::parse2(quote! { struct _Tmp { #ts } }).unwrap();
match st.fields {
syn::Fields::Named(named) => named.named.first().unwrap().clone(),
_ => unreachable!(),
}
}
#[test]
fn parsed_field_spec_plain() {
let field = parse_field(parse_quote!( num: u16 ));
let spec = ParsedFieldSpec::from_syn_field(&field);
assert_eq!(spec.ident().as_ref().unwrap(), "num");
assert!(!spec.is_option());
assert!(spec.inner_ty().is_none());
}
#[test]
fn parsed_field_spec_option() {
let field = parse_field(parse_quote!( #[rand_construct(psome = 0.8)] flag: Option<bool> ));
let spec = ParsedFieldSpec::from_syn_field(&field);
assert!(spec.is_option());
assert_eq!(*spec.some_prob(), 0.8);
assert_eq!(spec.inner_ty().as_ref().unwrap().to_token_stream().to_string(), "bool");
}
#[test]
fn field_generation_tokens_plain() {
let field = parse_field(parse_quote!( value: i32 ));
let spec = ParsedFieldSpec::from_syn_field(&field);
let tokens = spec.build_generation_tokens();
assert_eq!(tokens.provider_types().len(), 1);
assert_eq!(tokens.provider_types()[0].to_token_stream().to_string(), "i32");
assert_eq!(tokens.rand_bound().to_string(), "i32 : RandConstruct");
}
#[test]
fn field_generation_tokens_option() {
let field = parse_field(parse_quote!( flag: Option<u8> ));
let spec = ParsedFieldSpec::from_syn_field(&field);
let tokens = spec.build_generation_tokens();
assert_eq!(tokens.provider_types().len(), 1);
assert_eq!(tokens.provider_types()[0].to_token_stream().to_string(), "u8");
#[cfg(feature="env")]
{
let all = format!(
"{}{}{}{}",
tokens.random().to_string(),
tokens.uniform().to_string(),
tokens.random_env().to_string(),
tokens.uniform_env().to_string()
);
assert!(all.contains("Some"));
}
#[cfg(not(feature="env"))]
{
let all = format!(
"{}{}",
tokens.random().to_string(),
tokens.uniform().to_string(),
);
assert!(all.contains("Some"));
}
}
#[test]
fn provider_types_contain_primitive_predicate() {
let vec1: Vec<Type> = vec![parse_quote!(u8), parse_quote!(MyType)];
assert!(provider_types_contain_primitive(&vec1));
let vec2: Vec<Type> = vec![parse_quote!(Foo), parse_quote!(Bar)];
assert!(!provider_types_contain_primitive(&vec2));
}
}