Skip to content

Commit

Permalink
Separate struct generics from method generics for Context objects
Browse files Browse the repository at this point in the history
This commit changes Context objects for static methods of generic
structs.  It removes the method's generic parameters from the Context
object, so only the struct's generic parameters (if any) remain.
Similarly, Context::expect will now only have the method's generic
parameters.
  • Loading branch information
asomers committed Aug 24, 2019
1 parent 2f2250e commit ae23fdc
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 33 deletions.
2 changes: 1 addition & 1 deletion mockall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@
//! # fn main() {
//! let ctx = MockFoo::<u32>::new_context();
//! ctx.expect()
//! .returning(|_: u32| MockFoo::default());
//! .returning(|_| MockFoo::default());
//! let mock = MockFoo::<u32>::new(42u32);
//! # }
//! ```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ trait Foo<T: 'static> {
fn returning() {
let ctx = MockFoo::<u32>::foo_context();
ctx.expect()
.returning(|_: u32| ());
.returning(|_| ());
MockFoo::foo(42u32);
}
26 changes: 13 additions & 13 deletions mockall/tests/mock_generic_struct_with_generic_static_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ fn checkpoint() {

let mut mock = MockFoo::<u32>::new();
let ctx = MockFoo::<u32>::foo_context();
ctx.expect()
.returning(|_: u32, _: i16| 0)
ctx.expect::<i16>()
.returning(|_, _| 0)
.times(1..3);
mock.checkpoint();
panic!("Shouldn't get here!");
Expand All @@ -38,8 +38,8 @@ fn ctx_checkpoint() {
let _m = BAR_MTX.lock();

let ctx = MockFoo::<u32>::foo_context();
ctx.expect()
.returning(|_: u32, _: i16| 0)
ctx.expect::<i16>()
.returning(|_, _| 0)
.times(1..3);
ctx.checkpoint();
panic!("Shouldn't get here!");
Expand All @@ -53,8 +53,8 @@ fn ctx_hygiene() {

{
let ctx0 = MockFoo::<u32>::foo_context();
ctx0.expect()
.returning(|_: u32, _: i16| 0);
ctx0.expect::<i16>()
.returning(|_, _| 0);
}
MockFoo::foo(42, 69);
}
Expand All @@ -65,7 +65,7 @@ fn return_default() {
let _m = BAR_MTX.lock();

let ctx = MockFoo::<u32>::foo_context();
ctx.expect::<u32, i16>();
ctx.expect::<i16>();
MockFoo::foo(5u32, 6i16);
}

Expand All @@ -74,8 +74,8 @@ fn returning() {
let _m = BAR_MTX.lock();

let ctx = MockFoo::<u32>::foo_context();
ctx.expect()
.returning(|_: u32, _: i16| 0);
ctx.expect::<i16>()
.returning(|_, _| 0);
MockFoo::foo(41u32, 42i16);
}

Expand All @@ -84,10 +84,10 @@ fn two_matches() {
let _m = BAR_MTX.lock();

let ctx = MockFoo::<u32>::foo_context();
ctx.expect()
ctx.expect::<i16>()
.with(predicate::eq(42u32), predicate::eq(0i16))
.return_const(99u64);
ctx.expect()
ctx.expect::<i16>()
.with(predicate::eq(69u32), predicate::eq(0i16))
.return_const(101u64);
assert_eq!(101, MockFoo::foo(69u32, 0i16));
Expand All @@ -99,8 +99,8 @@ fn with() {
let _m = BAR_MTX.lock();

let ctx = MockFoo::<u32>::foo_context();
ctx.expect()
ctx.expect::<i16>()
.with(predicate::eq(42u32), predicate::eq(99i16))
.returning(|_: u32, _: i16| 0);
.returning(|_, _| 0);
MockFoo::foo(42u32, 99i16);
}
2 changes: 1 addition & 1 deletion mockall/tests/mock_generic_struct_with_static_method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ mock! {
fn returning() {
let ctx = MockFoo::<u32>::foo_context();
ctx.expect()
.returning(|_: u32| ());
.returning(|_| ());
MockFoo::foo(42u32);
}
2 changes: 1 addition & 1 deletion mockall_derive/src/automock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ fn mock_function(vis: &Visibility, sig: &Signature) -> TokenStream {
}
#meth_vis fn #context_ident() -> #mod_ident::Context
{
#mod_ident::Context{}
#mod_ident::Context::default()
}
).to_tokens(&mut out);
out
Expand Down
63 changes: 49 additions & 14 deletions mockall_derive/src/expectation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ struct Common<'a> {
attrs: &'a TokenStream,
/// The expectation's generic types as a list of types
fn_params: Punctuated<Ident, Token![,]>,
/// Generics of the parent struct
struct_generics: Option<&'a Generics>,
/// Generics of the method
meth_generics: &'a Generics,
/// Generics of the Expectation object
egenerics: Generics,
/// Is this for a static method or free function?
Expand Down Expand Up @@ -597,6 +601,8 @@ impl<'a> Expectation<'a> {
argty,
attrs,
fn_params,
struct_generics,
meth_generics,
egenerics,
is_static,
predexprs,
Expand Down Expand Up @@ -916,17 +922,42 @@ impl<'a> StaticExpectation<'a> {
let tbf = tg.as_turbofish();
let v = &self.common.vis;

let gd = Generics::default();
let (s_ig, s_tg, s_wc) = self.common.struct_generics.unwrap_or(&gd)
.split_for_impl();

// Add a lifetime parameter, needed by MutexGuard
let mut ltgenerics = self.common.egenerics.clone();
let mut meth_generics = self.common.meth_generics.clone();
let ltdef = LifetimeDef::new(
Lifetime::new("'__mockall_lt", Span::call_site())
);
meth_generics.params.push(GenericParam::Lifetime(ltdef));
let (meth_ig, _meth_tg, meth_wc) = meth_generics.split_for_impl();

let mut e_generics = self.common.egenerics.clone();
let ltdef = LifetimeDef::new(
Lifetime::new("'__mockall_lt", Span::call_site())
);
ltgenerics.params.push(GenericParam::Lifetime(ltdef));
let (lt_ig, lt_tg, lt_wc) = ltgenerics.split_for_impl();
e_generics.params.push(GenericParam::Lifetime(ltdef));
let (e_ig, e_tg, e_wc) = e_generics.split_for_impl();

let ctx_fn_params = match self.common.struct_generics {
None => Punctuated::new(),
Some(g) => Punctuated::<Ident, Token![,]>::from_iter(
g.type_params().map(|tp| tp.ident.clone())
)
};

let context_ts = quote!(
#v struct Context {}
impl Context {
#v struct Context #s_ig #s_wc {
// Prevent "unused type parameter" errors
// Surprisingly, PhantomData<Fn(generics)> is Send even if
// generics are not, unlike PhantomData<generics>
_phantom: ::std::marker::PhantomData<
Box<dyn Fn(#ctx_fn_params) -> () + Send>
>
}
impl #s_ig Context #s_tg #s_wc {
#v fn checkpoint(&self) {
Self::do_checkpoint()
}
Expand All @@ -939,14 +970,18 @@ impl<'a> StaticExpectation<'a> {
.collect::<Vec<_>>();
}

// TODO: separate struct generics from method generics, so this
// method can be invoked with the turbofish of the method
// generics alone.
#v fn expect #lt_ig ( &self,) -> ExpectationGuard #lt_tg #lt_wc{
#v fn expect #meth_ig ( &self,) -> ExpectationGuard #e_tg
#meth_wc
{
ExpectationGuard::new(EXPECTATIONS.lock().unwrap())
}
}
impl Drop for Context {
impl #s_ig Default for Context #s_tg #s_wc {
fn default() -> Self {
Context {_phantom: std::marker::PhantomData}
}
}
impl #s_ig Drop for Context #s_tg #s_wc {
fn drop(&mut self) {
if !std::thread::panicking() {
Self::do_checkpoint()
Expand All @@ -973,12 +1008,12 @@ impl<'a> StaticExpectation<'a> {
//
// ExpectationGuard is only defined for expectations that return
// 'static return types.
#v struct ExpectationGuard #lt_ig #lt_wc {
#v struct ExpectationGuard #e_ig #e_wc {
guard: MutexGuard<'__mockall_lt, Expectations #tg>,
i: usize
}

impl #lt_ig ExpectationGuard #lt_tg #lt_wc
impl #e_ig ExpectationGuard #e_tg #e_wc
{
/// Just like
/// [`Expectation::in_sequence`](struct.Expectation.html#method.in_sequence)
Expand Down Expand Up @@ -1094,13 +1129,13 @@ impl<'a> StaticExpectation<'a> {
/// [`&GenericExpectation`](struct.GenericExpectation.html) but
/// protected by a Mutex guard. Useful for mocking static
/// methods. Forwards accesses to an `Expectation` object.
#v struct ExpectationGuard #lt_ig #lt_wc{
#v struct ExpectationGuard #e_ig #e_wc{
guard: MutexGuard<'__mockall_lt, GenericExpectations>,
i: usize,
_phantom: ::std::marker::PhantomData<(#fn_params)>,
}

impl #lt_ig ExpectationGuard #lt_tg #lt_wc
impl #e_ig ExpectationGuard #e_tg #e_wc
{
/// Just like
/// [`Expectation::in_sequence`](struct.Expectation.html#method.in_sequence)
Expand Down
5 changes: 3 additions & 2 deletions mockall_derive/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,11 @@ fn gen_mock_method(mod_ident: Option<&syn::Ident>,
#[cfg(any(test, not(feature = "extra-docs")))]
let docstr: Option<syn::Attribute> = None;
let context_ident = format_ident!("{}_context", ident);
let (_, ctx_tg, _) = generics.split_for_impl();
quote!(#attrs #docstr #expect_vis fn #context_ident()
-> #mod_ident::#ident::Context
-> #mod_ident::#ident::Context #ctx_tg
{
#mod_ident::#ident::Context{}
#mod_ident::#ident::Context::default()
}
)
} else {
Expand Down

0 comments on commit ae23fdc

Please sign in to comment.