Repository: asomers/mockall (Rust)

Issue:
Support for anonymous lifetimes
This example doesn't work:
```
struct X<'a>(&'a u32);

#[automock]
trait Foo {
    fn foo(&self) -> X<'_>;
}
```
```
`'_` is a reserved lifetime name
```

If I specify the lifetime explicitly it does.


Discussion:
Hm, while I see your point, I don't think the benefit to this feature is very great, since the workaround is trivial.  Is there any method that can't be mocked just by naming the liftetimes?  If not, then I'm not going to give this feature much priority.
>  Is there any method that can't be mocked just by naming the liftetimes?

Hopefully not :)

I agree it's not a priority. 
Just a little annoying that it makes the code more verbose than required. 
But yeah, nothing critical.
`async_trait` workaround kind of needs this
How so, @MOZGIII ?
https://github.com/asomers/mockall/issues/75#issuecomment-554762890
Here you suggest using `'static`, while it really can be just `'_` :smile: 
But that's out of date anyways.
If that's your only concern then I wouldn't say _need_ since you can always specify a named lifetime, just like in https://github.com/asomers/mockall/blob/master/mockall/tests/mock_generic_method_returning_nonstatic.rs .
That would require changing the trait. You can’t always do that, so it’s not a fair solution.
patches welcome.
Ok, I have this working.  However, I still don't really understand the need.  Can you give an example of a real-world trait with such a method?



Patch:
diff --git a/mockall_derive/src/lib.rs b/mockall_derive/src/lib.rs
index fde5b15b..7318677f 100644
--- a/mockall_derive/src/lib.rs
+++ b/mockall_derive/src/lib.rs
@@ -53,6 +53,76 @@ cfg_if! {
     }
 }
 
+fn deanonymize_lifetime(lt: &mut Lifetime) {
+    if lt.ident == "_" {
+        lt.ident = format_ident!("static");
+    }
+}
+
+fn deanonymize_path(path: &mut Path) {
+    for seg in path.segments.iter_mut() {
+        match &mut seg.arguments {
+            PathArguments::None => (),
+            PathArguments::AngleBracketed(abga) => {
+                for ga in abga.args.iter_mut() {
+                    if let GenericArgument::Lifetime(lt) = ga {
+                        deanonymize_lifetime(lt)
+                    }
+                }
+            },
+            _ => compile_error(seg.arguments.span(),
+                "Methods returning functions are TODO"),
+        }
+    }
+}
+
+/// Replace any references to the anonymous lifetime `'_` with `'static`.
+fn deanonymize(literal_type: &mut Type) {
+    match literal_type {
+        Type::Array(ta) => deanonymize(ta.elem.as_mut()),
+        Type::BareFn(tbf) => {
+            if let ReturnType::Type(_, ref mut bt) = tbf.output {
+                deanonymize(bt.as_mut());
+            }
+            for input in tbf.inputs.iter_mut() {
+                deanonymize(&mut input.ty);
+            }
+        },
+        Type::Group(tg) => deanonymize(tg.elem.as_mut()),
+        Type::Infer(_) => (),
+        Type::Never(_) => (),
+        Type::Paren(tp) => deanonymize(tp.elem.as_mut()),
+        Type::Path(tp) => {
+            if let Some(ref _qself) = tp.qself {
+                compile_error(tp.span(), "QSelf is TODO");
+            }
+            deanonymize_path(&mut tp.path);
+        },
+        Type::Ptr(tptr) => deanonymize(tptr.elem.as_mut()),
+        Type::Reference(tr) => {
+            if let Some(lt) = tr.lifetime.as_mut() {
+                deanonymize_lifetime(lt)
+            }
+            deanonymize(tr.elem.as_mut());
+        },
+        Type::Slice(s) => deanonymize(s.elem.as_mut()),
+        Type::TraitObject(tto) => {
+            for tpb in tto.bounds.iter_mut() {
+                match tpb {
+                    TypeParamBound::Trait(tb) => deanonymize_path(&mut tb.path),
+                    TypeParamBound::Lifetime(lt) => deanonymize_lifetime(lt),
+                }
+            }
+        },
+        Type::Tuple(tt) => {
+            for ty in tt.elems.iter_mut() {
+                deanonymize(ty)
+            }
+        }
+        x => compile_error(x.span(), "Unimplemented type for deanonymize")
+    }
+}
+
 // If there are any closures in the argument list, turn them into boxed
 // functions
 fn declosurefy(gen: &Generics, args: &Punctuated<FnArg, Token![,]>) ->
diff --git a/mockall_derive/src/mockable_struct.rs b/mockall_derive/src/mockable_struct.rs
index 31b6bfb4..08766aa2 100644
--- a/mockall_derive/src/mockable_struct.rs
+++ b/mockall_derive/src/mockable_struct.rs
@@ -84,6 +84,7 @@ fn mockable_method(mut meth: ImplItemMethod, name: &Ident, generics: &Generics)
     deimplify(&mut meth.sig.output);
     if let ReturnType::Type(_, ty) = &mut meth.sig.output {
         deselfify(ty, name, generics);
+        deanonymize(ty);
     }
     meth
 }
@@ -98,6 +99,7 @@ fn mockable_trait_method(
     deimplify(&mut meth.sig.output);
     if let ReturnType::Type(_, ty) = &mut meth.sig.output {
         deselfify(ty, name, generics);
+        deanonymize(ty);
     }
 }
 


Test patch:
diff --git a/mockall/tests/mock_return_anonymous_lifetime.rs b/mockall/tests/mock_return_anonymous_lifetime.rs
new file mode 100644
index 00000000..387f9150
--- /dev/null
+++ b/mockall/tests/mock_return_anonymous_lifetime.rs
@@ -0,0 +1,54 @@
+// vim: tw=80
+//! A mock method should be able to return an object parameterized on the
+//! anonymous lifetime.
+//! https://github.com/asomers/mockall/issues/87
+#![deny(warnings)]
+
+use mockall::*;
+
+#[derive(Clone)]
+struct X<'a>(&'a u32);
+
+trait T {
+    fn trait_method(&self) -> X<'_>;
+}
+
+mock! {
+    Foo {
+        fn inherent_method(&self) -> X<'_>;
+    }
+    trait T {
+        fn trait_method(&self) -> X<'_>;
+    }
+}
+
+// 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.
+mod return_nonstatic {
+    use super::*;
+
+    #[test]
+    fn inherent_method() {
+        let d = 42u32;
+        let x = X(&d);
+        let xstatic: X<'static> = unsafe{ std::mem::transmute(x) };
+        let mut mock = MockFoo::new();
+        mock.expect_inherent_method()
+            .returning(move || xstatic.clone());
+
+        assert_eq!(42u32, *mock.inherent_method().0);
+    }
+    #[test]
+    fn trait_method() {
+        let d = 42u32;
+        let x = X(&d);
+        let xstatic: X<'static> = unsafe{ std::mem::transmute(x) };
+        let mut mock = MockFoo::new();
+        mock.expect_trait_method()
+            .returning(move || xstatic.clone());
+
+        assert_eq!(42u32, *mock.trait_method().0);
+    }
+
+}