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

Support specific impls #274

Merged
merged 3 commits into from
Apr 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [ Unreleased ] - ReleaseDate

### Added

- Added support for specific impls. A specific impl is an implementation of a
trait on a generic struct with specified generic parameters. For example,
`impl Foo for Bar<i32>` as opposed to `impl<T> Foo for Bar<T>`. Mockall does
not yet support generic methods in such traits.
([#274](https://github.com/asomers/mockall/pull/274))

### Changed

- Mockall is picker now about how you mock a trait on a generic struct.
Previously you could usually omit the generics. Now, they're required.
i.e.,
```rust
mock!{
MyStruct<T: Bounds> {...}
impl Foo for MyStruct {...}
}
```
should now be written as
```rust
mock!{
MyStruct<T: Bounds> {...}
impl<T: Bounds> Foo for MyStruct<T> {...}
}
```
([#274](https://github.com/asomers/mockall/pull/274))

### Fixed

- Fixed setting simultaneous expectations with different generic types on
Expand Down
2 changes: 1 addition & 1 deletion mockall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1262,7 +1262,7 @@ pub use mockall_derive::automock;
/// pub MyStruct<T: Clone + 'static> {
/// fn bar(&self) -> u8;
/// }
/// impl Foo for MyStruct {
/// impl<T: Clone + 'static> Foo for MyStruct<T> {
/// fn foo(&self, x: u32);
/// }
/// }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ trait Foo<T> {
}
mock! {
Bar<T: 'static> {}
impl<T: 'static> Foo<T> for Bar {
impl<T: 'static> Foo<T> for Bar<T> {
fn foo(&self, x: T) -> T;
}
}
Expand Down
49 changes: 49 additions & 0 deletions mockall/tests/specific_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// vim: ts=80
#![deny(warnings)]

use mockall::*;

trait Bar {
fn bar(&self);
}
//trait Baz {
//fn baz<Y: 'static>(&self, y: Y);
//}

mock! {
pub Foo<T: 'static> {
}
impl Bar for Foo<u32> {
fn bar(&self);
}
impl Bar for Foo<i32> {
fn bar(&self);
}
// TODO: support specific impls with generic methods, like this
//impl Baz for Foo<u32> {
//fn baz<Y: 'static>(&self, y: Y);
//}
}

#[test]
fn return_const() {
let mut mocku = MockFoo::<u32>::new();
mocku.expect_bar()
.return_const(());
let mut mocki = MockFoo::<i32>::new();
mocki.expect_bar()
.return_const(());

mocku.bar();
mocki.bar();
}

///// Make sure generic methods work with specific impls, too
//#[test]
//fn withf() {
//let mut mocku = MockFoo::<u32>::new();
//mocku.expect_baz::<f32>()
//.withf(|y| y == 3.14159)
//.return_const(());
//mocku.baz::<f32>(3.14159);
//}
19 changes: 19 additions & 0 deletions mockall_derive/src/automock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@ impl Attrs {
}
}

pub(crate) fn substitute_item_impl(&self, item_impl: &mut ItemImpl) {
let (_, trait_path, _) = item_impl.trait_.as_ref()
.expect("Should only be called for trait item impls");
let trait_ident = find_ident_from_path(&trait_path).0;
for item in item_impl.items.iter_mut() {
if let ImplItem::Method(method) = item {
let sig = &mut method.sig;
for fn_arg in sig.inputs.iter_mut() {
if let FnArg::Typed(arg) = fn_arg {
self.substitute_type(&mut arg.ty, &trait_ident);
}
}
if let ReturnType::Type(_, ref mut ty) = &mut sig.output {
self.substitute_type(ty, &trait_ident);
}
}
}
}

fn substitute_path_segment(&self, seg: &mut PathSegment, traitname: &Ident)
{
match &mut seg.arguments {
Expand Down
39 changes: 39 additions & 0 deletions mockall_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,16 @@ fn deselfify(literal_type: &mut Type, actual: &Ident, generics: &Generics) {
}
}

fn find_ident_from_path(path: &Path) -> (Ident, PathArguments) {
if path.segments.len() != 1 {
compile_error(path.span(),
"mockall_derive only supports structs defined in the current module");
return (Ident::new("", path.span()), PathArguments::None);
}
let last_seg = path.segments.last().unwrap();
(last_seg.ident.clone(), last_seg.arguments.clone())
}

fn find_lifetimes_in_tpb(bound: &TypeParamBound) -> HashSet<Lifetime> {
let mut ret = HashSet::default();
match bound {
Expand Down Expand Up @@ -1109,6 +1119,35 @@ mod mock {
assert_contains(&output, quote!(pub(super) fn expect_bean));
assert_contains(&output, quote!(pub(in crate::outer) fn expect_boom));
}

#[test]
fn specific_impl() {
let code = r#"
pub Foo<T: 'static> {}
impl Bar for Foo<u32> {
fn bar(&self);
}
impl Bar for Foo<i32> {
fn bar(&self);
}
"#;
let ts = proc_macro2::TokenStream::from_str(code).unwrap();
let output = do_mock(ts).to_string();
assert_contains(&output, quote!(impl Bar for MockFoo<u32>));
assert_contains(&output, quote!(impl Bar for MockFoo<i32>));
// Ensure we don't duplicate the checkpoint function
assert_not_contains(&output, quote!(
self.Bar_expectations.checkpoint();
self.Bar_expectations.checkpoint();
));
// The expect methods should return specific types, not generic ones
assert_contains(&output, quote!(
pub fn expect_bar(&mut self) -> &mut __mock_MockFoo_Bar::__bar::Expectation<u32>
));
assert_contains(&output, quote!(
pub fn expect_bar(&mut self) -> &mut __mock_MockFoo_Bar::__bar::Expectation<i32>
));
}
}

/// Various tests for overall code generation that are hard or impossible to
Expand Down
32 changes: 23 additions & 9 deletions mockall_derive/src/mock_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -556,15 +556,20 @@ impl MockFunction {
/// # Arguments
///
/// * `modname`: Name of the parent struct's private module
/// * `self_args`: If supplied, these are the
/// AngleBracketedGenericArguments of the self type of the
/// trait impl. e.g. The `T` in `impl Foo for Bar<T>`.
// Supplying modname is an unfortunately hack. Ideally MockFunction
// wouldn't need to know that.
pub fn expect(&self, modname: &Ident) -> impl ToTokens {
pub fn expect(&self, modname: &Ident, self_args: Option<&PathArguments>)
-> impl ToTokens
{
let attrs = AttrFormatter::new(&self.attrs)
.doc(false)
.format();
let name = self.name();
let expect_ident = format_ident!("expect_{}", &name);
let expectation_obj = self.expectation_obj();
let expectation_obj = self.expectation_obj(self_args);
let funcname = &self.sig.ident;
let (_, tg, _) = if self.is_method_generic() {
&self.egenerics
Expand Down Expand Up @@ -610,13 +615,22 @@ impl MockFunction {
}

/// Return the name of this function's expecation object
pub fn expectation_obj(&self) -> impl ToTokens {
fn expectation_obj(&self, self_args: Option<&PathArguments>)
-> impl ToTokens
{
let inner_mod_ident = self.inner_mod_ident();
// staticize any lifetimes. This is necessary for methods that return
// non-static types, because the Expectation itself must be 'static.
let segenerics = staticize(&self.egenerics);
let (_, tg, _) = segenerics.split_for_impl();
quote!(#inner_mod_ident::Expectation #tg)
if let Some(sa) = self_args {
assert!(!self.is_method_generic(),
"specific impls with generic methods are TODO");
quote!(#inner_mod_ident::Expectation #sa)
} else {
// staticize any lifetimes. This is necessary for methods that
// return non-static types, because the Expectation itself must be
// 'static.
let segenerics = staticize(&self.egenerics);
let (_, tg, _) = segenerics.split_for_impl();
quote!(#inner_mod_ident::Expectation #tg)
}
}

/// Return the name of this function's expecations object
Expand Down Expand Up @@ -677,7 +691,7 @@ impl MockFunction {

/// Is the mock method generic (as opposed to a non-generic method of a
/// generic mock struct)?
fn is_method_generic(&self) -> bool {
pub fn is_method_generic(&self) -> bool {
self.call_generics.params.iter().any(|p| {
matches!(p, GenericParam::Type(_))
}) || self.call_generics.where_clause.is_some()
Expand Down
21 changes: 19 additions & 2 deletions mockall_derive/src/mock_item_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use super::*;

use quote::ToTokens;
use std::collections::HashSet;

use crate::{
mock_function::MockFunction,
Expand Down Expand Up @@ -51,6 +52,22 @@ fn phantom_fields(generics: &Generics) -> Vec<TokenStream> {
}).collect()
}

/// Filter out multiple copies of the same trait, even if they're implemented on
/// different types.
fn unique_trait_iter<'a, I: Iterator<Item = &'a MockTrait>>(i: I)
-> impl Iterator<Item = &'a MockTrait>
{
let mut hs = HashSet::<Path>::default();
i.filter(move |mt| {
if hs.contains(&mt.trait_path) {
false
} else {
hs.insert(mt.trait_path.clone());
true
}
})
}

/// A collection of methods defined in one spot
struct Methods(Vec<MockFunction>);

Expand Down Expand Up @@ -205,12 +222,12 @@ impl ToTokens for MockItemStruct {
.collect::<Vec<_>>();
let expects = self.methods.0.iter()
.filter(|meth| !meth.is_static())
.map(|meth| meth.expect(modname))
.map(|meth| meth.expect(modname, None))
.collect::<Vec<_>>();
let method_checkpoints = self.methods.checkpoints();
let new_method = self.new_method();
let priv_mods = self.methods.priv_mods();
let substructs = self.traits.iter()
let substructs = unique_trait_iter(self.traits.iter())
.map(|trait_| {
MockItemTraitImpl {
attrs: trait_.attrs.clone(),
Expand Down
Loading