From 648d74d6085fbd3cf82f32b9d88c480afd9a2a5c Mon Sep 17 00:00:00 2001 From: michele Date: Sat, 24 Apr 2021 11:09:39 +0200 Subject: [PATCH] #111 Refactored inject fixture code --- src/render/fixture.rs | 7 +- src/render/inject.rs | 147 ++++++++++++++++++++++++++++++++++++++++ src/render/mod.rs | 152 ++---------------------------------------- src/utils.rs | 18 ++++- 4 files changed, 172 insertions(+), 152 deletions(-) create mode 100644 src/render/inject.rs diff --git a/src/render/fixture.rs b/src/render/fixture.rs index 3366ae4..abe72f9 100644 --- a/src/render/fixture.rs +++ b/src/render/fixture.rs @@ -3,7 +3,7 @@ use syn::{parse_quote, Ident, ItemFn}; use quote::quote; -use super::{generics_clean_up, render_exec_call, resolve_aruments}; +use super::{generics_clean_up, inject, render_exec_call}; use crate::parse::fixture::FixtureInfo; use crate::resolver::{self, Resolver}; use crate::utils::{fn_args, fn_args_idents}; @@ -35,7 +35,7 @@ pub(crate) fn render<'a>(fixture: ItemFn, info: FixtureInfo) -> TokenStream { .map(|tp| &tp.ident) .cloned() .collect::>(); - let inject = resolve_aruments(fixture.sig.inputs.iter(), &resolver, &generics_idents); + let inject = inject::resolve_aruments(fixture.sig.inputs.iter(), &resolver, &generics_idents); let partials = (1..=orig_args.len()).map(|n| render_partial_impl(&fixture, n, &resolver, &info)); @@ -86,7 +86,8 @@ fn render_partial_impl( .map(|tp| &tp.ident) .cloned() .collect::>(); - let inject = resolve_aruments(fixture.sig.inputs.iter().skip(n), resolver, &genercs_idents); + let inject = + inject::resolve_aruments(fixture.sig.inputs.iter().skip(n), resolver, &genercs_idents); let sign_args = fn_args(fixture).take(n); let fixture_args = fn_args_idents(fixture).cloned().collect::>(); diff --git a/src/render/inject.rs b/src/render/inject.rs new file mode 100644 index 0000000..fab7a14 --- /dev/null +++ b/src/render/inject.rs @@ -0,0 +1,147 @@ +use std::borrow::Cow; + +use proc_macro2::TokenStream; +use quote::quote; +use syn::{parse_quote, Expr, FnArg, Ident, Stmt, Type}; + +use crate::{ + refident::{MaybeIdent, MaybeType}, + resolver::Resolver, + utils::IsLiteralExpression, +}; + +pub(crate) fn resolve_aruments<'a>( + args: impl Iterator, + resolver: &impl Resolver, + generic_types: &[Ident], +) -> TokenStream { + let define_vars = args.map(|arg| ArgumentResolver::new(resolver, generic_types).resolve(arg)); + quote! { + #(#define_vars)* + } +} + +struct ArgumentResolver<'resolver, 'idents, 'f, R> +where + R: Resolver + 'resolver, +{ + resolver: &'resolver R, + generic_types_names: &'idents [Ident], + magic_conversion: &'f dyn Fn(Cow, &Type) -> Expr, +} + +impl<'resolver, 'idents, 'f, R> ArgumentResolver<'resolver, 'idents, 'f, R> +where + R: Resolver + 'resolver, +{ + fn new(resolver: &'resolver R, generic_types_names: &'idents [Ident]) -> Self { + Self { + resolver, + generic_types_names, + magic_conversion: &handling_magic_conversion_code, + } + } + + fn resolve(&self, arg: &FnArg) -> Option { + let ident = arg.maybe_ident()?; + let arg_type = arg.maybe_type()?; + let fixture_name = self.fixture_name(ident); + + let mut fixture = self + .resolver + .resolve(&fixture_name) + .map(|e| e.clone()) + .unwrap_or_else(|| default_fixture_resolve(&fixture_name)); + + if fixture.is_literal() && self.type_can_be_get_from_literal_str(arg_type) { + fixture = Cow::Owned((self.magic_conversion)(fixture, arg_type)); + } + Some(parse_quote! { + let #ident = #fixture; + }) + } + + fn fixture_name<'a>(&self, ident: &'a Ident) -> Cow<'a, Ident> { + let id_str = ident.to_string(); + if id_str.starts_with("_") && !id_str.starts_with("__") { + Cow::Owned(Ident::new(&id_str[1..], ident.span())) + } else { + Cow::Borrowed(ident) + } + } + + fn type_can_be_get_from_literal_str(&self, t: &Type) -> bool { + // Check valid type to apply magic conversion + match t { + Type::ImplTrait(_) + | Type::TraitObject(_) + | Type::Infer(_) + | Type::Group(_) + | Type::Macro(_) + | Type::Never(_) + | Type::Paren(_) + | Type::Verbatim(_) + | Type::Slice(_) => return false, + _ => {} + } + match t.maybe_ident() { + Some(id) => !self.generic_types_names.contains(id), + None => true, + } + } +} + +fn default_fixture_resolve(ident: &Ident) -> Cow { + Cow::Owned(parse_quote! { #ident::default() }) +} + +fn handling_magic_conversion_code(fixture: Cow, arg_type: &Type) -> Expr { + parse_quote! { + { + struct __Wrap(std::marker::PhantomData); + + trait __ViaParseDebug<'a, T> { + fn magic_conversion(&self, input: &'a str) -> T; + } + + impl<'a, T> __ViaParseDebug<'a, T> for &&__Wrap + where + T: std::str::FromStr, + T::Err: std::fmt::Debug, + { + fn magic_conversion(&self, input: &'a str) -> T { + T::from_str(input).unwrap() + } + } + + trait __ViaParse<'a, T> { + fn magic_conversion(&self, input: &'a str) -> T; + } + + impl<'a, T> __ViaParse<'a, T> for &__Wrap + where + T: std::str::FromStr, + { + fn magic_conversion(&self, input: &'a str) -> T { + match T::from_str(input) { + Ok(v) => v, + Err(_) => { + panic!("Cannot parse '{}' to get {}", input, std::stringify!(#arg_type)); + } + } + } + } + + trait __ViaIdent<'a, T> { + fn magic_conversion(&self, input: &'a str) -> T; + } + + impl<'a> __ViaIdent<'a, &'a str> for &&__Wrap<&'a str> { + fn magic_conversion(&self, input: &'a str) -> &'a str { + input + } + } + (&&&__Wrap::<#arg_type>(std::marker::PhantomData)).magic_conversion(#fixture) + } + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index bd31c8f..0138908 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -2,7 +2,7 @@ pub(crate) mod fixture; mod test; mod wrapper; -use std::{borrow::Cow, collections::HashMap}; +use std::collections::HashMap; use syn::token::Async; use proc_macro2::{Span, TokenStream}; @@ -23,12 +23,13 @@ use crate::{ utils::attr_is, }; use crate::{ - refident::{MaybeIdent, MaybeType}, + refident::MaybeIdent, resolver::{self, Resolver}, }; use wrapper::WrapByModule; pub(crate) use fixture::render as fixture; +pub(crate) mod inject; pub(crate) fn single(mut test: ItemFn, info: RsTestInfo) -> TokenStream { let resolver = resolver::fixtures::get(info.data.fixtures()); @@ -209,7 +210,7 @@ fn single_test_case<'a>( if trace_me.len() > 0 { attributes.add_trace(format_ident!("trace")); } - let inject = resolve_aruments(args.iter(), &resolver, generic_types); + let inject = inject::resolve_aruments(args.iter(), &resolver, generic_types); let args = args .iter() .filter_map(MaybeIdent::maybe_ident) @@ -265,151 +266,6 @@ fn trace_arguments<'a>( } } -fn default_fixture_resolve(ident: &Ident) -> Cow { - Cow::Owned(parse_quote! { #ident::default() }) -} - -fn handling_magic_conversion_code(fixture: Cow, arg_type: &Type) -> Expr { - parse_quote! { - { - struct __Wrap(std::marker::PhantomData); - - trait __ViaParseDebug<'a, T> { - fn magic_conversion(&self, input: &'a str) -> T; - } - - impl<'a, T> __ViaParseDebug<'a, T> for &&__Wrap - where - T: std::str::FromStr, - T::Err: std::fmt::Debug, - { - fn magic_conversion(&self, input: &'a str) -> T { - T::from_str(input).unwrap() - } - } - - trait __ViaParse<'a, T> { - fn magic_conversion(&self, input: &'a str) -> T; - } - - impl<'a, T> __ViaParse<'a, T> for &__Wrap - where - T: std::str::FromStr, - { - fn magic_conversion(&self, input: &'a str) -> T { - match T::from_str(input) { - Ok(v) => v, - Err(_) => { - panic!("Cannot parse '{}' to get {}", input, std::stringify!(#arg_type)); - } - } - } - } - - trait __ViaIdent<'a, T> { - fn magic_conversion(&self, input: &'a str) -> T; - } - - impl<'a> __ViaIdent<'a, &'a str> for &&__Wrap<&'a str> { - fn magic_conversion(&self, input: &'a str) -> &'a str { - input - } - } - (&&&__Wrap::<#arg_type>(std::marker::PhantomData)).magic_conversion(#fixture) - } - } -} - -trait IsLiteralExpression { - fn is_literal(&self) -> bool; -} - -impl> IsLiteralExpression for E { - fn is_literal(&self) -> bool { - match self.as_ref() { - &Expr::Lit(syn::ExprLit { ref lit, .. }) => match lit { - syn::Lit::Str(_) => true, - _ => false, - }, - _ => false, - } - } -} - -trait IsExplicitType { - fn is_explicit(&self) -> bool; -} - -impl IsExplicitType for &Type { - fn is_explicit(&self) -> bool { - match self { - Type::ImplTrait(_) - | Type::TraitObject(_) - | Type::Infer(_) - | Type::Group(_) - | Type::Macro(_) - | Type::Never(_) - | Type::Paren(_) - | Type::Verbatim(_) => false, - _ => true, - } - } -} - -fn ident_contains(haystack: &[Ident], needle: &Ident) -> bool { - haystack.iter().find(|&id| id == needle).is_some() -} - -fn type_can_be_get_from_literal_str(t: &Type, generics: &[Ident]) -> bool { - if !t.is_explicit() { - return false - } - match t.maybe_ident() { - Some(id) => !ident_contains(generics, id), - None => false - } -} - -fn resolve_argument( - arg: &FnArg, - resolver: &impl Resolver, - generic_types: &[Ident], -) -> Option { - let ident = arg.maybe_ident()?; - let arg_type = arg.maybe_type()?; - let id_str = ident.to_string(); - let fixture_name = if id_str.starts_with("_") && !id_str.starts_with("__") { - Cow::Owned(Ident::new(&id_str[1..], ident.span())) - } else { - Cow::Borrowed(ident) - }; - - let mut fixture = resolver - .resolve(&fixture_name) - .map(|e| e.clone()) - .unwrap_or_else(|| default_fixture_resolve(&fixture_name)); - - if fixture.is_literal() - && type_can_be_get_from_literal_str(arg_type, generic_types) - { - fixture = Cow::Owned(handling_magic_conversion_code(fixture, arg_type)); - } - Some(parse_quote! { - let #ident = #fixture; - }) -} - -fn resolve_aruments<'a>( - args: impl Iterator, - resolver: &impl Resolver, - generic_types: &[Ident], -) -> TokenStream { - let define_vars = args.map(|arg| resolve_argument(arg, resolver, generic_types)); - quote! { - #(#define_vars)* - } -} - fn where_predicate_bounded_type(wp: &WherePredicate) -> Option<&Type> { match wp { syn::WherePredicate::Type(pt) => Some(&pt.bounded_ty), diff --git a/src/utils.rs b/src/utils.rs index 144bfba..699119f 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -3,7 +3,7 @@ use quote::format_ident; /// Contains some unsorted functions used across others modules /// -use syn::{Attribute, FnArg, Ident, ItemFn}; +use syn::{Attribute, Expr, FnArg, Ident, ItemFn}; /// Return an iterator over fn arguments items. /// @@ -35,6 +35,22 @@ pub(crate) fn attr_is(attr: &Attribute, name: &str) -> bool { attr.path.is_ident(&format_ident!("{}", name)) } +pub(crate) trait IsLiteralExpression { + fn is_literal(&self) -> bool; +} + +impl> IsLiteralExpression for E { + fn is_literal(&self) -> bool { + match self.as_ref() { + &Expr::Lit(syn::ExprLit { ref lit, .. }) => match lit { + syn::Lit::Str(_) => true, + _ => false, + }, + _ => false, + } + } +} + #[cfg(test)] mod test { use syn::parse_quote;