diff --git a/CHANGELOG.md b/CHANGELOG.md index 78774d8a..129b39f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [ Unreleased ] - ReleaseDate + +### Fixed + +- Fix mocking generic methods of generic structs returning nonstatic, a + regression in v0.10.0. + ([#312](https://github.com/asomers/mockall/pull/312)) + ## [ 0.10.1 ] - 2021-07-01 ## Fixed diff --git a/mockall/tests/mock_generic_method_on_generic_struct_returning_nonstatic.rs b/mockall/tests/mock_generic_method_on_generic_struct_returning_nonstatic.rs new file mode 100644 index 00000000..b4f63860 --- /dev/null +++ b/mockall/tests/mock_generic_method_on_generic_struct_returning_nonstatic.rs @@ -0,0 +1,116 @@ +//! Mocking a generic method whose return value's lifetime is a generic +//! parameter on a generic struct requires extra care for the Expectation +//! object's type generics. +//! https://github.com/asomers/mockall/issues/306 +#![deny(warnings)] + +use mockall::*; + +#[derive(Clone)] +struct X<'a>(&'a u32); + +trait Bong { + fn trait_foo<'a>(&self) -> X<'a>; + fn trait_baz<'a>(&self) -> &X<'a>; +} + +mock! { + Thing { + fn foo<'a>(&self) -> X<'a>; + + // XXX static methods don't work yet. + // fn bar<'a>() -> X<'a>; + + fn baz<'a>(&self) -> &X<'a>; + + // Methods returning a mutable reference to a non-static value won't + // work unless 'a is static. I doubt there are any real-life methods + // that fit this pattern; open an issue if you find one. + //fn bang<'a>(&mut self) -> &mut X<'a>; + + // Generic methods can't return non-static values either, because + // Mockall requires generic methods' generic parameters to implement + // std::any::Any, which means they must be 'static. + //fn bean<'a, T: 'static>(&self, t: T) -> X<'a>; + } + // The same types of methods should work if they are Trait methods. + impl Bong for Thing { + fn trait_foo<'a>(&self) -> X<'a>; + fn trait_baz<'a>(&self) -> &X<'a>; + } +} + +#[test] +fn return_static() { + const D: u32 = 42; + let x = X(&D); + let mut thing = MockThing::::new(); + thing.expect_foo() + .return_const(x); + + assert_eq!(42u32, *thing.foo().0); +} + +#[test] +fn return_static_ref() { + const D: u32 = 42; + let x = X(&D); + let mut thing = MockThing::::new(); + thing.expect_baz() + .return_const(x); + + assert_eq!(42u32, *(*thing.baz()).0); +} + +// It isn't possible to safely set an expectation for a non-'static return value +// (because the mock object doesn't have any lifetime parameters itself), but +// unsafely setting such an expectation is a common use case. +#[test] +fn return_nonstatic() { + let d = 42u32; + let x = X(&d); + let xstatic: X<'static> = unsafe{ std::mem::transmute(x) }; + let mut thing = MockThing::::new(); + thing.expect_foo() + .returning(move || xstatic.clone()); + + assert_eq!(42u32, *thing.foo().0); +} + +mod trait_methods { + use super::*; + + #[test] + fn return_static() { + const D: u32 = 42; + let x = X(&D); + let mut thing = MockThing::::new(); + thing.expect_trait_foo() + .return_const(x); + + assert_eq!(42u32, *thing.trait_foo().0); + } + + #[test] + fn return_static_ref() { + const D: u32 = 42; + let x = X(&D); + let mut thing = MockThing::::new(); + thing.expect_trait_baz() + .return_const(x); + + assert_eq!(42u32, *(*thing.trait_baz()).0); + } + + #[test] + fn return_nonstatic() { + let d = 42u32; + let x = X(&d); + let xstatic: X<'static> = unsafe{ std::mem::transmute(x) }; + let mut thing = MockThing::::new(); + thing.expect_trait_foo() + .returning(move || xstatic.clone()); + + assert_eq!(42u32, *thing.trait_foo().0); + } +} diff --git a/mockall/tests/specific_impl.rs b/mockall/tests/specific_impl.rs index 6ea9506b..e16a48ec 100644 --- a/mockall/tests/specific_impl.rs +++ b/mockall/tests/specific_impl.rs @@ -26,7 +26,7 @@ mock! { } #[test] -fn return_const() { +fn specific_impl() { let mut mocku = MockFoo::::new(); mocku.expect_bar() .return_const(()); @@ -47,3 +47,28 @@ fn return_const() { //.return_const(()); //mocku.baz::(3.14159); //} + +// Here's a partially specific impl: Bar is implemented for Bean where one of +// the generic types is concrete, but the other isn't. +mock! { + pub Bean {} + impl Bar for Bean { + fn bar(&self); + } + impl Bar for Bean { + fn bar(&self); + } +} + +#[test] +fn partially_specific_impl() { + let mut mocku = MockBean::::new(); + mocku.expect_bar() + .return_const(()); + let mut mocki = MockBean::::new(); + mocki.expect_bar() + .return_const(()); + + mocku.bar(); + mocki.bar(); +} diff --git a/mockall_derive/src/mock_function.rs b/mockall_derive/src/mock_function.rs index 849ddbb2..8e201e5f 100644 --- a/mockall_derive/src/mock_function.rs +++ b/mockall_derive/src/mock_function.rs @@ -620,9 +620,18 @@ impl MockFunction { { let inner_mod_ident = self.inner_mod_ident(); if let Some(PathArguments::AngleBracketed(abga)) = self_args { + // staticize any lifetimes that might be present in the Expectation + // object but not in the self args. These come from the method's + // return type. + let mut abga2 = abga.clone(); + for _ in self.egenerics.lifetimes() { + let lt = Lifetime::new("'static", Span::call_site()); + let la = GenericArgument::Lifetime(lt); + abga2.args.insert(0, la); + } assert!(!self.is_method_generic(), "specific impls with generic methods are TODO"); - quote!(#inner_mod_ident::Expectation #abga) + quote!(#inner_mod_ident::Expectation #abga2) } else { // staticize any lifetimes. This is necessary for methods that // return non-static types, because the Expectation itself must be