Repository: asomers/mockall (Rust)

Issue:
Support &dyn T arguments
Whilst we wait for async trait to make it to stable, I'm following the workaround outlined in https://github.com/asomers/mockall/issues/75

But for the following code:
```
#[cfg(test)]
mock! {
    pub MyTrait {
        async fn my_method(&self, value: &dyn MyOtherTrait) -> Result<(), MyError>;
    }
}
```

I get the error:

```
error[E0621]: explicit lifetime required in the type of `value`
mock! {
    pub MyTrait {
        async fn my_method(&self, value: &dyn MyOtherTrait) -> Result<(), MyError>;
                                  ^^^^^ lifetime `'static` required
    }
}
- help: add explicit lifetime `'static` to the type of `value`: `&'static (dyn MyOtherTrait + 'static)`
```

This looks to be due to the generated implementation of the mock - in that ties together the lifetime of value and the returned Result object.

Not sure if this is async specific.


Discussion:
In this case the `Result` is already `'static`, so that's not it.  The problem I think is the `&dyn`.  That's an uncommon syntax, so Mockall doesn't support it.  Try this syntax instead:
```rust
async fn my_method(&self, value: &Box<dyn MyOtherTrait>) -> Result<(), MyError>;
```
Indeed using `&Box` does fix the problem - it's the workaround I already starting using for now.

However I don't agree that `&dyn` is an uncommon syntax. Indeed clippy complains about `&Box` and says that a normal reference should be used - so I've had to add a clippy ignore. And clippy is quite right - why have an extra Box when a borrow would be fine.

Further, I'm calling into `my_method` with a borrowed object and converting that to a Box involves taking ownership - which I can't do (it's only a borrow) or a copying / cloning type of operation - which isn't something that `MyOtherTrait` provides.

The problem is caused in the implementation of the mock macro somewhere - the compiler is clever enough to look through the implementation to see that the static Result depends on the incoming value parameter and is essentially complaining that the value should be static because the returned Result is static.
No, it has nothing to do with the result type.  Try removing the result entirely, and you'll get the same error.  If you really want to know the nitty-gritty details, the error comes from the code that Mockall generates to match arguments.  In order to do something like this:
```rust
let mut my_mock = MockMyTrait::new();
let expected: dyn MyOtherTrait = something_something;
my_mock.expect_my_method()
    .with(eq(expected));
```
`expected` must have `'static` lifetime.  Notice that its type is now `dyn MyOtherTrait`, and a value of that same type gets stored internally to the mock object.

So you can guess why it would take some work for Mockall to support this syntax.  However, the compiler's suggested solution is more restrictive than necessary.  You don't need for the actual reference to be static, just the referenced type.  Try this:
```rust
#[cfg(test)]
mock! {
    pub MyTrait {
        async fn my_method(&self, value: &(dyn MyOtherTrait + 'static)) -> Result<(), MyError>;
    }
}
```
I guess I'd leaned too heavily on the example in the E0621 section of https://doc.rust-lang.org/error-index.html where the lifetime of the parameter gets ignored when the return value is created. But reading more carefully, I can see it can just be how the parameters are used in the implementation rather than necessarily the implementation linking parameters to the return value.

I tried putting `&(dyn MyOtherTrait + 'static)` as the parameter type on the method in the `mock!`. The compiler wanted that propagated to the trait and so on, but things seem to fit together OK once I do that. Thanks for the suggestion.

I can't say I 100% understand what that `+ 'static` does. From what I can find, I'm guessing it says that `MyOtherTrait` isn't parameterised with a lifetime or, if it does, then the parameterised lifetime is `'static`.

Is the plan to ultimately implement this syntax (i.e. without the `'static`)?
> I guess I'd leaned too heavily on the example in the E0621 section of https://doc.rust-lang.org/error-index.html where the lifetime of the parameter gets ignored when the return value is created. But reading more carefully, I can see it can just be how the parameters are used in the implementation rather than necessarily the implementation linking parameters to the return value.
> 
> I tried putting `&(dyn MyOtherTrait + 'static)` as the parameter type on the method in the `mock!`. The compiler wanted that propagated to the trait and so on, but things seem to fit together OK once I do that. Thanks for the suggestion.
> 
> I can't say I 100% understand what that `+ 'static` does. From what I can find, I'm guessing it says that `MyOtherTrait` isn't parameterised with a lifetime or, if it does, then the parameterised lifetime is `'static`.

Yes

> 
> Is the plan to ultimately implement this syntax (i.e. without the `'static`)?

Yes.  I can do it by special-casing the generation code, to accept an owned `Box<dyn MyOtherTrait>` for the matching functions, while not altering the signature of the mocked method itself.
So the problem isn't as bad as I initially thought.  Boxed trait objects have an implicit `'static` lifetime unless otherwise specified, and internal to Mockall is a boxed trait object that is parameterized on your function's argument type.  Mockall tries to compare that boxed trait object to your function's argument, but the types are different (`dyn Trait + static` vs `dyn Trait`) :disappointed: .

So I can suggest a better workaround: Instead of adding `'static` to your function's signature, add a lifetime parameter.  Rustc won't complain about the same `method not compatible with trait` error, and you will have more flexibility in how you call it.  Do it like this:
```rust
#[cfg(test)]
mock! {
    pub MyTrait {
        async fn my_method<'a>(&self, value: &(dyn MyOtherTrait + 'a)) -> Result<(), MyError>;
    }
}
```
> So the problem isn't as bad as I initially thought. Boxed trait objects have an implicit `'static` lifetime unless otherwise specified, and internal to Mockall is a boxed trait object that is parameterized on your function's argument type. Mockall tries to compare that boxed trait object to your function's argument, but the types are different (`dyn Trait + static` vs `dyn Trait`) 😞 .
> 
> So I can suggest a better workaround: Instead of adding `'static` to your function's signature, add a lifetime parameter. Rustc won't complain about the same `method not compatible with trait` error, and you will have more flexibility in how you call it. Do it like this:
> 
> #[cfg(test)]
> mock! {
>     pub MyTrait {
>         async fn my_method<'a>(&self, value: &(dyn MyOtherTrait + 'a)) -> Result<(), MyError>;
>     }
> }

This seems to work perfectly for my #645 issue as well (using `#[automock]`). I wonder if we could create something like `serde.rs` with `mdbook` to provide a single knowledge base? I am really sorry I couldn't find a solution to this one myself, and thanks for pointing me to it, @asomers !
@iddm I've tried to keep all of the necessary documentation in rustdoc, in the "User Guide" section.  I'm not necessarily averse to converting that to mdbook.  But would the doctests still cover it?  And would that took really organize it better than rustdoc?
> [@iddm](https://github.com/iddm) I've tried to keep all of the necessary documentation in rustdoc, in the "User Guide" section. I'm not necessarily averse to converting that to mdbook. But would the doctests still cover it? And would that took really organize it better than rustdoc?

I am also not sure of a better way myself. I just wish I could have avoided asking you and spending your and my time and could have simply found this ticket or at least the relevant information :-) I guess this mentioned in the rustdoc is fine as well :-) It mentions the Box, after all :-)



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4393f062..bebee026 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).
 
 ## [Unreleased] - ReleaseDate
 ### Added
+- Support mocking methods with trait object arguments that use implicit
+  lifetimes
+  ([#174](https://github.com/asomers/mockall/pull/174))
+
 ### Changed
 - Raised the minimum supported Rust version (MSRV) to 1.40.0.
   ([#170](https://github.com/asomers/mockall/pull/170))
diff --git a/mockall_derive/src/mockable_struct.rs b/mockall_derive/src/mockable_struct.rs
index 31b6bfb4..2bb0f398 100644
--- a/mockall_derive/src/mockable_struct.rs
+++ b/mockall_derive/src/mockable_struct.rs
@@ -2,6 +2,96 @@
 use super::*;
 use syn::parse::{Parse, ParseStream};
 
+/// Make any implicit lifetime parameters explicit
+fn add_lifetime_parameters(sig: &mut Signature) {
+    fn add_to_trait_object(generics: &mut Generics, var: &Pat, to: &mut TypeTraitObject) {
+        let mut has_lifetime = false;
+        for bound in to.bounds.iter() {
+            if let TypeParamBound::Lifetime(_) = bound {
+                has_lifetime = true;
+            }
+        }
+        if ! has_lifetime {
+            let arg_ident = match *var {
+                Pat::Wild(_) => {
+                    compile_error(var.span(),
+                        "Mocked methods must have named arguments");
+                    format_ident!("dont_care")
+                },
+                Pat::Ident(ref pat_ident) => {
+                    if let Some(r) = &pat_ident.by_ref {
+                        compile_error(r.span(),
+                            "Mockall does not support by-reference argument bindings");
+                    }
+                    if let Some((_at, subpat)) = &pat_ident.subpat {
+                        compile_error(subpat.span(),
+                            "Mockall does not support subpattern bindings");
+                    }
+                    pat_ident.ident.clone()
+                },
+                _ => {
+                    compile_error(var.span(),
+                        "Unsupported argument type");
+                    format_ident!("dont_care")
+                }
+            };
+            let s = format!("'__mockall_{}", arg_ident);
+            let span = Span::call_site();
+            let lt = Lifetime::new(&s, span);
+            to.bounds.push(TypeParamBound::Lifetime(lt.clone()));
+            generics.lt_token.get_or_insert(Token![<](span));
+            generics.gt_token.get_or_insert(Token![>](span));
+            let gpl = GenericParam::Lifetime(LifetimeDef::new(lt));
+            generics.params.push(gpl);
+        }
+    }
+
+    fn add_to_type(generics: &mut Generics, var: &Pat, ty: &mut Type) {
+        match ty {
+            Type::Array(ta) => add_to_type(generics, var, ta.elem.as_mut()),
+            Type::BareFn(_) => (),
+            Type::ImplTrait(_) => (),
+            Type::Path(_) => (),
+            Type::Ptr(_) => (),
+            Type::Reference(tr) => {
+                match tr.elem.as_mut() {
+                    Type::Paren(tp) => {
+                        if let Type::TraitObject(to) = tp.elem.as_mut() {
+                            add_to_trait_object(generics, var, to);
+                        } else {
+                            add_to_type(generics, var, tr.elem.as_mut());
+                        }
+                    },
+                    Type::TraitObject(to) => {
+                        add_to_trait_object(generics, var, to);
+                        // We need to wrap it in a Paren.  Otherwise it won't be
+                        // syntactically valid after we add a lifetime bound,
+                        // due to a "ambiguous `+` in a type" error
+                        *tr.elem = Type::Paren(TypeParen {
+                            paren_token: token::Paren::default(),
+                            elem: Box::new(Type::TraitObject(to.clone()))
+                        });
+                    },
+                    _ => add_to_type(generics, var, tr.elem.as_mut()),
+                }
+            },
+            Type::Slice(ts) => add_to_type(generics, var, ts.elem.as_mut()),
+            Type::Tuple(tt) => {
+                for mut ty in tt.elems.iter_mut() {
+                    add_to_type(generics, var, &mut ty)
+                }
+            },
+            _ => compile_error(ty.span(), "unsupported type in this position")
+        }
+    }
+
+    for arg in sig.inputs.iter_mut() {
+        if let FnArg::Typed(pt) = arg {
+            add_to_type(&mut sig.generics, &pt.pat, &mut pt.ty)
+        }
+    }
+}
+
 /// Filter a generics list, keeping only the elements specified by path_args
 /// e.g. filter_generics(<A: Copy, B: Clone>, <A>) -> <A: Copy>
 fn filter_generics(g: &Generics, path_args: &PathArguments)
@@ -81,6 +171,7 @@ fn mockable_method(mut meth: ImplItemMethod, name: &Ident, generics: &Generics)
     -> ImplItemMethod
 {
     demutify(&mut meth.sig.inputs);
+    add_lifetime_parameters(&mut meth.sig);
     deimplify(&mut meth.sig.output);
     if let ReturnType::Type(_, ty) = &mut meth.sig.output {
         deselfify(ty, name, generics);
@@ -95,6 +186,7 @@ fn mockable_trait_method(
     generics: &Generics)
 {
     demutify(&mut meth.sig.inputs);
+    add_lifetime_parameters(&mut meth.sig);
     deimplify(&mut meth.sig.output);
     if let ReturnType::Type(_, ty) = &mut meth.sig.output {
         deselfify(ty, name, generics);
@@ -296,3 +388,126 @@ impl Parse for MockableStruct {
         )
     }
 }
+
+#[cfg(test)]
+mod t {
+    use super::*;
+
+mod add_lifetime_parameters {
+    use super::*;
+
+    #[test]
+    fn array() {
+        let mut meth: TraitItemMethod = parse2(quote!(
+            fn foo(&self, x: [&dyn T; 1]);
+        )).unwrap();
+        add_lifetime_parameters(&mut meth.sig);
+        assert_eq!(
+            quote!(fn foo<'__mockall_x>(&self, x: [&(dyn T + '__mockall_x); 1]);)
+                .to_string(),
+            quote!(#meth).to_string()
+        );
+    }
+
+    #[test]
+    fn bare_fn_with_named_args() {
+        let mut meth: TraitItemMethod = parse2(quote!(
+            fn foo(&self, x: fn(&dyn T));
+        )).unwrap();
+        add_lifetime_parameters(&mut meth.sig);
+        assert_eq!(
+            quote!(fn foo(&self, x: fn(&dyn T));).to_string(),
+            quote!(#meth).to_string()
+        );
+    }
+
+    #[test]
+    fn plain() {
+        let mut meth: TraitItemMethod = parse2(quote!(
+            fn foo(&self, x: &dyn T);
+        )).unwrap();
+        add_lifetime_parameters(&mut meth.sig);
+        assert_eq!(
+            quote!(fn foo<'__mockall_x>(&self, x: &(dyn T + '__mockall_x));)
+                .to_string(),
+            quote!(#meth).to_string()
+        );
+    }
+
+    #[test]
+    fn slice() {
+        let mut meth: TraitItemMethod = parse2(quote!(
+            fn foo(&self, x: &[&dyn T]);
+        )).unwrap();
+        add_lifetime_parameters(&mut meth.sig);
+        assert_eq!(
+            quote!(fn foo<'__mockall_x>(&self, x: &[&(dyn T + '__mockall_x)]);)
+                .to_string(),
+            quote!(#meth).to_string()
+        );
+    }
+
+    #[test]
+    fn tuple() {
+        let mut meth: TraitItemMethod = parse2(quote!(
+            fn foo(&self, x: (&dyn T, u32));
+        )).unwrap();
+        add_lifetime_parameters(&mut meth.sig);
+        assert_eq!(
+            quote!(fn foo<'__mockall_x>(&self, x: (&(dyn T + '__mockall_x), u32));)
+                .to_string(),
+            quote!(#meth).to_string()
+        );
+    }
+
+    #[test]
+    fn with_anonymous_lifetime() {
+        let mut meth: TraitItemMethod = parse2(quote!(
+            fn foo(&self, x: &(dyn T + '_));
+        )).unwrap();
+        add_lifetime_parameters(&mut meth.sig);
+        assert_eq!(
+            quote!(fn foo(&self, x: &(dyn T + '_));).to_string(),
+            quote!(#meth).to_string()
+        );
+    }
+
+    #[test]
+    fn with_parens() {
+        let mut meth: TraitItemMethod = parse2(quote!(
+            fn foo(&self, x: &(dyn T));
+        )).unwrap();
+        add_lifetime_parameters(&mut meth.sig);
+        assert_eq!(
+            quote!(fn foo<'__mockall_x>(&self, x: &(dyn T + '__mockall_x));)
+                .to_string(),
+            quote!(#meth).to_string()
+        );
+    }
+
+    #[test]
+    fn with_lifetime_parameter() {
+        let mut meth: TraitItemMethod = parse2(quote!(
+            fn foo<'a>(&self, x: &(dyn T + 'a));
+        )).unwrap();
+        add_lifetime_parameters(&mut meth.sig);
+        assert_eq!(
+            quote!(fn foo<'a>(&self, x: &(dyn T + 'a));).to_string(),
+            quote!(#meth).to_string()
+        );
+    }
+
+    #[test]
+    fn with_static_lifetime() {
+        let mut meth: TraitItemMethod = parse2(quote!(
+            fn foo(&self, x: &(dyn T + 'static));
+        )).unwrap();
+        add_lifetime_parameters(&mut meth.sig);
+        assert_eq!(
+            quote!(fn foo(&self, x: &(dyn T + 'static));).to_string(),
+            quote!(#meth).to_string()
+        );
+    }
+
+    }
+}


Test patch:
diff --git a/mockall/tests/automock_trait_object.rs b/mockall/tests/automock_trait_object.rs
new file mode 100644
index 00000000..78f93a01
--- /dev/null
+++ b/mockall/tests/automock_trait_object.rs
@@ -0,0 +1,42 @@
+// vim: tw=80
+//! a method that uses unboxed trait object arguments
+#![deny(warnings)]
+
+use mockall::*;
+
+#[automock]
+trait Foo {
+    fn foo(&self, x: &dyn PartialEq<u32>);
+}
+
+// It's almost impossible to construct a Predicate object that will work with
+// trait objects for two reasons:
+// * Mockall requires the predicates to be Debug, and any useful predicate will
+//   also be Eq, Ord, or similar, but Rust only allows up to one non-auto trait
+//   per trait object.
+// * The predicate is requird to meet the HRTB
+//   for<'a> Predicate<(dyn Trait + 'a)>
+//   That's not impossible, but Mockall doesn't provide any way to construct
+//   such a predicate.
+// So, use of `with` is pretty much limited to predicates like `always` and
+// `function`.
+#[test]
+fn with() {
+    let mut mock = MockFoo::new();
+    mock.expect_foo()
+        .with(predicate::always())
+        .return_const(());
+    mock.foo(&42u32)
+}
+
+/// trait object arguments can't be matched with `predicate::eq`, because
+/// `PartialEq<Self>` cannot be made into a trait object.  But withf still
+/// works.
+#[test]
+fn withf() {
+    let mut mock = MockFoo::new();
+    mock.expect_foo()
+        .withf(|x| *x == 42u32)
+        .return_const(());
+    mock.foo(&42u32);
+}