Skip to content

Commit eb25d4b

Browse files
authored
Merge pull request #62 from o0Ignition0o/feat/generic_method_returns
Feat: Allow generics in method return types and arguments.
2 parents 9f2956b + f26ef3d commit eb25d4b

File tree

7 files changed

+216
-20
lines changed

7 files changed

+216
-20
lines changed

faux_macros/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub fn when(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
5858
receiver,
5959
method,
6060
args,
61+
turbofish,
6162
..
6263
}) => {
6364
let when = quote::format_ident!("_when_{}", method);
@@ -69,7 +70,8 @@ pub fn when(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
6970

7071
match args {
7172
Err(e) => e.write_errors().into(),
72-
Ok(args) => TokenStream::from(quote!({ #receiver.#when().with_args((#(#args,)*)) }))
73+
Ok(args) if args.is_empty() => { TokenStream::from(quote!({ #receiver.#when #turbofish() }))}
74+
Ok(args) => { TokenStream::from(quote!({ #receiver.#when #turbofish().with_args((#(#args,)*)) }))}
7375
}
7476
}
7577
expr => darling::Error::custom("faux::when! only accepts arguments in the format of: `when!(receiver.method)` or `receiver.method(args...)`")

faux_macros/src/methods/morphed.rs

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use crate::{methods::receiver::Receiver, self_type::SelfType};
22
use proc_macro2::TokenStream;
33
use quote::{quote, ToTokens};
4-
use syn::{spanned::Spanned, PathArguments, Type, TypePath};
4+
use syn::{spanned::Spanned, Generics, Ident, PathArguments, Type, TypePath};
55

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

1515
pub struct MethodData<'a> {
1616
receiver: Receiver,
17+
generics: syn::Generics,
1718
arg_types: Vec<WhenArg<'a>>,
1819
is_private: bool,
1920
}
@@ -89,6 +90,7 @@ impl<'a> Signature<'a> {
8990
vis: &syn::Visibility,
9091
) -> Signature<'a> {
9192
let receiver = Receiver::from_signature(signature);
93+
let generics = signature.generics.clone();
9294

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

111113
MethodData {
112114
receiver,
115+
generics,
113116
arg_types,
114117
is_private: trait_path.is_none() && *vis == syn::Visibility::Inherited,
115118
}
@@ -144,9 +147,17 @@ impl<'a> Signature<'a> {
144147
let name = &self.name;
145148
let args = &self.args;
146149

150+
let generics = self
151+
.method_data
152+
.as_ref()
153+
.map(|method_data| method_data.generics.clone());
154+
155+
let generic_idents = generic_type_idents(generics);
156+
let turbofish = turbofish(&generic_idents);
157+
147158
let proxy = match self.trait_path {
148-
None => quote! { <#real_ty>::#name },
149-
Some(path) => quote! { <#real_ty as #path>::#name },
159+
None => quote! { <#real_ty>::#name #turbofish },
160+
Some(path) => quote! { <#real_ty as #path>::#name #turbofish },
150161
};
151162

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

206217
let fn_name = name.to_string();
218+
let mut generics_str = generic_idents
219+
.into_iter()
220+
.map(|i| i.to_string())
221+
.collect::<Vec<_>>()
222+
.join(",");
223+
generics_str.retain(|c| !c.is_whitespace());
207224

208225
quote! {
209226
unsafe {
210-
match _maybe_faux_faux.call_stub(<Self>::#faux_ident, #fn_name, #args) {
227+
match _maybe_faux_faux.call_stub(<Self>::#faux_ident #turbofish, #fn_name, #args, #generics_str) {
211228
std::result::Result::Ok(o) => o,
212229
std::result::Result::Err(e) => panic!("{}", e),
213230
}
@@ -332,6 +349,7 @@ impl<'a> MethodData<'a> {
332349
let MethodData {
333350
arg_types,
334351
receiver,
352+
generics,
335353
..
336354
} = self;
337355
let receiver_ty = &receiver.ty;
@@ -345,11 +363,23 @@ impl<'a> MethodData<'a> {
345363
let output = output.unwrap_or(&empty);
346364
let name_str = name.to_string();
347365

366+
let generics_contents = if generics.params.is_empty() {
367+
None
368+
} else {
369+
let params = &generics.params;
370+
Some(quote! { , #params })
371+
};
372+
373+
let generics_where_clause = &generics.where_clause;
374+
375+
let generic_idents = generic_type_idents(Some(generics.clone()));
376+
let turbofish = turbofish(&generic_idents);
377+
348378
let when_method = syn::parse_quote! {
349-
pub fn #when_ident<'m>(&'m mut self) -> faux::When<'m, #receiver_ty, (#(#arg_types),*), #output, faux::matcher::AnyInvocation> {
379+
pub fn #when_ident<'m #generics_contents>(&'m mut self) -> faux::When<'m, #receiver_ty, (#(#arg_types),*), #output, faux::matcher::AnyInvocation> #generics_where_clause {
350380
match &mut self.0 {
351381
faux::MaybeFaux::Faux(_maybe_faux_faux) => faux::When::new(
352-
<Self>::#faux_ident,
382+
<Self>::#faux_ident #turbofish,
353383
#name_str,
354384
_maybe_faux_faux
355385
),
@@ -362,7 +392,7 @@ impl<'a> MethodData<'a> {
362392
let faux_method = syn::parse_quote! {
363393
#[allow(clippy::needless_arbitrary_self_type)]
364394
#[allow(clippy::boxed_local)]
365-
pub fn #faux_ident(self: #receiver_ty, _: (#(#arg_types),*)) -> #output {
395+
pub fn #faux_ident #generics (self: #receiver_ty, _: (#(#arg_types),*)) -> #output #generics_where_clause {
366396
panic!(#panic_message)
367397
}
368398
};
@@ -466,3 +496,22 @@ fn path_args_contains_self(path: &syn::Path, self_path: &syn::TypePath) -> bool
466496
}
467497
}
468498
}
499+
500+
fn generic_type_idents(generics: Option<Generics>) -> Vec<Ident> {
501+
generics
502+
.map(|g| {
503+
g.type_params()
504+
.into_iter()
505+
.map(|tp| tp.ident.clone())
506+
.collect()
507+
})
508+
.unwrap_or_default()
509+
}
510+
511+
fn turbofish(idents: &[Ident]) -> TokenStream {
512+
if idents.is_empty() {
513+
quote! {}
514+
} else {
515+
quote! { :: < #(#idents),* > }
516+
}
517+
}

src/lib.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -999,11 +999,13 @@ impl Faux {
999999
id: fn(R, I) -> O,
10001000
fn_name: &'static str,
10011001
input: I,
1002+
generics: &'static str,
10021003
) -> Result<O, InvocationError> {
1003-
let mock = self.store.get(id, fn_name)?;
1004+
let mock = self.store.get(id, fn_name, generics)?;
10041005
mock.call(input).map_err(|stub_error| InvocationError {
10051006
fn_name: mock.name(),
10061007
struct_name: self.store.struct_name,
1008+
generics,
10071009
stub_error,
10081010
})
10091011
}
@@ -1012,22 +1014,30 @@ impl Faux {
10121014
pub struct InvocationError {
10131015
struct_name: &'static str,
10141016
fn_name: &'static str,
1017+
generics: &'static str,
10151018
stub_error: mock::InvocationError,
10161019
}
10171020

10181021
impl fmt::Display for InvocationError {
10191022
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1023+
let generics = if self.generics.is_empty() {
1024+
String::new()
1025+
} else {
1026+
format!("<{}>", self.generics)
1027+
};
10201028
match &self.stub_error {
1021-
mock::InvocationError::NeverStubbed => write!(
1022-
f,
1023-
"`{}::{}` was called but never stubbed",
1024-
self.struct_name, self.fn_name
1025-
),
1029+
mock::InvocationError::NeverStubbed => {
1030+
write!(
1031+
f,
1032+
"`{}::{}{}` was called but never stubbed",
1033+
self.struct_name, self.fn_name, generics
1034+
)
1035+
}
10261036
mock::InvocationError::Stub(errors) => {
10271037
writeln!(
10281038
f,
1029-
"`{}::{}` had no suitable stubs. Existing stubs failed because:",
1030-
self.struct_name, self.fn_name
1039+
"`{}::{}{}` had no suitable stubs. Existing stubs failed because:",
1040+
self.struct_name, self.fn_name, generics
10311041
)?;
10321042
let mut errors = errors.iter();
10331043
if let Some(e) = errors.next() {

src/mock/store.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ impl<'stub> Store<'stub> {
4444
&self,
4545
id: fn(R, I) -> O,
4646
fn_name: &'static str,
47+
generics: &'static str,
4748
) -> Result<&Mock<'stub, I, O>, InvocationError> {
4849
match self.stubs.get(&(id as usize)).map(|m| m.as_typed()) {
4950
Some(mock) => {
@@ -53,6 +54,7 @@ impl<'stub> Store<'stub> {
5354
None => Err(InvocationError {
5455
fn_name,
5556
struct_name: self.struct_name,
57+
generics,
5658
stub_error: super::InvocationError::NeverStubbed,
5759
}),
5860
}

tests/generic_method_return.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
#![allow(clippy::disallowed_names)]
2+
3+
pub trait MyTrait {}
4+
5+
#[derive(Clone, PartialEq, Debug)]
6+
struct Entity {}
7+
impl MyTrait for Entity {}
8+
9+
#[derive(Clone, PartialEq, Debug)]
10+
struct Entity2 {}
11+
impl MyTrait for Entity2 {}
12+
13+
#[faux::create]
14+
pub struct Foo {}
15+
16+
#[faux::create]
17+
pub struct Bar {}
18+
19+
#[faux::methods]
20+
impl Foo {
21+
pub fn foo<E: MyTrait>(&self, _e: E) -> E {
22+
todo!()
23+
}
24+
pub fn bar<E: MyTrait, F: MyTrait>(&self, _e: E, _f: F) -> Result<E, F> {
25+
todo!()
26+
}
27+
pub fn baz<E>(&self, _e: E) -> E
28+
where
29+
E: MyTrait,
30+
{
31+
todo!()
32+
}
33+
pub fn qux<E>(&self)
34+
where
35+
E: MyTrait,
36+
{
37+
todo!()
38+
}
39+
}
40+
41+
#[faux::create]
42+
struct AsyncFoo {}
43+
#[faux::methods]
44+
impl AsyncFoo {
45+
pub async fn foo<E: MyTrait>(&self, _e: E) -> E {
46+
todo!()
47+
}
48+
pub async fn bar<E: MyTrait, F: MyTrait>(&self, _e: E, _f: F) -> Result<E, F> {
49+
todo!()
50+
}
51+
pub async fn baz<E>(&self, _e: E) -> E
52+
where
53+
E: MyTrait,
54+
{
55+
todo!()
56+
}
57+
pub async fn qux<E>(&self)
58+
where
59+
E: MyTrait,
60+
{
61+
todo!()
62+
}
63+
64+
pub async fn qux_with_arg<E>(&self, _arg: u32) -> u32
65+
where
66+
E: MyTrait,
67+
{
68+
todo!()
69+
}
70+
}
71+
72+
#[test]
73+
fn generics() {
74+
let mut foo = Foo::faux();
75+
faux::when!(foo.foo).then_return(Entity {});
76+
assert_eq!(foo.foo(Entity {}), Entity {});
77+
78+
let mut bar = Foo::faux();
79+
faux::when!(bar.bar).then_return(Ok::<_, Entity>(Entity {}));
80+
assert_eq!(bar.bar(Entity {}, Entity {}), Ok(Entity {}));
81+
82+
let mut baz = Foo::faux();
83+
faux::when!(baz.baz).then_return(Entity {});
84+
assert_eq!(baz.baz(Entity {}), Entity {});
85+
86+
let mut qux = Foo::faux();
87+
faux::when!(qux.qux::<Entity>()).then(|_| {});
88+
qux.qux::<Entity>();
89+
}
90+
91+
#[test]
92+
fn generic_tests_async() {
93+
let mut foo: AsyncFoo = AsyncFoo::faux();
94+
faux::when!(foo.foo).then_return(Entity {});
95+
96+
let mut bar = AsyncFoo::faux();
97+
faux::when!(bar.bar).then_return(Ok::<_, Entity>(Entity {}));
98+
99+
let mut baz = AsyncFoo::faux();
100+
faux::when!(baz.baz).then_return(Entity {});
101+
102+
let mut qux = AsyncFoo::faux();
103+
faux::when!(qux.qux::<Entity>()).then(|_| {});
104+
105+
let mut qux_with_arg = AsyncFoo::faux();
106+
faux::when!(qux_with_arg.qux_with_arg::<Entity>()).then(|_| 100);
107+
faux::when!(qux_with_arg.qux_with_arg::<Entity>(42)).then(|_| 84);
108+
faux::when!(qux_with_arg.qux_with_arg::<Entity>(43)).then(|_| 86);
109+
futures::executor::block_on(async {
110+
assert_eq!(foo.foo(Entity {}).await, Entity {});
111+
assert_eq!(bar.bar(Entity {}, Entity {}).await, Ok(Entity {}));
112+
assert_eq!(baz.baz(Entity {}).await, Entity {});
113+
qux.qux::<Entity>().await;
114+
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(42).await, 84);
115+
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(43).await, 86);
116+
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(50).await, 100);
117+
});
118+
}
119+
120+
#[test]
121+
fn generic_two_different_impls() {
122+
let mut qux_with_arg = AsyncFoo::faux();
123+
faux::when!(qux_with_arg.qux_with_arg::<Entity>()).then(|_| 100);
124+
faux::when!(qux_with_arg.qux_with_arg::<Entity2>()).then(|_| 200);
125+
futures::executor::block_on(async {
126+
assert_eq!(qux_with_arg.qux_with_arg::<Entity>(42).await, 100);
127+
assert_eq!(qux_with_arg.qux_with_arg::<Entity2>(42).await, 200);
128+
});
129+
}
130+
131+
#[test]
132+
#[should_panic(expected = "`Foo::qux<E>` was called but never stubbed")]
133+
fn unmocked_faux_panics_with_generic_information() {
134+
let foo = Foo::faux();
135+
foo.qux::<Entity>();
136+
}

tests/simple.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ fn faux_ref_output() {
6969
}
7070

7171
#[test]
72-
#[should_panic]
72+
#[should_panic(expected = "`Foo::get_stuff` was called but never stubbed")]
7373
fn unmocked_faux_panics() {
7474
let mock = Foo::faux();
7575
mock.get_stuff();

tests/when_arguments.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ impl Foo {
2828
}
2929
}
3030

31-
#[derive(Debug)]
32-
struct Bar(i32);
33-
3431
#[test]
3532
fn no_args() {
3633
let mut mock = Foo::faux();

0 commit comments

Comments
 (0)