mockall icon indicating copy to clipboard operation
mockall copied to clipboard

Unexpected `undeclared lifetime` error

Open chris-zen opened this issue 4 years ago • 6 comments

I have not been able to compile the following mock (neither with mock! nor automock):

  mockall::mock! {
    TestAccountsReportWriter {}
    #[async_trait(?Send)]
    impl AccountsReportWriter for TestAccountsReportWriter {
      async fn write_accounts_report<'a, T>(&'a mut self, report: T) -> anyhow::Result<()>
      where
        T: Iterator<Item = AccountReport> + 'a;
    }
  }

The error I see is:

error[E0261]: use of undeclared lifetime name `'a`
  --> src/processors/simple.rs:87:45
   |
85 |       async fn write_accounts_report<'a, T>(&'a mut self, report: T) -> anyhow::Result<()>
   |                                          - help: consider introducing lifetime `'a` here: `'a,`
86 |       where
87 |         T: Iterator<Item = AccountReport> + 'a;
   |                                             ^^ undeclared lifetime

error[E0261]: use of undeclared lifetime name `'a`
  --> src/processors/simple.rs:87:45
   |
85 |       async fn write_accounts_report<'a, T>(&'a mut self, report: T) -> anyhow::Result<()>
   |                                          - help: consider introducing lifetime `'a` here: `'a,`
86 |       where
87 |         T: Iterator<Item = AccountReport> + 'a;
   |                                             ^^ undeclared lifetime
88 |     }
89 |   }
   |    - lifetime `'a` is missing in item created through this procedural macro

I've tested both versions 0.9.1 and 0.10.0 (using rustc 1.53.0)

It seems like the macro misses to generate the lifetime 'a, and then the compiler complains about it being used in the where clause, but it doesn't complain about its use in other parts of the signature.

I'm pretty new to this library and I don't know where else to look at. Please let me know if you need further information to help with the identification of the problem. Thanks a lot ;-)

chris-zen avatar Jun 27 '21 20:06 chris-zen

I guess this may be related to #293. I've been seeing what's generated by using cargo expand. I've tested this with a simpler trait:

#[mockall::automock]
pub trait MyTrait {
    fn do_something<'a>(&'a self, report: &'a String) -> Result<(), String>;
}

This works perfectly.

On the other hand, if we introduce a new generic parameter, by changing report: &'a String for report: &'a T:

#[mockall::automock]
pub trait MyTrait {
    fn do_something<'a, T>(&'a self, report: &'a T) -> Result<(), String>;
}

We get an error. And the code being generated for generics doesn't take the lifetimes into account:

impl GenericExpectations {
            /// Simulating calling the real method.
            pub fn call<T>(&self, report: &'a T) -> Option<Result<(), String>> {

robertohuertasm avatar Jun 27 '21 22:06 robertohuertasm

There are some limitations when mocking generic methods. First, Mockall doesn't currently doesn't support generic methods that have both generic type parameters and generic lifetime parameters. That could probably be fixed. However, your bigger problem is that Mockall doesn't support generic methods with non-'static generic type arguments. That's a limitation in the language that Mockall can't work around. If you're a language expert, you might chime in at https://internals.rust-lang.org/t/hrtbs-for-unspecified-lifetimes/14868 .

I suggest two work arounds:

  • Bound T by 'static, at least during #[cfg(test)].
  • Manually implement the trait on the mock object in terms of a method that can be mocked. For example, you could unsafely transmute the lifetime to static, making the mock method take only static generic type arguments.

https://docs.rs/mockall/0.10.1/mockall/#methods-with-generic-lifetimes

asomers avatar Jul 04 '21 21:07 asomers

Mockall doesn't currently doesn't support generic methods that have both generic type parameters and generic lifetime parameters. That could probably be fixed.

Is there a ticket or rough estimate of effort to enable this? Curious because I've started integrating mockall into a proof of concept and have a feeling I may run into this.

mattremmel avatar Jul 20 '21 23:07 mattremmel

No ticket for it. And no effort estimate. Sorry.

asomers avatar Jul 21 '21 00:07 asomers

I'm not sure if this is helpful or not, but another small example that reproduces this issue:

struct Ref<'a>(&'a ());
#[mockall::automock]
trait Foo {
    fn bar(&self, x: Ref<'_>) -> Ref<'_>;
    //                   ~~ error: `'_` cannot be used here
    fn baz<'a>(&'a self, x: Ref<'a>) -> Ref<'a>;
    //                          ~~ error: use of undeclared lifetime name `'a`
    fn baw<'a>(&'a self, x: Ref<'a>); // ok.
}

Note that this does not involve any generic types, only generic lifetimes.

nlordell avatar Dec 03 '21 21:12 nlordell

I also ran into this problem mocking a production service, specifically a trait that returned a boxed Stream with a lifetime parameter.

kellpossible avatar Jul 26 '22 02:07 kellpossible