Skip to content

Commit

Permalink
Fix mocking generic methods of generic structs returning nonstatic
Browse files Browse the repository at this point in the history
Fixes #306
  • Loading branch information
asomers committed Jul 10, 2021
1 parent fc18e54 commit 16cff13
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 2 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T: 'static> {
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<T: 'static> Bong for Thing<T> {
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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::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::<u32>::new();
thing.expect_trait_foo()
.returning(move || xstatic.clone());

assert_eq!(42u32, *thing.trait_foo().0);
}
}
27 changes: 26 additions & 1 deletion mockall/tests/specific_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ mock! {
}

#[test]
fn return_const() {
fn specific_impl() {
let mut mocku = MockFoo::<u32>::new();
mocku.expect_bar()
.return_const(());
Expand All @@ -47,3 +47,28 @@ fn return_const() {
//.return_const(());
//mocku.baz::<f32>(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<X: 'static, Y: 'static> {}
impl<Y: 'static> Bar for Bean<u32, Y> {
fn bar(&self);
}
impl<Y: 'static> Bar for Bean<i32, Y> {
fn bar(&self);
}
}

#[test]
fn partially_specific_impl() {
let mut mocku = MockBean::<u32, f32>::new();
mocku.expect_bar()
.return_const(());
let mut mocki = MockBean::<i32, f32>::new();
mocki.expect_bar()
.return_const(());

mocku.bar();
mocki.bar();
}
11 changes: 10 additions & 1 deletion mockall_derive/src/mock_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 16cff13

Please sign in to comment.