From b1f0e78057cdde796903f2ce931a8565979821fb Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Wed, 13 Dec 2023 09:33:45 -0700 Subject: [PATCH] Fix using #[concretize] on functions with bounded generic types It's usually possible to turn such types into trait objects. We just need to emit extra parenthesis in the generated code. Fixes #530 --- CHANGELOG.md | 8 ++ mockall/tests/mock_concretize_with_bounds.rs | 135 +++++++++++++++++++ mockall_derive/src/lib.rs | 39 +++--- 3 files changed, 166 insertions(+), 16 deletions(-) create mode 100644 mockall/tests/mock_concretize_with_bounds.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac39a8f..788a5500 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [ Unreleased ] - ReleaseDate + +### Fixed + +- Fixed using `#[mockall::concretize]` on functions whose generic types contain + trait bounds, yet are still object safe. + ([#531](https://github.com/asomers/mockall/pull/531)) + ## [ 0.12.0 ] - 2023-12-10 ### Added diff --git a/mockall/tests/mock_concretize_with_bounds.rs b/mockall/tests/mock_concretize_with_bounds.rs new file mode 100644 index 00000000..76b55a86 --- /dev/null +++ b/mockall/tests/mock_concretize_with_bounds.rs @@ -0,0 +1,135 @@ +// vim: tw=80 +//! Using #[concretize] on generic types with trait bounds +#![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 { + /// Base concretized function + #[mockall::concretize] + fn foo + Send>(&self, x: P); + + /// With a where clause + #[mockall::concretize] + fn boom

(&self, x: P) where P: AsRef + Send; + + /// Static function + #[mockall::concretize] + fn bang + Send>(x: P); + + /// Reference argument + #[mockall::concretize] + fn boomref + Send>(&self, x: &P); + + /// Mutable reference argument + #[mockall::concretize] + fn boom_mutref + Send>(&self, x: &mut T); + + /// Slice argument + #[mockall::concretize] + fn boomv

(&self, x: &[P]) where P: AsRef + Send; + } +} + +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(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 505b4b3e..70705834 100644 --- a/mockall_derive/src/lib.rs +++ b/mockall_derive/src/lib.rs @@ -80,7 +80,7 @@ fn concretize_args(gen: &Generics, args: &Punctuated) -> let mut save_types = |ident: &Ident, tpb: &Punctuated| { if !tpb.is_empty() { - if let Ok(newty) = parse2::(quote!(&dyn #tpb)) { + 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)); @@ -93,7 +93,7 @@ fn concretize_args(gen: &Generics, args: &Punctuated) -> "Type cannot be made into a trait object"); } - if let Ok(newty) = parse2::(quote!(&mut dyn #tpb)) { + 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)); @@ -104,7 +104,7 @@ fn concretize_args(gen: &Generics, args: &Punctuated) -> // 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])) { + 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 { @@ -180,7 +180,7 @@ fn concretize_args(gen: &Generics, args: &Punctuated) -> // here Some(quote!( &(0..#pat.len()) - .map(|__mockall_i| &#pat[__mockall_i] as &dyn #newbound) + .map(|__mockall_i| &#pat[__mockall_i] as &(dyn #newbound)) .collect::>() )) } else { @@ -1491,17 +1491,16 @@ mod concretize_args { 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: 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: &(dyn AsRef + AsMut))], &[quote!(&p)] ); } @@ -1510,18 +1509,17 @@ mod concretize_args { fn mutable_reference_arg() { check_concretize( quote!(fn foo>(p: &mut P)), - &[quote!(p: &mut dyn AsMut)], + &[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)] + &[quote!(p: &mut (dyn AsRef + AsMut))], + &[quote!(p)] ); } @@ -1529,7 +1527,7 @@ mod concretize_args { fn reference_arg() { check_concretize( quote!(fn foo>(p: &P)), - &[quote!(p: &dyn AsRef)], + &[quote!(p: &(dyn AsRef))], &[quote!(p)] ); } @@ -1538,7 +1536,7 @@ mod concretize_args { fn simple() { check_concretize( quote!(fn foo>(p: P)), - &[quote!(p: &dyn AsRef)], + &[quote!(p: &(dyn AsRef))], &[quote!(&p)] ); } @@ -1547,8 +1545,17 @@ mod concretize_args { fn slice() { check_concretize( quote!(fn foo>(p: &[P])), - &[quote!(p: &[&dyn AsRef])], - &[quote!(&(0..p.len()).map(|__mockall_i| &p[__mockall_i] as &dyn AsRef).collect::>())] + &[quote!(p: &[&(dyn AsRef)])], + &[quote!(&(0..p.len()).map(|__mockall_i| &p[__mockall_i] as &(dyn AsRef)).collect::>())] + ); + } + + #[test] + fn slice_with_multi_bounds() { + check_concretize( + quote!(fn foo + AsMut>(p: &[P])), + &[quote!(p: &[&(dyn AsRef + AsMut)])], + &[quote!(&(0..p.len()).map(|__mockall_i| &p[__mockall_i] as &(dyn AsRef + AsMut)).collect::>())] ); } @@ -1556,7 +1563,7 @@ mod concretize_args { fn where_clause() { check_concretize( quote!(fn foo

(p: P) where P: AsRef), - &[quote!(p: &dyn AsRef)], + &[quote!(p: &(dyn AsRef))], &[quote!(&p)] ); }