From 3b65da0f01cabbfff19d1c0530a91d1afb65a597 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 6 Aug 2019 22:13:23 -0600 Subject: [PATCH] Eliminate the generic static methods hack It is no longer necessary to duplicate the struct's generic parameters in the mock method's definition. --- CHANGELOG.md | 4 +++ mockall/src/lib.rs | 29 +++++-------------- ...omock_generic_struct_with_static_method.rs | 18 ++++++++++++ mockall/tests/mock_generic_constructor.rs | 4 +-- ...k_generic_constructor_with_where_clause.rs | 4 +-- ...neric_struct_with_generic_static_method.rs | 18 ++++++++++++ .../mock_generic_struct_with_static_method.rs | 20 +++++-------- mockall_derive/src/lib.rs | 19 ++++++++---- mockall_derive/src/mock.rs | 10 ++++--- 9 files changed, 77 insertions(+), 49 deletions(-) create mode 100644 mockall/tests/automock_generic_struct_with_static_method.rs create mode 100644 mockall/tests/mock_generic_struct_with_generic_static_method.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cb802bab..8729606d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate ### Added +- Static methods of generic structs (and traits) can now be mocked without the + hack of duplicating the struct's (or trait's) generic parameters. + ([#18](https://github.com/asomers/mockall/pull/18)) + - Methods with closure arguments can now be mocked. Technically they always could be, but until now it wasn't possible to call the closure argument from `withf` or `returning`. No special tricks are required by the user. diff --git a/mockall/src/lib.rs b/mockall/src/lib.rs index c4c4872c..93372ffd 100644 --- a/mockall/src/lib.rs +++ b/mockall/src/lib.rs @@ -679,11 +679,11 @@ //! //! Without trait specialization, it probably won't be possible for Mockall //! to support all specializing methods. However, most specializing methods can -//! be mocked as generic methods with repeated type parameters, just like static -//! methods of generic structs. +//! be mocked as generic methods with repeated type parameters. //! //! ``` //! # use mockall::*; +//! // A struct like this //! struct Foo(T); //! impl Foo { //! fn foo(&self, t: T) where T: Copy { @@ -691,6 +691,7 @@ //! # unimplemented!() //! } //! } +//! // Can be mocked like this //! mock! { //! Foo { //! fn foo(&self, t: T2); @@ -841,35 +842,19 @@ //! //! ### Generic static methods //! -//! Mocking static methods of generic structs is a little bit tricky. If the -//! static method uses any generic parameters, then those generic parameters -//! must be duplicated as generic parameters of the static method itself. -//! Here's an example: +//! Mocking static methods of generic structs or traits, whether or not the +//! methods themselves are generic, should work seamlessly. //! //! ``` //! # use mockall::*; -//! // A struct like this: -//! struct Foo { -//! // ... -//! # _t0: std::marker::PhantomData -//! } -//! impl Foo { -//! fn new(t: T) -> Self { -//! // ... -//! # unimplemented!() -//! } -//! } -//! -//! // Can be mocked like this: //! mock! { //! Foo { -//! fn new(t: T2) -> MockFoo; +//! fn new(t: T) -> MockFoo; //! } //! } //! -//! // And used like this: //! # fn main() { -//! MockFoo::::expect_new::() +//! MockFoo::::expect_new() //! .returning(|_| MockFoo::default()); //! let mock = MockFoo::::new(42u32); //! # } diff --git a/mockall/tests/automock_generic_struct_with_static_method.rs b/mockall/tests/automock_generic_struct_with_static_method.rs new file mode 100644 index 00000000..c39d32ca --- /dev/null +++ b/mockall/tests/automock_generic_struct_with_static_method.rs @@ -0,0 +1,18 @@ +// vim: tw=80 +//! static non-generic methods of generic structs shouldn't require any special +//! treatment when mocking. Prior to version 0.3.0, the struct's generic +//! parameters had to be duplicated as generic parameters of the method. + +use mockall::*; + +#[automock] +trait Foo { + fn foo(t: T); +} + +#[test] +fn returning() { + MockFoo::::expect_foo() + .returning(|_| ()); + MockFoo::foo(42u32); +} diff --git a/mockall/tests/mock_generic_constructor.rs b/mockall/tests/mock_generic_constructor.rs index 14610328..40f42c1b 100644 --- a/mockall/tests/mock_generic_constructor.rs +++ b/mockall/tests/mock_generic_constructor.rs @@ -6,13 +6,13 @@ use mockall::*; mock! { pub Foo { - fn build() -> MockFoo; + fn build() -> MockFoo; } } #[test] fn returning_once() { - MockFoo::::expect_build::() + MockFoo::::expect_build() .return_once(MockFoo::::default); let _mock: MockFoo = MockFoo::::build(); diff --git a/mockall/tests/mock_generic_constructor_with_where_clause.rs b/mockall/tests/mock_generic_constructor_with_where_clause.rs index 6431c0b9..f23732f9 100644 --- a/mockall/tests/mock_generic_constructor_with_where_clause.rs +++ b/mockall/tests/mock_generic_constructor_with_where_clause.rs @@ -6,13 +6,13 @@ use mockall::*; mock! { pub Foo where T: Default + 'static { - fn build() -> MockFoo where T2: Default + 'static; + fn build() -> MockFoo; } } #[test] fn returning_once() { - MockFoo::::expect_build::() + MockFoo::::expect_build() .return_once(MockFoo::::default); let _mock: MockFoo = MockFoo::::build(); diff --git a/mockall/tests/mock_generic_struct_with_generic_static_method.rs b/mockall/tests/mock_generic_struct_with_generic_static_method.rs new file mode 100644 index 00000000..a5217be6 --- /dev/null +++ b/mockall/tests/mock_generic_struct_with_generic_static_method.rs @@ -0,0 +1,18 @@ +// vim: ts=80 +//! A generic struct with a generic method on a different parameter + +use mockall::*; + +mock! { + Foo { + fn foo(t: T, q: Q); + } +} + +#[test] +fn with() { + MockFoo::::expect_foo::() + .with(predicate::eq(42u32), predicate::eq(99i16)) + .returning(|_, _| ()); + MockFoo::foo(42u32, 99i16); +} diff --git a/mockall/tests/mock_generic_struct_with_static_method.rs b/mockall/tests/mock_generic_struct_with_static_method.rs index 37504022..70b9ec89 100644 --- a/mockall/tests/mock_generic_struct_with_static_method.rs +++ b/mockall/tests/mock_generic_struct_with_static_method.rs @@ -1,25 +1,19 @@ -// vim: ts=80 +// vim: tw=80 +//! static non-generic methods of generic structs shouldn't require any special +//! treatment when mocking. Prior to version 0.3.0, the struct's generic +//! parameters had to be duplicated as generic parameters of the method. use mockall::*; -// Static methods parameterized on the struct's generic parameter need to be -// turned into generic methods for mocking. A struct like this: -// -// struct Foo {} -// impl Foo { -// fn foo(t: T) {...} -// } -// -// Can be mocked like this: mock! { Foo { - fn foo(t: T2); + fn foo(t: T); } } #[test] fn returning() { - MockFoo::::expect_foo::() + MockFoo::::expect_foo() .returning(|_| ()); - MockFoo::::foo(42u32); + MockFoo::foo(42u32); } diff --git a/mockall_derive/src/lib.rs b/mockall_derive/src/lib.rs index 9aeb58a0..7ef97b99 100644 --- a/mockall_derive/src/lib.rs +++ b/mockall_derive/src/lib.rs @@ -32,6 +32,9 @@ use crate::mock::{Mock, do_mock}; struct MethodTypes { is_static: bool, + /// Is the Expectation type generic? This can be true even if the method is + /// not generic + is_expectation_generic: bool, /// Type of Expectation returned by the expect method expectation: Type, /// Generics applicable to the Expectation object @@ -396,6 +399,8 @@ fn gen_mod_ident(struct_: &Ident, trait_: Option<&Ident>) /// Combine two Generics structs, producing a new one that has the union of /// their parameters. fn merge_generics(x: &Generics, y: &Generics) -> Generics { + //dbg!(x); + //dbg!(y); /// Compare only the identifiers of two GenericParams fn cmp_gp_idents(x: &GenericParam, y: &GenericParam) -> bool { use GenericParam::*; @@ -547,7 +552,6 @@ fn method_types(sig: &MethodSig, generics: Option<&Generics>) let ident = &sig.ident; let (expectation_generics, expectation_inputs, call_exprs) = declosurefy(&sig.decl.generics, &sig.decl.inputs); - let is_generic = !sig.decl.generics.params.is_empty(); let merged_g = if let Some(g) = generics { merge_generics(&g, &expectation_generics) } else { @@ -620,8 +624,11 @@ fn method_types(sig: &MethodSig, generics: Option<&Generics>) } }; + let is_expectation_generic = !sig.decl.generics.params.is_empty() || + (is_static && generics.filter(|g| !g.params.is_empty()).is_some()); + let expectation_ident = Ident::new("Expectation", span); - let expectations_ident = if is_generic { + let expectations_ident = if is_expectation_generic { Ident::new("GenericExpectations", span) } else { Ident::new("Expectations", span) @@ -629,7 +636,7 @@ fn method_types(sig: &MethodSig, generics: Option<&Generics>) let expectations = parse2( quote!(#ident::#expectations_ident) ).unwrap(); - let expect_obj = if is_generic { + let expect_obj = if is_expectation_generic { parse2(quote!(#expectations)).unwrap() } else { parse2(quote!(#expectations #tg)).unwrap() @@ -639,9 +646,9 @@ fn method_types(sig: &MethodSig, generics: Option<&Generics>) let mut output = sig.decl.output.clone(); deimplify(&mut output); - MethodTypes{is_static, expectation, expectation_generics, - expectation_inputs, expectations, call, expect_obj, call_exprs, - inputs, output, altargs, matchexprs} + MethodTypes{is_static, is_expectation_generic, expectation, + expectation_generics, expectation_inputs, expectations, call, + expect_obj, call_exprs, inputs, output, altargs, matchexprs} } /// Manually mock a structure. diff --git a/mockall_derive/src/mock.rs b/mockall_derive/src/mock.rs index 47ce99b1..3653180d 100644 --- a/mockall_derive/src/mock.rs +++ b/mockall_derive/src/mock.rs @@ -270,9 +270,7 @@ fn gen_mock_method(mod_ident: Option<&syn::Ident>, } let (ig, ex_tg, wc) = meth_types.expectation_generics.split_for_impl(); - let is_generic_method = !meth_types.expectation_generics.params.is_empty(); - //let tg = ex_tg; - let tg = if meth_types.is_static || is_generic_method { + let tg = if meth_types.is_static || meth_types.is_expectation_generic { // For generic and static methods only, the trait's generic parameters // become generic parameters of the method. merged_g.split_for_impl().1 @@ -309,10 +307,14 @@ fn gen_mock_method(mod_ident: Option<&syn::Ident>, let ltd = syn::LifetimeDef::new(lt); let mut g = meth_types.expectation_generics.clone(); g.params.push(syn::GenericParam::Lifetime(ltd.clone())); + if g.lt_token.is_none() { + g.lt_token = Some(Token![<](Span::call_site())); + g.gt_token = Some(Token![>](Span::call_site())); + } let merged_g = merge_generics(&generics, &g); let (ig, _, _) = g.split_for_impl(); let (_, tg, _) = merged_g.split_for_impl(); - let guard_name = if sig.decl.generics.params.is_empty() { + let guard_name = if !meth_types.is_expectation_generic { syn::Ident::new("ExpectationGuard", Span::call_site()) } else { syn::Ident::new("GenericExpectationGuard", Span::call_site())