diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a5340ac..d9c4d113 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [ Unreleased ] - ReleaseDate +## Added + +- Added `#[mockall::concretize]`, which can be used to mock some generic + methods that have non-`'static` generic parameters. It works by turning the + generic arguments into trait objects for the expectation. + ([#408](https://github.com/asomers/mockall/pull/408)) + ## Changed - Raised MSRV to 1.45.0 because futures-task did. diff --git a/mockall/src/lib.rs b/mockall/src/lib.rs index d30f53e2..5ec46291 100644 --- a/mockall/src/lib.rs +++ b/mockall/src/lib.rs @@ -37,7 +37,6 @@ //! * [`impl Trait`](#impl-trait) //! * [`Mocking structs`](#mocking-structs) //! * [`Generic methods`](#generic-methods) -//! * [`Methods with generic lifetimes`](#methods-with-generic-lifetimes) //! * [`Generic traits and structs`](#generic-traits-and-structs) //! * [`Associated types`](#associated-types-1) //! * [`Multiple and inherited traits`](#multiple-and-inherited-traits) @@ -673,12 +672,15 @@ //! //! ## Generic methods //! -//! Generic methods can be mocked, too. Effectively each generic method is an -//! infinite set of regular methods, and each of those works just like any other -//! regular method. The expect_* method is generic, too, and usually must be -//! called with a turbofish. The only restrictions on mocking generic methods -//! are that all generic parameters must be `'static`, and generic lifetime -//! parameters are not allowed. +//! Mocking generic methods is possible, but the exact process depends on +//! whether the parameters are `'static`, non-`'static`, or lifetimes. +//! +//! ### With static parameters +//! +//! With fully `'static` parameters, the mock method is generic and so is its +//! expect_* method. The expect_* method usually must be called with a +//! turbofish. Expectations set with different generic parameters operate +//! completely independently of one another. //! //! ``` //! # use mockall::*; @@ -697,7 +699,15 @@ //! assert_eq!(-5, mock.foo(5i8)); //! ``` //! -//! ## Methods with generic lifetimes +//! ### With non-`static` type parameters +//! +//! Mocking methods with non-`'static` type parameters is harder. The way +//! Mockall does it is by turning the generic parameters into trait objects +//! before evaluating expectations. This makes the expect_* method concrete, +//! rather than generic. It also comes with many restrictions. See +//! [`#[concretize]`](attr.concretize.html) for more details. +//! +//! ### With generic lifetimes //! //! A method with a lifetime parameter is technically a generic method, but //! Mockall treats it like a non-generic method that must work for all possible @@ -1257,6 +1267,60 @@ pub mod examples; /// to choose your own name for the mock structure. pub use mockall_derive::automock; +/// Decorates a method or function to tell Mockall to treat its generic arguments +/// as trait objects when creating expectations. +/// +/// This allows users to use non-`'static` generic parameters, which otherwise +/// can't be mocked. The downsides of using this attribute are: +/// +/// * Mockall can't tell if a parameter isn't `'static`, so you must annotate +/// such methods with the `#[mockall::concretize]` attribute. +/// * Generic methods will share expectations for all argument types. That is, +/// you won't be able to do `my_mock.expect_foo::(...)`. +/// * It can't be used on methods with a closure argument (though this may be +/// fixable). +/// * Concretized methods' expectations may only be matched with `.withf` or +/// `.withf_st`, not `.with`. +/// * It only works for parameters that can be turned into a trait object. +/// may be fixable). +/// * Mockall needs to know how to turn the function argument into a trait +/// object. Given a generic parameter `T`, currently supported patterns are: +/// - `T` +/// - `&T` +/// - `&mut T` +/// - `&[T]` +/// +/// # Examples +/// ``` +/// # use std::path::Path; +/// # use mockall::{automock, concretize}; +/// #[automock] +/// trait Foo { +/// #[mockall::concretize] +/// fn foo>(&self, p: P); +/// } +/// +/// # fn main() { +/// let mut mock = MockFoo::new(); +/// mock.expect_foo() +/// .withf(|p| p.as_ref() == Path::new("/tmp")) +/// .return_const(()); +/// mock.foo(Path::new("/tmp")); +/// # } +/// ``` +/// +/// NB: This attribute must be imported with its canonical name. It won't work +/// otherwise! +/// ```compile_fail +/// use mockall::concretize as something_else; +/// #[mockall::automock] +/// trait Foo { +/// #[something_else] +/// fn foo(&self, t: T); +/// } +/// ``` +pub use mockall_derive::concretize; + /// Manually mock a structure. /// /// Sometimes `automock` can't be used. In those cases you can use `mock!`, diff --git a/mockall/tests/automock_concretize.rs b/mockall/tests/automock_concretize.rs new file mode 100644 index 00000000..dbde0d6c --- /dev/null +++ b/mockall/tests/automock_concretize.rs @@ -0,0 +1,52 @@ +// vim: tw=80 +//! A method whose argument is a common `Deref` target. +//! +//! The Expectation should wotk on the Deref implementor, so that it's 'static. +#![deny(warnings)] + +use mockall::*; +use std::path::Path; + +#[automock] +trait Foo { + #[concretize] + fn foo>(&self, x: P); +} + +#[automock] +pub mod mymod { + #[mockall::concretize] + pub fn bang>(_x: P) { unimplemented!() } +} + +mod generic_arg { + use super::*; + + #[test] + fn withf() { + let mut foo = MockFoo::new(); + foo.expect_foo() + .withf(|p| p.as_ref() == Path::new("/tmp")) + .times(3) + .return_const(()); + foo.foo(Path::new("/tmp")); + foo.foo(Path::new("/tmp").to_owned()); + foo.foo("/tmp"); + } +} + +mod module { + use super::*; + + #[test] + fn withf() { + let ctx = mock_mymod::bang_context(); + ctx.expect() + .withf(|p| p.as_ref() == Path::new("/tmp")) + .times(3) + .return_const(()); + mock_mymod::bang(Path::new("/tmp")); + mock_mymod::bang(Path::new("/tmp").to_owned()); + mock_mymod::bang("/tmp"); + } +} diff --git a/mockall/tests/mock_concretize.rs b/mockall/tests/mock_concretize.rs new file mode 100644 index 00000000..a75cdd82 --- /dev/null +++ b/mockall/tests/mock_concretize.rs @@ -0,0 +1,155 @@ +// vim: tw=80 +//! A method whose argument is a common `Deref` target. +//! +//! The Expectation should wotk on the Deref implementor, so that it's 'static. +#![deny(warnings)] + +use mockall::*; +use std::path::{Path, PathBuf}; + +trait AsRefMut: AsRef + AsMut {} +impl AsRefMut for Q where Q: AsRef + AsMut, T: ?Sized {} + +mock! { + Foo { + #[mockall::concretize] + fn foo>(&self, x: P); + + #[mockall::concretize] + fn boom

(&self, x: P) where P: AsRef; + + #[mockall::concretize] + fn bang>(x: P); + + #[mockall::concretize] + fn boomref>(&self, x: &P); + + #[mockall::concretize] + fn boom_mutref>(&self, x: &mut T); + + #[mockall::concretize] + fn boomv

(&self, x: &[P]) where P: AsRef; + // TODO: mutable slices + + //fn boom_iter(&self, x: I) + //where I: ExactSizeIterator, + //P: AsRef; + + // TODO: combination closure plus concretization + //fn closure_and_generics(&self, x: P, f: F) + //where F: Fn(u32) -> u32 + 'static, + //P: AsRef; + } +} + +mod generic_arg { + use super::*; + + // This is as close as you can come when using `with`, but it fails to + // compile. + //#[test] + //fn with() { + //let mut foo = MockFoo::new(); + //foo.expect_foo() + //.with(predicate::eq(&Path::new("/tmp") as &dyn AsRef)) + //.times(3) + //.return_const(()); + //foo.foo(Path::new("/tmp")); + //foo.foo(Path::new("/tmp").to_owned()); + //foo.foo("/tmp"); + //} + + #[test] + fn withf() { + let mut foo = MockFoo::new(); + foo.expect_foo() + .withf(|p| p.as_ref() == Path::new("/tmp")) + .times(3) + .return_const(()); + foo.foo(Path::new("/tmp")); + foo.foo(PathBuf::from(Path::new("/tmp"))); + foo.foo("/tmp"); + } +} + +mod where_clause { + use super::*; + + #[test] + fn withf() { + let mut foo = MockFoo::new(); + foo.expect_boom() + .withf(|p| p.as_ref() == Path::new("/tmp")) + .times(3) + .return_const(()); + foo.boom(Path::new("/tmp")); + foo.boom(PathBuf::from(Path::new("/tmp"))); + foo.boom("/tmp"); + } +} + +mod mutable_reference_arg { + use super::*; + + #[test] + fn withf() { + let mut foo = MockFoo::new(); + foo.expect_boom_mutref() + .withf(|p| p.as_ref() == "/tmp") + .once() + .returning(|s| s.as_mut().make_ascii_uppercase()); + let mut s = String::from("/tmp"); + foo.boom_mutref(&mut s); + assert_eq!(s, "/TMP"); + } +} + +mod reference_arg { + use super::*; + + #[test] + fn withf() { + let mut foo = MockFoo::new(); + foo.expect_boomref() + .withf(|p| p.as_ref() == Path::new("/tmp")) + .times(3) + .return_const(()); + foo.boomref(&Path::new("/tmp")); + foo.boomref(&PathBuf::from(Path::new("/tmp"))); + foo.boomref(&"/tmp"); + } +} + +mod slice { + use super::*; + + #[test] + fn withf() { + let mut foo = MockFoo::new(); + foo.expect_boomv() + .withf(|v| + v[0].as_ref() == Path::new("/tmp") && + v[1].as_ref() == Path::new("/mnt") + ).times(3) + .return_const(()); + foo.boomv(&[Path::new("/tmp"), Path::new("/mnt")]); + foo.boomv(&[PathBuf::from("/tmp"), PathBuf::from("/mnt")]); + foo.boomv(&["/tmp", "/mnt"]); + } +} + +mod static_method { + use super::*; + + #[test] + fn withf() { + let ctx = MockFoo::bang_context(); + ctx.expect() + .withf(|p| p.as_ref() == Path::new("/tmp")) + .times(3) + .return_const(()); + MockFoo::bang(Path::new("/tmp")); + MockFoo::bang(PathBuf::from(Path::new("/tmp"))); + MockFoo::bang("/tmp"); + } +} diff --git a/mockall_derive/src/lib.rs b/mockall_derive/src/lib.rs index 887a7828..935dfaf6 100644 --- a/mockall_derive/src/lib.rs +++ b/mockall_derive/src/lib.rs @@ -55,6 +55,133 @@ cfg_if! { } } +/// replace generic arguments with concrete trait object arguments +fn concretize_args(gen: &Generics, args: &Punctuated) -> + (Generics, Vec, Vec) +{ + let mut hm = HashMap::default(); + + let mut save_types = |ident: &Ident, tpb: &Punctuated| { + if !tpb.is_empty() { + if let Ok(newty) = parse2::(quote!(&dyn #tpb)) { + // substitute T arguments + let subst_ty: Type = parse2(quote!(#ident)).unwrap(); + hm.insert(subst_ty, (newty.clone(), None)); + + // substitute &T arguments + let subst_ty: Type = parse2(quote!(&#ident)).unwrap(); + hm.insert(subst_ty, (newty, None)); + } else { + compile_error(tpb.span(), + "Type cannot be made into a trait object"); + } + + if let Ok(newty) = parse2::(quote!(&mut dyn #tpb)) { + // substitute &mut T arguments + let subst_ty: Type = parse2(quote!(&mut #ident)).unwrap(); + hm.insert(subst_ty, (newty, None)); + } else { + compile_error(tpb.span(), + "Type cannot be made into a trait object"); + } + + // I wish we could substitute &[T] arguments. But there's no way + // for the mock method to turn &[T] into &[&dyn T]. + if let Ok(newty) = parse2::(quote!(&[&dyn #tpb])) { + let subst_ty: Type = parse2(quote!(&[#ident])).unwrap(); + hm.insert(subst_ty, (newty, Some(tpb.clone()))); + } else { + compile_error(tpb.span(), + "Type cannot be made into a trait object"); + } + } + }; + + for g in gen.params.iter() { + if let GenericParam::Type(tp) = g { + save_types(&tp.ident, &tp.bounds); + // else there had better be a where clause + } + } + if let Some(wc) = &gen.where_clause { + for pred in wc.predicates.iter() { + if let WherePredicate::Type(pt) = pred { + let bounded_ty = &pt.bounded_ty; + if let Ok(ident) = parse2::(quote!(#bounded_ty)) { + save_types(&ident, &pt.bounds); + } else { + // We can't yet handle where clauses this complicated + } + } + } + } + + let outg = Generics { + lt_token: None, + gt_token: None, + params: Punctuated::new(), + where_clause: None + }; + let outargs: Vec = args.iter().map(|arg| { + if let FnArg::Typed(pt) = arg { + let mut immutable_pt = pt.clone(); + demutify_arg(&mut immutable_pt); + if let Some((newty, _)) = hm.get(&pt.ty) { + FnArg::Typed(PatType { + attrs: Vec::default(), + pat: immutable_pt.pat, + colon_token: pt.colon_token, + ty: Box::new(newty.clone()) + }) + } else { + FnArg::Typed(PatType { + attrs: Vec::default(), + pat: immutable_pt.pat, + colon_token: pt.colon_token, + ty: pt.ty.clone() + }) + } + } else { + arg.clone() + } + }).collect(); + + // Finally, Reference any concretizing arguments + // use filter_map to remove the &self argument + let call_exprs = args.iter().filter_map(|arg| { + match arg { + FnArg::Typed(pt) => { + let mut pt2 = pt.clone(); + demutify_arg(&mut pt2); + let pat = &pt2.pat; + if pat_is_self(pat) { + None + } else if let Some((_, newbound)) = hm.get(&pt.ty) { + if let Type::Reference(tr) = &*pt.ty { + if let Type::Slice(_ts) = &*tr.elem { + // Assume _ts is the generic type or we wouldn't be + // here + Some(quote!( + &(0..#pat.len()) + .map(|__mockall_i| &#pat[__mockall_i] as &dyn #newbound) + .collect::>() + )) + } else { + Some(quote!(#pat)) + } + } else { + Some(quote!(&#pat)) + } + } else { + Some(quote!(#pat)) + } + }, + FnArg::Receiver(_) => None, + } + }).collect(); + (outg, outargs, call_exprs) +} + fn deanonymize_lifetime(lt: &mut Lifetime) { if lt.ident == "_" { lt.ident = format_ident!("static"); @@ -588,7 +715,7 @@ impl<'a> AttrFormatter<'a> { self.attrs.iter() .cloned() .filter(|attr| { - let i = attr.path.get_ident(); + let i = attr.path.segments.last().map(|ps| &ps.ident); if i.is_none() { false } else if *i.as_ref().unwrap() == "derive" { @@ -603,6 +730,9 @@ impl<'a> AttrFormatter<'a> { // ignore this attribute. // https://docs.rs/tracing/0.1.23/tracing/attr.instrument.html false + } else if *i.as_ref().unwrap() == "concretize" { + // Internally used attribute. Never emit. + false } else { true } @@ -1074,6 +1204,16 @@ fn do_mock(input: TokenStream) -> TokenStream do_mock_once(input) } +#[proc_macro_attribute] +pub fn concretize( + _attrs: proc_macro::TokenStream, + input: proc_macro::TokenStream) -> proc_macro::TokenStream +{ + // Do nothing. This "attribute" is processed as text by the real proc + // macros. + input +} + #[proc_macro] pub fn mock(input: proc_macro::TokenStream) -> proc_macro::TokenStream { do_mock(input.into()).into() @@ -1266,6 +1406,98 @@ mod automock { } } +mod concretize_args { + use super::*; + + fn check_concretize( + sig: TokenStream, + expected_inputs: &[TokenStream], + expected_call_exprs: &[TokenStream]) + { + let f: Signature = parse2(sig).unwrap(); + let (generics, inputs, call_exprs) = + concretize_args(&f.generics, &f.inputs); + assert!(generics.params.is_empty()); + assert_eq!(inputs.len(), expected_inputs.len()); + assert_eq!(call_exprs.len(), expected_call_exprs.len()); + for i in 0..inputs.len() { + let actual = &inputs[i]; + let exp = &expected_inputs[i]; + assert_eq!(quote!(#actual).to_string(), quote!(#exp).to_string()); + } + for i in 0..call_exprs.len() { + let actual = &call_exprs[i]; + let exp = &expected_call_exprs[i]; + assert_eq!(quote!(#actual).to_string(), quote!(#exp).to_string()); + } + } + + #[test] + fn bystanders() { + check_concretize( + quote!(fn foo>(x: i32, p: P, y: &f64)), + &[quote!(x: i32), quote!(p: &dyn AsRef), quote!(y: &f64)], + &[quote!(x), quote!(&p), quote!(y)] + ); + } + + #[test] + #[should_panic(expected = "Type cannot be made into a trait object.")] + fn multi_bounds() { + check_concretize( + quote!(fn foo + AsMut>(p: P)), + &[quote!(p: &dyn AsRef)], + &[quote!(&p)] + ); + } + + #[test] + fn mutable_reference_arg() { + check_concretize( + quote!(fn foo>(p: &mut P)), + &[quote!(p: &mut dyn AsMut)], + &[quote!(p)] + ); + } + + #[test] + #[should_panic(expected = "Type cannot be made into a trait object.")] + fn mutable_reference_multi_bounds() { + check_concretize( + quote!(fn foo + AsMut>(p: &mut P)), + &[quote!(p: &dyn AsRef)], + &[quote!(&p)] + ); + } + + #[test] + fn reference_arg() { + check_concretize( + quote!(fn foo>(p: &P)), + &[quote!(p: &dyn AsRef)], + &[quote!(p)] + ); + } + + #[test] + fn simple() { + check_concretize( + quote!(fn foo>(p: P)), + &[quote!(p: &dyn AsRef)], + &[quote!(&p)] + ); + } + + #[test] + fn where_clause() { + check_concretize( + quote!(fn foo

(p: P) where P: AsRef), + &[quote!(p: &dyn AsRef)], + &[quote!(&p)] + ); + } +} + mod deimplify { use super::*; diff --git a/mockall_derive/src/mock_function.rs b/mockall_derive/src/mock_function.rs index 0e16ac84..2d695c3d 100644 --- a/mockall_derive/src/mock_function.rs +++ b/mockall_derive/src/mock_function.rs @@ -151,6 +151,7 @@ fn send_syncify(wc: &mut Option, bounded_ty: Type) { pub(crate) struct Builder<'a> { attrs: &'a [Attribute], call_levels: Option, + concretize: bool, levels: usize, parent: Option<&'a Ident>, sig: &'a Signature, @@ -163,6 +164,14 @@ pub(crate) struct Builder<'a> { impl<'a> Builder<'a> { pub fn attrs(&mut self, attrs: &'a[Attribute]) -> &mut Self { self.attrs = attrs; + if attrs.iter() + .any(|attr| + attr.path.segments.last() + .map(|ps| ps.ident == "concretize") + .unwrap_or(false) + ) { + self.concretize = true; + } self } @@ -175,7 +184,12 @@ impl<'a> Builder<'a> { let mut refpredty = Vec::new(); let (mut declosured_generics, declosured_inputs, call_exprs) = - declosurefy(&self.sig.generics, &self.sig.inputs); + if self.concretize { + concretize_args(&self.sig.generics, &self.sig.inputs) + } else { + declosurefy(&self.sig.generics, &self.sig.inputs) + }; + // TODO: make concretize and declosurefy work for the same function for fa in declosured_inputs.iter() { if let FnArg::Typed(pt) = fa { @@ -286,6 +300,7 @@ impl<'a> Builder<'a> { call_exprs, call_generics, call_vis: expectation_visibility(self.vis, call_levels), + concretize: self.concretize, egenerics, cgenerics, fn_params, @@ -329,6 +344,7 @@ impl<'a> Builder<'a> { pub fn new(sig: &'a Signature, vis: &'a Visibility) -> Self { Builder { attrs: &[], + concretize: false, levels: 0, call_levels: None, parent: None, @@ -382,6 +398,8 @@ pub(crate) struct MockFunction { call_generics: Generics, /// Visibility of the mock function itself call_vis: Visibility, + /// Are we turning generic arguments into concrete trait objects? + concretize: bool, /// Generics of the Expectation object egenerics: Generics, /// Generics of the Common object @@ -856,6 +874,21 @@ impl<'a> ToTokens for Common<'a> { let boxed_withargs = argnames.iter() .map(|aa| quote!(Box::new(#aa), )) .collect::(); + let with_method = if self.f.concretize { + quote!( + // No `with` method when concretizing generics + ) + } else { + quote!( + fn with<#with_generics>(&mut self, #with_args) + { + let mut __mockall_guard = self.matcher.lock().unwrap(); + *__mockall_guard.deref_mut() = + Matcher::Pred(Box::new((#boxed_withargs))); + } + ) + }; + quote!( /// Holds the stuff that is independent of the output type struct Common #ig #wc { @@ -927,12 +960,7 @@ impl<'a> ToTokens for Common<'a> { self.times.times(__mockall_r) } - fn with<#with_generics>(&mut self, #with_args) - { - let mut __mockall_guard = self.matcher.lock().unwrap(); - *__mockall_guard.deref_mut() = - Matcher::Pred(Box::new((#boxed_withargs))); - } + #with_method fn withf(&mut self, __mockall_f: MockallF) where MockallF: #hrtb Fn(#( #refpredty, )*) @@ -1003,6 +1031,24 @@ impl<'a> ToTokens for CommonExpectationMethods<'a> { .map(|(argname, id)| quote!(#argname: #id, )) .collect::(); let v = &self.f.privmod_vis; + let with_method = if self.f.concretize { + quote!( + // No `with` method when concretizing generics + ) + } else { + quote!( + /// Set matching criteria for this Expectation. + /// + /// The matching predicate can be anything implemening the + /// [`Predicate`](../../../mockall/trait.Predicate.html) trait. Only + /// one matcher can be set per `Expectation` at a time. + #v fn with<#with_generics>(&mut self, #with_args) -> &mut Self + { + self.common.with(#(#argnames, )*); + self + } + ) + }; quote!( /// Add this expectation to a /// [`Sequence`](../../../mockall/struct.Sequence.html). @@ -1058,16 +1104,7 @@ impl<'a> ToTokens for CommonExpectationMethods<'a> { self } - /// Set matching crieteria for this Expectation. - /// - /// The matching predicate can be anything implemening the - /// [`Predicate`](../../../mockall/trait.Predicate.html) trait. Only - /// one matcher can be set per `Expectation` at a time. - #v fn with<#with_generics>(&mut self, #with_args) -> &mut Self - { - self.common.with(#(#argnames, )*); - self - } + #with_method /// Set a matching function for this Expectation. /// @@ -1181,6 +1218,19 @@ impl<'a> ToTokens for ExpectationGuardCommonMethods<'a> { .map(|(argname, id)| quote!(#argname: #id, )) .collect::(); let v = &self.f.privmod_vis; + let with_method = if self.f.concretize { + quote!() + } else { + quote!( + /// Just like + /// [`Expectation::with`](struct.Expectation.html#method.with) + #v fn with<#with_generics> (&mut self, #with_args) + -> &mut Expectation #tg + { + #expectations.0[self.i].with(#(#argnames, )*) + } + ) + }; quote!( /// Just like /// [`Expectation::in_sequence`](struct.Expectation.html#method.in_sequence) @@ -1273,13 +1323,7 @@ impl<'a> ToTokens for ExpectationGuardCommonMethods<'a> { #expectations.0[self.i].times(__mockall_r) } - /// Just like - /// [`Expectation::with`](struct.Expectation.html#method.with) - #v fn with<#with_generics> (&mut self, #with_args) - -> &mut Expectation #tg - { - #expectations.0[self.i].with(#(#argnames, )*) - } + #with_method /// Just like /// [`Expectation::withf`](struct.Expectation.html#method.withf) @@ -1562,11 +1606,30 @@ impl<'a> ToTokens for Matcher<'a> { let idx = syn::Index::from(i); quote!(__mockall_pred.#idx.eval(#argname),) }).collect::(); - let preds = self.f.predty.iter() + let preds = if self.f.concretize { + quote!(()) + } else { + self.f.predty.iter() .map(|t| quote!(Box + Send>,)) - .collect::(); + .collect::() + }; let predty = &self.f.predty; let refpredty = &self.f.refpredty; + let predmatches_body = if self.f.concretize { + quote!() + } else { + quote!(Matcher::Pred(__mockall_pred) => [#pred_matches].iter().all(|__mockall_x| *__mockall_x),) + }; + let preddbg_body = if self.f.concretize { + quote!() + } else { + quote!( + Matcher::Pred(__mockall_p) => { + write!(__mockall_fmt, #braces, + #(__mockall_p.#indices,)*) + } + ) + }; quote!( enum Matcher #ig #wc { Always, @@ -1588,10 +1651,7 @@ impl<'a> ToTokens for Matcher<'a> { __mockall_f(#(#argnames, )*), Matcher::FuncSt(__mockall_f) => (__mockall_f.get())(#(#argnames, )*), - Matcher::Pred(__mockall_pred) => - [#pred_matches] - .iter() - .all(|__mockall_x| *__mockall_x), + #predmatches_body _ => unreachable!() } } @@ -1612,10 +1672,7 @@ impl<'a> ToTokens for Matcher<'a> { Matcher::Always => write!(__mockall_fmt, ""), Matcher::Func(_) => write!(__mockall_fmt, ""), Matcher::FuncSt(_) => write!(__mockall_fmt, ""), - Matcher::Pred(__mockall_p) => { - write!(__mockall_fmt, #braces, - #(__mockall_p.#indices,)*) - } + #preddbg_body _ => unreachable!(), } }