asomers / mockall

A powerful mock object library for Rust
Apache License 2.0
1.54k stars 64 forks source link

Mock for Function returning `!` fails to compile #624

Open wmmc88 opened 1 day ago

wmmc88 commented 1 day ago

I have a bindgen generated ffi function that returns !. This is allowed on stable rust. The code that automock generates fails to compile for stable rust.

Code Example:

mod outer {
    #[cfg_attr(test, mockall::automock)]
    pub mod ffi {
        extern "C" {
            pub fn never_returns() -> !;
        }
    }
}

#[mockall_double::double]
pub use outer::ffi;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn t() {
        let ctx = ffi::never_returns_context();
        ctx.expect();

        unsafe {
            ffi::never_returns();
        }
    }
}

Error:

error[E0658]: the `!` type is experimental
 --> src\lib.rs:5:39
  |
5 |             pub fn never_returns() -> !;
  |                                       ^
  |
  = note: see issue #35121 <https://github.com/rust-lang/rust/issues/35121> for more information
  = help: add `#![feature(never_type)]` to the crate attributes to enable
  = note: this compiler was built on 2024-11-03; consider upgrading it if it is out of date

warning: unreachable call
 --> src\lib.rs:2:22
  |
2 |     #[cfg_attr(test, mockall::automock)]
  |                      ^^^^^^^^^^^^^^^^^
  |                      |
  |                      unreachable call
  |                      any code following this expression is unreachable
  |
  = note: `#[warn(unreachable_code)]` on by default
  = note: this warning originates in the attribute macro `mockall::automock` (in Nightly builds, run with -Z macro-backtrace for more info)

The error[E0658]: the `!` type is experimental error is actually coming from the macro-generated code, not the original ffi declaration:

Expanded macro

```rust // Recursive expansion of automock macro // ====================================== pub mod ffi { extern "C" { pub fn never_returns() -> !; } } #[allow(unused_imports)] #[doc = "Mock version of the `ffi` module"] pub mod mock_ffi { #[allow(missing_docs)] #[allow(clippy::too_many_arguments, clippy::indexing_slicing)] pub mod __never_returns { use super::*; use ::mockall::CaseTreeExt; use ::std::sync::MutexGuard; use ::std::{ boxed::Box, mem, ops::{DerefMut, Range}, sync::Mutex, vec::Vec, }; #[allow(clippy::unused_unit)] enum Rfunc { Default, Expired, Mut(Box ! + Send>), MutSt(::mockall::Fragile !>>), Once(Box ! + Send>), OnceSt(::mockall::Fragile !>>), _Phantom(Box), } impl Rfunc { fn call_mut(&mut self) -> std::result::Result { match self { Rfunc::Default => { use ::mockall::ReturnDefault; ::mockall::DefaultReturner::::return_default() } Rfunc::Expired => Err("called twice, but it returns by move"), Rfunc::Mut(__mockall_f) => ::std::result::Result::Ok(__mockall_f()), Rfunc::MutSt(__mockall_f) => { ::std::result::Result::Ok((__mockall_f.get_mut())()) } Rfunc::Once(_) => { if let Rfunc::Once(mut __mockall_f) = mem::replace(self, Rfunc::Expired) { ::std::result::Result::Ok(__mockall_f()) } else { core::panicking::panic("internal error: entered unreachable code") } } Rfunc::OnceSt(_) => { if let Rfunc::OnceSt(mut __mockall_f) = mem::replace(self, Rfunc::Expired) { ::std::result::Result::Ok((__mockall_f.into_inner())()) } else { core::panicking::panic("internal error: entered unreachable code") } } Rfunc::_Phantom(_) => { core::panicking::panic("internal error: entered unreachable code") } } } } impl std::default::Default for Rfunc { fn default() -> Self { Rfunc::Default } } enum Matcher { Always, Func(Box bool + Send>), FuncSt(::mockall::Fragile bool>>), Pred(Box<()>), _Phantom(Box), } impl Matcher { #[allow(clippy::ptr_arg)] #[allow(clippy::ref_option)] fn matches(&self) -> bool { match self { Matcher::Always => true, Matcher::Func(__mockall_f) => __mockall_f(), Matcher::FuncSt(__mockall_f) => (__mockall_f.get())(), Matcher::Pred(__mockall_pred) => [].iter().all(|__mockall_x| *__mockall_x), _ => core::panicking::panic("internal error: entered unreachable code"), } } } impl Default for Matcher { #[allow(unused_variables)] fn default() -> Self { Matcher::Always } } impl ::std::fmt::Display for Matcher { fn fmt(&self, __mockall_fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { match self { Matcher::Always => __mockall_fmt.write_fmt(core::format_args!("")), Matcher::Func(_) => __mockall_fmt.write_fmt(core::format_args!("")), Matcher::FuncSt(_) => { __mockall_fmt.write_fmt(core::format_args!("")) } Matcher::Pred(__mockall_p) => __mockall_fmt.write_fmt(core::format_args!("",)), _ => core::panicking::panic("internal error: entered unreachable code"), } } } #[doc = r" Holds the stuff that is independent of the output type"] struct Common { matcher: Mutex, seq_handle: Option<::mockall::SeqHandle>, times: ::mockall::Times, } impl std::default::Default for Common { fn default() -> Self { Common { matcher: Mutex::new(Matcher::default()), seq_handle: None, times: ::mockall::Times::default(), } } } impl Common { fn call(&self, desc: &str) { self.times.call().unwrap_or_else(|m| { let desc = alloc::__export::must_use({ let res = alloc::fmt::format(alloc::__export::format_args!( "{}", self.matcher.lock().unwrap() )); res }); { core::panicking::panic_fmt(core::const_format_args!( "{}: Expectation({}) {}", "never_returns", desc, m )); }; }); self.verify_sequence(desc); if ::mockall::ExpectedCalls::TooFew != self.times.is_satisfied() { self.satisfy_sequence() } } fn in_sequence(&mut self, __mockall_seq: &mut ::mockall::Sequence) -> &mut Self { { if !(self.times.is_exact()) { { core::panicking::panic_fmt(core::const_format_args!( "Only Expectations with an exact call count have sequences" )); }; } }; self.seq_handle = Some(__mockall_seq.next_handle()); self } fn is_done(&self) -> bool { self.times.is_done() } #[allow(clippy::ptr_arg)] #[allow(clippy::ref_option)] fn matches(&self) -> bool { self.matcher.lock().unwrap().matches() } #[doc = r" Forbid this expectation from ever being called."] fn never(&mut self) { self.times.never(); } fn satisfy_sequence(&self) { if let Some(__mockall_handle) = &self.seq_handle { __mockall_handle.satisfy() } } #[doc = r" Expect this expectation to be called any number of times"] #[doc = r" contained with the given range."] fn times(&mut self, __mockall_r: MockallR) where MockallR: Into<::mockall::TimesRange>, { self.times.times(__mockall_r) } fn with(&mut self) { let mut __mockall_guard = self.matcher.lock().unwrap(); *__mockall_guard.deref_mut() = Matcher::Pred(Box::new(())); } fn withf(&mut self, __mockall_f: MockallF) where MockallF: Fn() -> bool + Send + 'static, { let mut __mockall_guard = self.matcher.lock().unwrap(); *__mockall_guard.deref_mut() = Matcher::Func(Box::new(__mockall_f)); } fn withf_st(&mut self, __mockall_f: MockallF) where MockallF: Fn() -> bool + 'static, { let mut __mockall_guard = self.matcher.lock().unwrap(); *__mockall_guard.deref_mut() = Matcher::FuncSt(::mockall::Fragile::new(Box::new(__mockall_f))); } fn verify_sequence(&self, desc: &str) { if let Some(__mockall_handle) = &self.seq_handle { __mockall_handle.verify(desc) } } } impl Drop for Common { fn drop(&mut self) { if !::std::thread::panicking() { let desc = alloc::__export::must_use({ let res = alloc::fmt::format(alloc::__export::format_args!( "{}", self.matcher.lock().unwrap() )); res }); match self.times.is_satisfied() { ::mockall::ExpectedCalls::TooFew => { { core::panicking::panic_fmt(core::const_format_args!("{}: Expectation({}) called {} time(s) which is fewer than expected {}","never_returns",desc,self.times.count(),self.times.minimum())); }; } ::mockall::ExpectedCalls::TooMany => { { core::panicking::panic_fmt(core::const_format_args!("{}: Expectation({}) called {} time(s) which is more than expected {}","never_returns",desc,self.times.count(),self.times.maximum())); }; } _ => (), } } } } #[doc = r" Expectation type for methods that return a `'static` type."] #[doc = r" This is the type returned by the `expect_*` methods."] pub struct Expectation { common: Common, rfunc: Mutex, } #[allow(clippy::unused_unit)] impl Expectation { #[doc = r" Call this [`Expectation`] as if it were the real method."] #[doc(hidden)] pub fn call(&self) -> ! { use ::mockall::{ViaDebug, ViaNothing}; self.common.call(&alloc::__export::must_use({ let res = alloc::fmt::format(alloc::__export::format_args!( "mock_ffi::never_returns()", )); res })); self.rfunc .lock() .unwrap() .call_mut() .unwrap_or_else(|message| { let desc = alloc::__export::must_use({ let res = alloc::fmt::format(alloc::__export::format_args!( "{}", self.common.matcher.lock().unwrap() )); res }); { core::panicking::panic_fmt(core::const_format_args!( "{}: Expectation({}) {}", "never_returns", desc, message )); }; }) } #[doc = r" Return a constant value from the `Expectation`"] #[doc = r""] #[doc = r" The output type must be `Clone`. The compiler can't always"] #[doc = r" infer the proper type to use with this method; you will"] #[doc = r" usually need to specify it explicitly. i.e."] #[doc = r" `return_const(42i32)` instead of `return_const(42)`."] #[allow(unused_variables)] pub fn return_const(&mut self, __mockall_c: MockallOutput) -> &mut Self where MockallOutput: Clone + Into + Send + 'static, { self.returning(move || __mockall_c.clone().into()) } #[doc = r" Single-threaded version of"] #[doc = r" [`return_const`](#method.return_const). This is useful for"] #[doc = r" return types that are not `Send`."] #[doc = r""] #[doc = r" The output type must be `Clone`. The compiler can't always"] #[doc = r" infer the proper type to use with this method; you will"] #[doc = r" usually need to specify it explicitly. i.e."] #[doc = r" `return_const(42i32)` instead of `return_const(42)`."] #[doc = r""] #[doc = r" It is a runtime error to call the mock method from a"] #[doc = r" different thread than the one that originally called this"] #[doc = r" method."] #[allow(unused_variables)] pub fn return_const_st( &mut self, __mockall_c: MockallOutput, ) -> &mut Self where MockallOutput: Clone + Into + 'static, { self.returning_st(move || __mockall_c.clone().into()) } #[doc = r" Supply an `FnOnce` closure that will provide the return"] #[doc = r" value for this Expectation. This is useful for return types"] #[doc = r" that aren't `Clone`. It will be an error to call this"] #[doc = r" method multiple times."] pub fn return_once(&mut self, __mockall_f: MockallF) -> &mut Self where MockallF: FnOnce() -> ! + Send + 'static, { { let mut __mockall_guard = self.rfunc.lock().unwrap(); *__mockall_guard.deref_mut() = Rfunc::Once(Box::new(__mockall_f)); } self } #[doc = r" Single-threaded version of"] #[doc = r" [`return_once`](#method.return_once). This is useful for"] #[doc = r" return types that are neither `Send` nor `Clone`."] #[doc = r""] #[doc = r" It is a runtime error to call the mock method from a"] #[doc = r" different thread than the one that originally called this"] #[doc = r" method. It is also a runtime error to call the method more"] #[doc = r" than once."] pub fn return_once_st(&mut self, __mockall_f: MockallF) -> &mut Self where MockallF: FnOnce() -> ! + 'static, { { let mut __mockall_guard = self.rfunc.lock().unwrap(); *__mockall_guard.deref_mut() = Rfunc::OnceSt(::mockall::Fragile::new(Box::new(__mockall_f))); } self } #[doc = r" Supply a closure that will provide the return value for this"] #[doc = r" `Expectation`. The method's arguments are passed to the"] #[doc = r" closure by value."] pub fn returning(&mut self, __mockall_f: MockallF) -> &mut Self where MockallF: FnMut() -> ! + Send + 'static, { { let mut __mockall_guard = self.rfunc.lock().unwrap(); *__mockall_guard.deref_mut() = Rfunc::Mut(Box::new(__mockall_f)); } self } #[doc = r" Single-threaded version of [`returning`](#method.returning)."] #[doc = r" Can be used when the argument or return type isn't `Send`."] #[doc = r""] #[doc = r" It is a runtime error to call the mock method from a"] #[doc = r" different thread than the one that originally called this"] #[doc = r" method."] pub fn returning_st(&mut self, __mockall_f: MockallF) -> &mut Self where MockallF: FnMut() -> ! + 'static, { { let mut __mockall_guard = self.rfunc.lock().unwrap(); *__mockall_guard.deref_mut() = Rfunc::MutSt(::mockall::Fragile::new(Box::new(__mockall_f))); } self } #[doc = r" Add this expectation to a"] #[doc = r" [`Sequence`](../../../mockall/struct.Sequence.html)."] pub fn in_sequence(&mut self, __mockall_seq: &mut ::mockall::Sequence) -> &mut Self { self.common.in_sequence(__mockall_seq); self } fn is_done(&self) -> bool { self.common.is_done() } #[doc = r" Validate this expectation's matcher."] #[allow(clippy::ptr_arg)] #[allow(clippy::ref_option)] fn matches(&self) -> bool { self.common.matches() } #[doc = r" Forbid this expectation from ever being called."] pub fn never(&mut self) -> &mut Self { self.common.never(); self } #[doc = r" Create a new, default, [`Expectation`](struct.Expectation.html)"] pub fn new() -> Self { Self::default() } #[doc = r" Expect this expectation to be called exactly once. Shortcut for"] #[doc = r" [`times(1)`](#method.times)."] pub fn once(&mut self) -> &mut Self { self.times(1) } #[doc = r" Restrict the number of times that that this method may be called."] #[doc = r""] #[doc = r" The argument may be:"] #[doc = r" * A fixed number: `.times(4)`"] #[doc = r" * Various types of range:"] #[doc = r" - `.times(5..10)`"] #[doc = r" - `.times(..10)`"] #[doc = r" - `.times(5..)`"] #[doc = r" - `.times(5..=10)`"] #[doc = r" - `.times(..=10)`"] #[doc = r" * The wildcard: `.times(..)`"] pub fn times(&mut self, __mockall_r: MockallR) -> &mut Self where MockallR: Into<::mockall::TimesRange>, { self.common.times(__mockall_r); self } #[doc = r" Set matching criteria for this Expectation."] #[doc = r""] #[doc = r" The matching predicate can be anything implemening the"] #[doc = r" [`Predicate`](../../../mockall/trait.Predicate.html) trait. Only"] #[doc = r" one matcher can be set per `Expectation` at a time."] pub fn with(&mut self) -> &mut Self { self.common.with(); self } #[doc = r" Set a matching function for this Expectation."] #[doc = r""] #[doc = r" This is equivalent to calling [`with`](#method.with) with a"] #[doc = r" function argument, like `with(predicate::function(f))`."] pub fn withf(&mut self, __mockall_f: MockallF) -> &mut Self where MockallF: Fn() -> bool + Send + 'static, { self.common.withf(__mockall_f); self } #[doc = r" Single-threaded version of [`withf`](#method.withf)."] #[doc = r" Can be used when the argument type isn't `Send`."] pub fn withf_st(&mut self, __mockall_f: MockallF) -> &mut Self where MockallF: Fn() -> bool + 'static, { self.common.withf_st(__mockall_f); self } } impl Default for Expectation { fn default() -> Self { Expectation { common: Common::default(), rfunc: Mutex::new(Rfunc::default()), } } } #[doc = r" A collection of [`Expectation`](struct.Expectations.html)"] #[doc = r" objects. Users will rarely if ever use this struct directly."] #[doc(hidden)] pub struct Expectations(Vec); impl Expectations { #[doc = r" Verify that all current expectations are satisfied and clear"] #[doc = r" them."] pub fn checkpoint(&mut self) -> std::vec::Drain { self.0.drain(..) } #[doc = r" Create a new expectation for this method."] pub fn expect(&mut self) -> &mut Expectation { self.0.push(Expectation::default()); let __mockall_l = self.0.len(); &mut self.0[__mockall_l - 1] } pub const fn new() -> Self { Self(Vec::new()) } } impl Default for Expectations { fn default() -> Self { Expectations::new() } } impl Expectations { #[doc = r" Simulate calling the real method. Every current expectation"] #[doc = r" will be checked in FIFO order and the first one with"] #[doc = r" matching arguments will be used."] pub fn call(&self) -> Option { self.0 .iter() .find(|__mockall_e| { __mockall_e.matches() && (!__mockall_e.is_done() || self.0.len() == 1) }) .map(move |__mockall_e| __mockall_e.call()) } } #[doc(hidden)] pub fn get_expectations() -> &'static ::std::sync::Mutex { static EXPECTATIONS: ::std::sync::Mutex = ::std::sync::Mutex::new(Expectations::new()); &EXPECTATIONS } #[doc = r" Like an [`&Expectation`](struct.Expectation.html) but"] #[doc = r" protected by a Mutex guard. Useful for mocking static"] #[doc = r" methods. Forwards accesses to an `Expectation` object."] pub struct ExpectationGuard<'__mockall_lt> { guard: MutexGuard<'__mockall_lt, Expectations>, i: usize, } #[allow(clippy::unused_unit)] impl<'__mockall_lt> ExpectationGuard<'__mockall_lt> { #[doc(hidden)] pub fn new(mut __mockall_guard: MutexGuard<'__mockall_lt, Expectations>) -> Self { __mockall_guard.expect(); let __mockall_i = __mockall_guard.0.len() - 1; ExpectationGuard { guard: __mockall_guard, i: __mockall_i, } } #[doc = r" Just like"] #[doc = r" [`Expectation::in_sequence`](struct.Expectation.html#method.in_sequence)"] pub fn in_sequence( &mut self, __mockall_seq: &mut ::mockall::Sequence, ) -> &mut Expectation { self.guard.0[self.i].in_sequence(__mockall_seq) } #[doc = r" Just like"] #[doc = r" [`Expectation::never`](struct.Expectation.html#method.never)"] pub fn never(&mut self) -> &mut Expectation { self.guard.0[self.i].never() } #[doc = r" Just like"] #[doc = r" [`Expectation::once`](struct.Expectation.html#method.once)"] pub fn once(&mut self) -> &mut Expectation { self.guard.0[self.i].once() } #[doc = r" Just like"] #[doc = r" [`Expectation::return_const`](struct.Expectation.html#method.return_const)"] pub fn return_const( &mut self, __mockall_c: MockallOutput, ) -> &mut Expectation where MockallOutput: Clone + Into + Send + 'static, { self.guard.0[self.i].return_const(__mockall_c) } #[doc = r" Just like"] #[doc = r" [`Expectation::return_const_st`](struct.Expectation.html#method.return_const_st)"] pub fn return_const_st( &mut self, __mockall_c: MockallOutput, ) -> &mut Expectation where MockallOutput: Clone + Into + 'static, { self.guard.0[self.i].return_const_st(__mockall_c) } #[doc = r" Just like"] #[doc = r" [`Expectation::returning`](struct.Expectation.html#method.returning)"] pub fn returning(&mut self, __mockall_f: MockallF) -> &mut Expectation where MockallF: FnMut() -> ! + Send + 'static, { self.guard.0[self.i].returning(__mockall_f) } #[doc = r" Just like"] #[doc = r" [`Expectation::return_once`](struct.Expectation.html#method.return_once)"] pub fn return_once(&mut self, __mockall_f: MockallF) -> &mut Expectation where MockallF: FnOnce() -> ! + Send + 'static, { self.guard.0[self.i].return_once(__mockall_f) } #[doc = r" Just like"] #[doc = r" [`Expectation::return_once_st`](struct.Expectation.html#method.return_once_st)"] pub fn return_once_st(&mut self, __mockall_f: MockallF) -> &mut Expectation where MockallF: FnOnce() -> ! + 'static, { self.guard.0[self.i].return_once_st(__mockall_f) } #[doc = r" Just like"] #[doc = r" [`Expectation::returning_st`](struct.Expectation.html#method.returning_st)"] pub fn returning_st(&mut self, __mockall_f: MockallF) -> &mut Expectation where MockallF: FnMut() -> ! + 'static, { self.guard.0[self.i].returning_st(__mockall_f) } #[doc = r" Just like"] #[doc = r" [`Expectation::times`](struct.Expectation.html#method.times)"] pub fn times(&mut self, __mockall_r: MockallR) -> &mut Expectation where MockallR: Into<::mockall::TimesRange>, { self.guard.0[self.i].times(__mockall_r) } #[doc = r" Just like"] #[doc = r" [`Expectation::with`](struct.Expectation.html#method.with)"] pub fn with(&mut self) -> &mut Expectation { self.guard.0[self.i].with() } #[doc = r" Just like"] #[doc = r" [`Expectation::withf`](struct.Expectation.html#method.withf)"] pub fn withf(&mut self, __mockall_f: MockallF) -> &mut Expectation where MockallF: Fn() -> bool + Send + 'static, { self.guard.0[self.i].withf(__mockall_f) } #[doc = r" Just like"] #[doc = r" [`Expectation::withf_st`](struct.Expectation.html#method.withf_st)"] pub fn withf_st(&mut self, __mockall_f: MockallF) -> &mut Expectation where MockallF: Fn() -> bool + 'static, { self.guard.0[self.i].withf_st(__mockall_f) } } #[doc = r" Manages the context for expectations of static methods."] #[doc = r""] #[doc = r" Expectations on this method will be validated and cleared when"] #[doc = r" the `Context` object drops. The `Context` object does *not*"] #[doc = r" provide any form of synchronization, so multiple tests that set"] #[doc = r" expectations on the same static method must provide their own."] #[must_use = "Context only serves to create expectations"] pub struct Context { _phantom: ::std::marker::PhantomData>, } impl Context { #[doc = r" Verify that all current expectations for this method are"] #[doc = r" satisfied and clear them."] pub fn checkpoint(&self) { Self::do_checkpoint() } #[doc(hidden)] pub fn do_checkpoint() { let __mockall_timeses = get_expectations() .lock() .unwrap() .checkpoint() .collect::>(); } #[doc = r" Create a new expectation for this method."] #[must_use = "Must set return value when not using the \"nightly\" feature"] pub fn expect<'__mockall_lt>(&self) -> ExpectationGuard<'__mockall_lt> { ExpectationGuard::new(get_expectations().lock().unwrap()) } } impl Default for Context { fn default() -> Self { Context { _phantom: std::marker::PhantomData, } } } impl Drop for Context { fn drop(&mut self) { if ::std::thread::panicking() { let _ = get_expectations() .lock() .map(|mut g| g.checkpoint().collect::>()); } else { Self::do_checkpoint(); } } } } #[no_mangle] pub unsafe extern "C-unwind" fn never_returns() -> ! { use ::mockall::{ViaDebug, ViaNothing}; let no_match_msg = alloc::__export::must_use({ let res = alloc::fmt::format(alloc::__export::format_args!( "{}: No matching expectation found", std::format!("mock_ffi::never_returns()",) )); res }); { let __mockall_guard = __never_returns::get_expectations().lock().unwrap(); __mockall_guard.call() } .expect(&no_match_msg) } #[doc = "Create a [`Context`](__never_returns/struct.Context.html) for mocking the `never_returns` method"] pub fn never_returns_context() -> __never_returns::Context { __never_returns::Context::default() } #[doc = r" Verify that all current expectations for every function in"] #[doc = r" this module are satisfied and clear them."] pub fn checkpoint() { { let __mockall_timeses = __never_returns::get_expectations() .lock() .unwrap() .checkpoint() .collect::>(); } } } ```

Some situations in which supporting mocks for never returning functions would be useful include:

wmmc88 commented 1 day ago

For both #624 and #625:

Is it possible to have the automock macro take in an optional blocklist, such that it does not try to generate mocks for certain free functions in the module?

asomers commented 1 day ago

As you've figured out by now, Mockall needs to generate a lot of code for every mocked function. Just because stable Rust allows something in an extern C declaration, that doesn't mean that stable Rust also allows everything you need to mock that function. You can try on nightly, but there just isn't a way to do it for now.

wmmc88 commented 1 day ago

I was hoping there might be a way to special-case this or have an approach that may work on stable.

From browsing the issues on this repo, it seems like there are users that are trying to use this crate to mock ffi functions. With that in mind, and that it looks like many are using bindgen, would it be possible to extend the automock macro to blocklist functions in a module (so as to not generate mocks for them)?

asomers commented 1 day ago

No, I'm afraid. I don't want to add any new syntax until https://github.com/asomers/mockall/discussions/539 is complete.