Skip to content

Commit

Permalink
#111 Refactored inject fixture code
Browse files Browse the repository at this point in the history
  • Loading branch information
la10736 committed Apr 24, 2021
1 parent 8337423 commit 648d74d
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 152 deletions.
7 changes: 4 additions & 3 deletions src/render/fixture.rs
Expand Up @@ -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};
Expand Down Expand Up @@ -35,7 +35,7 @@ pub(crate) fn render<'a>(fixture: ItemFn, info: FixtureInfo) -> TokenStream {
.map(|tp| &tp.ident)
.cloned()
.collect::<Vec<_>>();
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));

Expand Down Expand Up @@ -86,7 +86,8 @@ fn render_partial_impl(
.map(|tp| &tp.ident)
.cloned()
.collect::<Vec<_>>();
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::<Vec<_>>();
Expand Down
147 changes: 147 additions & 0 deletions 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<Item = &'a FnArg>,
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<Expr>, &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<Stmt> {
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<Expr> {
Cow::Owned(parse_quote! { #ident::default() })
}

fn handling_magic_conversion_code(fixture: Cow<Expr>, arg_type: &Type) -> Expr {
parse_quote! {
{
struct __Wrap<T>(std::marker::PhantomData<T>);

trait __ViaParseDebug<'a, T> {
fn magic_conversion(&self, input: &'a str) -> T;
}

impl<'a, T> __ViaParseDebug<'a, T> for &&__Wrap<T>
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<T>
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)
}
}
}
152 changes: 4 additions & 148 deletions src/render/mod.rs
Expand Up @@ -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};
Expand All @@ -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());
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -265,151 +266,6 @@ fn trace_arguments<'a>(
}
}

fn default_fixture_resolve(ident: &Ident) -> Cow<Expr> {
Cow::Owned(parse_quote! { #ident::default() })
}

fn handling_magic_conversion_code(fixture: Cow<Expr>, arg_type: &Type) -> Expr {
parse_quote! {
{
struct __Wrap<T>(std::marker::PhantomData<T>);

trait __ViaParseDebug<'a, T> {
fn magic_conversion(&self, input: &'a str) -> T;
}

impl<'a, T> __ViaParseDebug<'a, T> for &&__Wrap<T>
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<T>
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<E: AsRef<Expr>> 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<Stmt> {
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<Item = &'a FnArg>,
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),
Expand Down
18 changes: 17 additions & 1 deletion src/utils.rs
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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<E: AsRef<Expr>> 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;
Expand Down

0 comments on commit 648d74d

Please sign in to comment.