Skip to content

Commit

Permalink
Merge pull request #531 from asomers/concretize-bounds
Browse files Browse the repository at this point in the history
Fix using #[concretize] on functions with bounded generic types
  • Loading branch information
asomers authored Dec 16, 2023
2 parents 7e4af04 + b1f0e78 commit d4e0710
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 16 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
135 changes: 135 additions & 0 deletions mockall/tests/mock_concretize_with_bounds.rs
Original file line number Diff line number Diff line change
@@ -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<T: ?Sized>: AsRef<T> + AsMut<T> {}
impl<Q, T> AsRefMut<T> for Q where Q: AsRef<T> + AsMut<T>, T: ?Sized {}

mock! {
Foo {
/// Base concretized function
#[mockall::concretize]
fn foo<P: AsRef<std::path::Path> + Send>(&self, x: P);

/// With a where clause
#[mockall::concretize]
fn boom<P>(&self, x: P) where P: AsRef<std::path::Path> + Send;

/// Static function
#[mockall::concretize]
fn bang<P: AsRef<std::path::Path> + Send>(x: P);

/// Reference argument
#[mockall::concretize]
fn boomref<P: AsRef<std::path::Path> + Send>(&self, x: &P);

/// Mutable reference argument
#[mockall::concretize]
fn boom_mutref<T: AsRefMut<str> + Send>(&self, x: &mut T);

/// Slice argument
#[mockall::concretize]
fn boomv<P>(&self, x: &[P]) where P: AsRef<std::path::Path> + 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");
}
}
39 changes: 23 additions & 16 deletions mockall_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ fn concretize_args(gen: &Generics, args: &Punctuated<FnArg, Token![,]>) ->

let mut save_types = |ident: &Ident, tpb: &Punctuated<TypeParamBound, Token![+]>| {
if !tpb.is_empty() {
if let Ok(newty) = parse2::<Type>(quote!(&dyn #tpb)) {
if let Ok(newty) = parse2::<Type>(quote!(&(dyn #tpb))) {
// substitute T arguments
let subst_ty: Type = parse2(quote!(#ident)).unwrap();
hm.insert(subst_ty, (newty.clone(), None));
Expand All @@ -93,7 +93,7 @@ fn concretize_args(gen: &Generics, args: &Punctuated<FnArg, Token![,]>) ->
"Type cannot be made into a trait object");
}

if let Ok(newty) = parse2::<Type>(quote!(&mut dyn #tpb)) {
if let Ok(newty) = parse2::<Type>(quote!(&mut (dyn #tpb))) {
// substitute &mut T arguments
let subst_ty: Type = parse2(quote!(&mut #ident)).unwrap();
hm.insert(subst_ty, (newty, None));
Expand All @@ -104,7 +104,7 @@ fn concretize_args(gen: &Generics, args: &Punctuated<FnArg, Token![,]>) ->

// 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::<Type>(quote!(&[&dyn #tpb])) {
if let Ok(newty) = parse2::<Type>(quote!(&[&(dyn #tpb)])) {
let subst_ty: Type = parse2(quote!(&[#ident])).unwrap();
hm.insert(subst_ty, (newty, Some(tpb.clone())));
} else {
Expand Down Expand Up @@ -180,7 +180,7 @@ fn concretize_args(gen: &Generics, args: &Punctuated<FnArg, Token![,]>) ->
// 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::<Vec<_>>()
))
} else {
Expand Down Expand Up @@ -1491,17 +1491,16 @@ mod concretize_args {
fn bystanders() {
check_concretize(
quote!(fn foo<P: AsRef<Path>>(x: i32, p: P, y: &f64)),
&[quote!(x: i32), quote!(p: &dyn AsRef<Path>), quote!(y: &f64)],
&[quote!(x: i32), quote!(p: &(dyn AsRef<Path>)), 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<P: AsRef<String> + AsMut<String>>(p: P)),
&[quote!(p: &dyn AsRef<Path>)],
&[quote!(p: &(dyn AsRef<String> + AsMut<String>))],
&[quote!(&p)]
);
}
Expand All @@ -1510,26 +1509,25 @@ mod concretize_args {
fn mutable_reference_arg() {
check_concretize(
quote!(fn foo<P: AsMut<Path>>(p: &mut P)),
&[quote!(p: &mut dyn AsMut<Path>)],
&[quote!(p: &mut (dyn AsMut<Path>))],
&[quote!(p)]
);
}

#[test]
#[should_panic(expected = "Type cannot be made into a trait object.")]
fn mutable_reference_multi_bounds() {
check_concretize(
quote!(fn foo<P: AsRef<String> + AsMut<String>>(p: &mut P)),
&[quote!(p: &dyn AsRef<Path>)],
&[quote!(&p)]
&[quote!(p: &mut (dyn AsRef<String> + AsMut<String>))],
&[quote!(p)]
);
}

#[test]
fn reference_arg() {
check_concretize(
quote!(fn foo<P: AsRef<Path>>(p: &P)),
&[quote!(p: &dyn AsRef<Path>)],
&[quote!(p: &(dyn AsRef<Path>))],
&[quote!(p)]
);
}
Expand All @@ -1538,7 +1536,7 @@ mod concretize_args {
fn simple() {
check_concretize(
quote!(fn foo<P: AsRef<Path>>(p: P)),
&[quote!(p: &dyn AsRef<Path>)],
&[quote!(p: &(dyn AsRef<Path>))],
&[quote!(&p)]
);
}
Expand All @@ -1547,16 +1545,25 @@ mod concretize_args {
fn slice() {
check_concretize(
quote!(fn foo<P: AsRef<Path>>(p: &[P])),
&[quote!(p: &[&dyn AsRef<Path>])],
&[quote!(&(0..p.len()).map(|__mockall_i| &p[__mockall_i] as &dyn AsRef<Path>).collect::<Vec<_>>())]
&[quote!(p: &[&(dyn AsRef<Path>)])],
&[quote!(&(0..p.len()).map(|__mockall_i| &p[__mockall_i] as &(dyn AsRef<Path>)).collect::<Vec<_>>())]
);
}

#[test]
fn slice_with_multi_bounds() {
check_concretize(
quote!(fn foo<P: AsRef<Path> + AsMut<String>>(p: &[P])),
&[quote!(p: &[&(dyn AsRef<Path> + AsMut<String>)])],
&[quote!(&(0..p.len()).map(|__mockall_i| &p[__mockall_i] as &(dyn AsRef<Path> + AsMut<String>)).collect::<Vec<_>>())]
);
}

#[test]
fn where_clause() {
check_concretize(
quote!(fn foo<P>(p: P) where P: AsRef<Path>),
&[quote!(p: &dyn AsRef<Path>)],
&[quote!(p: &(dyn AsRef<Path>))],
&[quote!(&p)]
);
}
Expand Down

0 comments on commit d4e0710

Please sign in to comment.