use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse2, Block, Expr, Ident, Pat, ReturnType, Type, TypeTuple};
use crate::property_test::{options::Options, utils::Argument};
use super::{nth_field_name, struct_name};
pub(super) fn body(
block: Block,
args: &[Argument],
struct_and_impl: TokenStream,
fn_name: &Ident,
ret_ty: &ReturnType,
options: &Options,
) -> Block {
let struct_name = struct_name(fn_name);
let errors = &options.errors;
let struct_fields = args.iter().enumerate().map(|(index, arg)| {
let pat = arg.pat_ty.pat.as_ref();
let field_name = nth_field_name(args, index);
match pat {
Pat::Ident(i) => match i.mutability {
Some(mutability) => quote!(#mutability #field_name,),
None => quote!(#field_name,),
},
_ => quote!(#field_name: #pat,),
}
});
let struct_pattern = quote! {
#struct_name { #(#struct_fields)* }
};
let handle_result = handle_result(ret_ty);
let config = make_config(options.config.as_ref(), fn_name);
let tokens = quote! ( {
#(#errors)*
#struct_and_impl
#config
let mut runner = ::proptest::test_runner::TestRunner::new(config);
let result = runner.run(
&::proptest::strategy::Strategy::prop_map(::proptest::prelude::any::<#struct_name>(), |values| {
::proptest::sugar::NamedArguments(stringify!(#struct_name), values)
}),
|::proptest::sugar::NamedArguments(_, #struct_pattern)| {
let result = #block;
#handle_result
},
);
match result {
Ok(()) => {}
Err(e) => panic!("{}", e),
}
} );
parse2(tokens).unwrap()
}
fn handle_result(ret_ty: &ReturnType) -> TokenStream {
let default_body = || quote! { Ok(result) };
let result_body = || quote! { result };
match ret_ty {
ReturnType::Default => default_body(),
ReturnType::Type(_, ty) => match ty.as_ref() {
Type::Tuple(TypeTuple { elems, .. }) if elems.is_empty() => {
default_body()
}
_ => result_body(),
},
}
}
fn make_config(config: Option<&Expr>, fn_name: &Ident) -> TokenStream {
let trailing = match config {
None => quote! { ::proptest::test_runner::Config::default() },
Some(config) => config.to_token_stream(),
};
quote! {
let config = ::proptest::test_runner::Config {
test_name: Some(concat!(module_path!(), "::", stringify!(#fn_name))),
source_file: Some(file!()),
..#trailing
};
}
}