Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

concretize drops lifetimes from already concrete types in methods #620

Open
mathieulj opened this issue Nov 17, 2024 · 3 comments
Open

concretize drops lifetimes from already concrete types in methods #620

mathieulj opened this issue Nov 17, 2024 · 3 comments

Comments

@mathieulj
Copy link

I've been trying to work around #217 in a complex codebase and have a simplified reproduction of a related issue. In my case, the trait in question needs a generic type and lifetime (due to async code).

The following simplified example reproduces the issue:

struct Thing<'a>(&'a ());

#[mockall::automock]
trait Something {
    /// Some method that needs a generic
    // This compiles fine
    #[mockall::concretize]
    fn generic<R: AsRef<()> + 'static>(&self, r: R);

    /// Some method that needs an explicit lifetime
    // This compiles fine
    fn reference<'a>(&self, thing: &Thing<'a>);

    /// Some method that needs an explicit lifetime **and** a generic
    // This errors
    #[mockall::concretize]
    fn generic_and_reference<'a, R: AsRef<()> + 'static>(&self, thing: Thing<'a>, r: R);
}

Compilation of this example errors on the third method with a lifetime error:

error[E0261]: use of undeclared lifetime name `'a`
  --> src/lib.rs:14:78
   |
3  | #[mockall::automock]
   | -                   - help: consider introducing lifetime `'a` here: `<'a>`
   | |
   | lifetime `'a` is missing in item created through this procedural macro
...
14 |     fn generic_and_reference<'a, R: AsRef<()> + 'static>(&self, thing: Thing<'a>, r: R);
   |                                                                              ^^ undeclared lifetime

error[E0261]: use of undeclared lifetime name `'a`
  --> src/lib.rs:14:78
   |
3  | #[mockall::automock]
   |                     -
   |                     |
   |                     lifetime `'a` is missing in item created through this procedural macro
   |                     help: consider introducing lifetime `'a` here: `<'a>`
...
14 |     fn generic_and_reference<'a, R: AsRef<()> + 'static>(&self, thing: Thing<'a>, r: R);
   |                                                                              ^^ undeclared lifetime

error[E0261]: use of undeclared lifetime name `'a`
  --> src/lib.rs:14:78
   |
3  | #[mockall::automock]
   | - lifetime `'a` is missing in item created through this procedural macro
...
14 |     fn generic_and_reference<'a, R: AsRef<()> + 'static>(&self, thing: Thing<'a>, r: R);
   |                                                                              ^^ undeclared lifetime
   |
   = note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
help: consider making the bound lifetime-generic with a new `'a` lifetime
   |
3  | for<'a> #[mockall::automock]
   | +++++++
help: consider introducing lifetime `'a` here
   |
3  | #[mockall::automock]'a,
   |                     +++
help: consider introducing lifetime `'a` here
   |
3  | #[mockall::automock]<'a>
   |                     ++++

For more information about this error, try `rustc --explain E0261`.

Looking at the macro expansion, it looks like concretize removed the generic lifetime.

pub mod __mock_MockSomething_Something {
    ...
    pub mod __generic {
        ...
        enum Rfunc {
            Default,
            Expired,
            Mut(Box<dyn FnMut(&(dyn AsRef<()> + 'static)) -> () + Send>),
            ...
        }
        ...
   }
   ...
    pub mod __reference {
        ...
        enum Rfunc {
            Default,
            Expired,
            Mut(Box<dyn for<'a> FnMut(&Thing<'a>) -> () + Send>),
            ...
        }
        ...
   }
   ...
   pub mod __generic_and_reference {
        ...
        enum Rfunc {
            Default,
            Expired,
            Mut(Box<dyn FnMut(Thing<'a>, &(dyn AsRef<()> + 'static)) -> () + Send>),
@asomers
Copy link
Owner

asomers commented Nov 17, 2024

So the problem is that #[concretize] doesn't work on functions with lifetime parameters. That's unsurprising. There are a billion special cases involving lifetime parameters.

@mathieulj
Copy link
Author

Would you oppose a PR that fixes this particular case? If so, do you have any hints/pointers to share for a first time contributor to mockall (but proficient in Rust)? I'd create a test case based off of mockall/tests/automock_concretize_closures.rs and attempt to make it pass while getting to know the mockall codebase.

@asomers
Copy link
Owner

asomers commented Nov 18, 2024

#610 should probably be fixed before this one. And I think that the first step towards fixing #610 would be to track the mock struct's Generics separately from the trait's. Then generating the code might be as simple as substituting the mock struct's generics in the appropriate places. To make #[concretize] work, we would have to then do some substitutions in both the trait generics and the struct generics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants