Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion faux_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub fn when(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
receiver,
method,
args,
turbofish,
..
}) => {
let when = quote::format_ident!("_when_{}", method);
Expand All @@ -69,7 +70,8 @@ pub fn when(input: proc_macro::TokenStream) -> proc_macro::TokenStream {

match args {
Err(e) => e.write_errors().into(),
Ok(args) => TokenStream::from(quote!({ #receiver.#when().with_args((#(#args,)*)) }))
Ok(args) if args.is_empty() => { TokenStream::from(quote!({ #receiver.#when #turbofish() }))}
Ok(args) => { TokenStream::from(quote!({ #receiver.#when #turbofish().with_args((#(#args,)*)) }))}
}
}
expr => darling::Error::custom("faux::when! only accepts arguments in the format of: `when!(receiver.method)` or `receiver.method(args...)`")
Expand Down
63 changes: 56 additions & 7 deletions faux_macros/src/methods/morphed.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{methods::receiver::Receiver, self_type::SelfType};
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{spanned::Spanned, PathArguments, Type, TypePath};
use syn::{spanned::Spanned, Generics, Ident, PathArguments, Type, TypePath};

pub struct Signature<'a> {
name: &'a syn::Ident,
Expand All @@ -14,6 +14,7 @@ pub struct Signature<'a> {

pub struct MethodData<'a> {
receiver: Receiver,
generics: syn::Generics,
arg_types: Vec<WhenArg<'a>>,
is_private: bool,
}
Expand Down Expand Up @@ -89,6 +90,7 @@ impl<'a> Signature<'a> {
vis: &syn::Visibility,
) -> Signature<'a> {
let receiver = Receiver::from_signature(signature);
let generics = signature.generics.clone();

let output = match &signature.output {
syn::ReturnType::Default => None,
Expand All @@ -110,6 +112,7 @@ impl<'a> Signature<'a> {

MethodData {
receiver,
generics,
arg_types,
is_private: trait_path.is_none() && *vis == syn::Visibility::Inherited,
}
Expand Down Expand Up @@ -144,9 +147,17 @@ impl<'a> Signature<'a> {
let name = &self.name;
let args = &self.args;

let generics = self
.method_data
.as_ref()
.map(|method_data| method_data.generics.clone());

let generic_idents = generic_type_idents(generics);
let turbofish = turbofish(&generic_idents);

let proxy = match self.trait_path {
None => quote! { <#real_ty>::#name },
Some(path) => quote! { <#real_ty as #path>::#name },
None => quote! { <#real_ty>::#name #turbofish },
Some(path) => quote! { <#real_ty as #path>::#name #turbofish },
};

let real_self_arg = self.method_data.as_ref().map(|_| {
Expand Down Expand Up @@ -204,10 +215,16 @@ impl<'a> Signature<'a> {
};

let fn_name = name.to_string();
let mut generics_str = generic_idents
.into_iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(",");
generics_str.retain(|c| !c.is_whitespace());

quote! {
unsafe {
match _maybe_faux_faux.call_stub(<Self>::#faux_ident, #fn_name, #args) {
match _maybe_faux_faux.call_stub(<Self>::#faux_ident #turbofish, #fn_name, #args, #generics_str) {
std::result::Result::Ok(o) => o,
std::result::Result::Err(e) => panic!("{}", e),
}
Expand Down Expand Up @@ -332,6 +349,7 @@ impl<'a> MethodData<'a> {
let MethodData {
arg_types,
receiver,
generics,
..
} = self;
let receiver_ty = &receiver.ty;
Expand All @@ -345,11 +363,23 @@ impl<'a> MethodData<'a> {
let output = output.unwrap_or(&empty);
let name_str = name.to_string();

let generics_contents = if generics.params.is_empty() {
None
} else {
let params = &generics.params;
Some(quote! { , #params })
};

let generics_where_clause = &generics.where_clause;

let generic_idents = generic_type_idents(Some(generics.clone()));
let turbofish = turbofish(&generic_idents);

let when_method = syn::parse_quote! {
pub fn #when_ident<'m>(&'m mut self) -> faux::When<'m, #receiver_ty, (#(#arg_types),*), #output, faux::matcher::AnyInvocation> {
pub fn #when_ident<'m #generics_contents>(&'m mut self) -> faux::When<'m, #receiver_ty, (#(#arg_types),*), #output, faux::matcher::AnyInvocation> #generics_where_clause {
match &mut self.0 {
faux::MaybeFaux::Faux(_maybe_faux_faux) => faux::When::new(
<Self>::#faux_ident,
<Self>::#faux_ident #turbofish,
#name_str,
_maybe_faux_faux
),
Expand All @@ -362,7 +392,7 @@ impl<'a> MethodData<'a> {
let faux_method = syn::parse_quote! {
#[allow(clippy::needless_arbitrary_self_type)]
#[allow(clippy::boxed_local)]
pub fn #faux_ident(self: #receiver_ty, _: (#(#arg_types),*)) -> #output {
pub fn #faux_ident #generics (self: #receiver_ty, _: (#(#arg_types),*)) -> #output #generics_where_clause {
panic!(#panic_message)
}
};
Expand Down Expand Up @@ -466,3 +496,22 @@ fn path_args_contains_self(path: &syn::Path, self_path: &syn::TypePath) -> bool
}
}
}

fn generic_type_idents(generics: Option<Generics>) -> Vec<Ident> {
generics
.map(|g| {
g.type_params()
.into_iter()
.map(|tp| tp.ident.clone())
.collect()
})
.unwrap_or_default()
}

fn turbofish(idents: &[Ident]) -> TokenStream {
if idents.is_empty() {
quote! {}
} else {
quote! { :: < #(#idents),* > }
}
}
26 changes: 18 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -999,11 +999,13 @@ impl Faux {
id: fn(R, I) -> O,
fn_name: &'static str,
input: I,
generics: &'static str,
) -> Result<O, InvocationError> {
let mock = self.store.get(id, fn_name)?;
let mock = self.store.get(id, fn_name, generics)?;
mock.call(input).map_err(|stub_error| InvocationError {
fn_name: mock.name(),
struct_name: self.store.struct_name,
generics,
stub_error,
})
}
Expand All @@ -1012,22 +1014,30 @@ impl Faux {
pub struct InvocationError {
struct_name: &'static str,
fn_name: &'static str,
generics: &'static str,
stub_error: mock::InvocationError,
}

impl fmt::Display for InvocationError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let generics = if self.generics.is_empty() {
String::new()
} else {
format!("<{}>", self.generics)
};
match &self.stub_error {
mock::InvocationError::NeverStubbed => write!(
f,
"`{}::{}` was called but never stubbed",
self.struct_name, self.fn_name
),
mock::InvocationError::NeverStubbed => {
write!(
f,
"`{}::{}{}` was called but never stubbed",
self.struct_name, self.fn_name, generics
)
}
mock::InvocationError::Stub(errors) => {
writeln!(
f,
"`{}::{}` had no suitable stubs. Existing stubs failed because:",
self.struct_name, self.fn_name
"`{}::{}{}` had no suitable stubs. Existing stubs failed because:",
self.struct_name, self.fn_name, generics
)?;
let mut errors = errors.iter();
if let Some(e) = errors.next() {
Expand Down
2 changes: 2 additions & 0 deletions src/mock/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl<'stub> Store<'stub> {
&self,
id: fn(R, I) -> O,
fn_name: &'static str,
generics: &'static str,
) -> Result<&Mock<'stub, I, O>, InvocationError> {
match self.stubs.get(&(id as usize)).map(|m| m.as_typed()) {
Some(mock) => {
Expand All @@ -53,6 +54,7 @@ impl<'stub> Store<'stub> {
None => Err(InvocationError {
fn_name,
struct_name: self.struct_name,
generics,
stub_error: super::InvocationError::NeverStubbed,
}),
}
Expand Down
136 changes: 136 additions & 0 deletions tests/generic_method_return.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#![allow(clippy::disallowed_names)]

pub trait MyTrait {}

#[derive(Clone, PartialEq, Debug)]
struct Entity {}
impl MyTrait for Entity {}

#[derive(Clone, PartialEq, Debug)]
struct Entity2 {}
impl MyTrait for Entity2 {}

#[faux::create]
pub struct Foo {}

#[faux::create]
pub struct Bar {}

#[faux::methods]
impl Foo {
pub fn foo<E: MyTrait>(&self, _e: E) -> E {
todo!()
}
pub fn bar<E: MyTrait, F: MyTrait>(&self, _e: E, _f: F) -> Result<E, F> {
todo!()
}
pub fn baz<E>(&self, _e: E) -> E
where
E: MyTrait,
{
todo!()
}
pub fn qux<E>(&self)
where
E: MyTrait,
{
todo!()
}
}

#[faux::create]
struct AsyncFoo {}
#[faux::methods]
impl AsyncFoo {
pub async fn foo<E: MyTrait>(&self, _e: E) -> E {
todo!()
}
pub async fn bar<E: MyTrait, F: MyTrait>(&self, _e: E, _f: F) -> Result<E, F> {
todo!()
}
pub async fn baz<E>(&self, _e: E) -> E
where
E: MyTrait,
{
todo!()
}
pub async fn qux<E>(&self)
where
E: MyTrait,
{
todo!()
}

pub async fn qux_with_arg<E>(&self, _arg: u32) -> u32
where
E: MyTrait,
{
todo!()
}
}

#[test]
fn generics() {
let mut foo = Foo::faux();
faux::when!(foo.foo).then_return(Entity {});
assert_eq!(foo.foo(Entity {}), Entity {});

let mut bar = Foo::faux();
faux::when!(bar.bar).then_return(Ok::<_, Entity>(Entity {}));
assert_eq!(bar.bar(Entity {}, Entity {}), Ok(Entity {}));

let mut baz = Foo::faux();
faux::when!(baz.baz).then_return(Entity {});
assert_eq!(baz.baz(Entity {}), Entity {});

let mut qux = Foo::faux();
faux::when!(qux.qux::<Entity>()).then(|_| {});
qux.qux::<Entity>();
}

#[test]
fn generic_tests_async() {
let mut foo: AsyncFoo = AsyncFoo::faux();
faux::when!(foo.foo).then_return(Entity {});

let mut bar = AsyncFoo::faux();
faux::when!(bar.bar).then_return(Ok::<_, Entity>(Entity {}));

let mut baz = AsyncFoo::faux();
faux::when!(baz.baz).then_return(Entity {});

let mut qux = AsyncFoo::faux();
faux::when!(qux.qux::<Entity>()).then(|_| {});

let mut qux_with_arg = AsyncFoo::faux();
faux::when!(qux_with_arg.qux_with_arg::<Entity>()).then(|_| 100);
faux::when!(qux_with_arg.qux_with_arg::<Entity>(42)).then(|_| 84);
faux::when!(qux_with_arg.qux_with_arg::<Entity>(43)).then(|_| 86);
futures::executor::block_on(async {
assert_eq!(foo.foo(Entity {}).await, Entity {});
assert_eq!(bar.bar(Entity {}, Entity {}).await, Ok(Entity {}));
assert_eq!(baz.baz(Entity {}).await, Entity {});
qux.qux::<Entity>().await;
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(42).await, 84);
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(43).await, 86);
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(50).await, 100);
});
}

#[test]
fn generic_two_different_impls() {
let mut qux_with_arg = AsyncFoo::faux();
faux::when!(qux_with_arg.qux_with_arg::<Entity>()).then(|_| 100);
faux::when!(qux_with_arg.qux_with_arg::<Entity2>()).then(|_| 200);
futures::executor::block_on(async {
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(42).await, 100);
assert_eq!(qux_with_arg.qux_with_arg::<Entity2>(42).await, 200);
});
}

#[test]
#[should_panic(expected = "`Foo::qux<E>` was called but never stubbed")]
fn unmocked_faux_panics_with_generic_information() {
let foo = Foo::faux();
foo.qux::<Entity>();
}
2 changes: 1 addition & 1 deletion tests/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ fn faux_ref_output() {
}

#[test]
#[should_panic]
#[should_panic(expected = "`Foo::get_stuff` was called but never stubbed")]
fn unmocked_faux_panics() {
let mock = Foo::faux();
mock.get_stuff();
Expand Down
3 changes: 0 additions & 3 deletions tests/when_arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ impl Foo {
}
}

#[derive(Debug)]
struct Bar(i32);

#[test]
fn no_args() {
let mut mock = Foo::faux();
Expand Down