From 41feb41d54c0152fe1318527cc8a9b164b9f9565 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 23 Apr 2021 14:11:50 -0600 Subject: [PATCH 1/3] Internal refactor Previously when mock! encountered a trait impl (`impl Foo for Bar`) it would construct a new `Trait` and mock that using the same path used by `#[automock]`. But ever since 0.9.0, it's easier to simply mock the ItemImpl directly. This removes a bunch of internal code, but has no user-visible changes. --- mockall_derive/src/automock.rs | 19 +++ mockall_derive/src/lib.rs | 10 ++ mockall_derive/src/mockable_struct.rs | 182 +++++--------------------- 3 files changed, 65 insertions(+), 146 deletions(-) diff --git a/mockall_derive/src/automock.rs b/mockall_derive/src/automock.rs index 0e426f8f..7c03b040 100644 --- a/mockall_derive/src/automock.rs +++ b/mockall_derive/src/automock.rs @@ -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 { diff --git a/mockall_derive/src/lib.rs b/mockall_derive/src/lib.rs index c7648b6c..78673dab 100644 --- a/mockall_derive/src/lib.rs +++ b/mockall_derive/src/lib.rs @@ -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 { let mut ret = HashSet::default(); match bound { diff --git a/mockall_derive/src/mockable_struct.rs b/mockall_derive/src/mockable_struct.rs index a9ed9ddd..f324bdc6 100644 --- a/mockall_derive/src/mockable_struct.rs +++ b/mockall_derive/src/mockable_struct.rs @@ -92,80 +92,6 @@ fn add_lifetime_parameters(sig: &mut Signature) { } } -/// Filter a generics list, keeping only the elements specified by path_args -/// e.g. filter_generics(, ) -> -fn filter_generics(g: &Generics, path_args: &PathArguments) - -> Generics -{ - let mut params = Punctuated::new(); - match path_args { - PathArguments::None => ()/* No generics selected */, - PathArguments::Parenthesized(p) => { - compile_error(p.span(), - "Mockall does not support mocking Fn objects. See https://github.com/asomers/mockall/issues/139"); - }, - PathArguments::AngleBracketed(abga) => { - let args = &abga.args; - if g.where_clause.is_some() { - compile_error(g.where_clause.span(), - "Mockall does not yet support where clauses here"); - return g.clone(); - } - for param in g.params.iter() { - match param { - GenericParam::Type(tp) => { - let matches = |ga: &GenericArgument| { - if let GenericArgument::Type( - Type::Path(type_path)) = ga - { - type_path.path.is_ident(&tp.ident) - } else { - false - } - }; - if args.iter().any(matches) { - params.push(param.clone()) - } - }, - GenericParam::Lifetime(ld) => { - let matches = |ga: &GenericArgument| { - if let GenericArgument::Lifetime(lt) = ga { - *lt == ld.lifetime - } else { - false - } - }; - if args.iter().any(matches) { - params.push(param.clone()) - } - }, - GenericParam::Const(_) => ()/* Ignore */, - } - } - } - }; - if params.is_empty() { - Generics::default() - } else { - Generics { - lt_token: Some(Token![<](g.span())), - params, - gt_token: Some(Token![>](g.span())), - where_clause: None - } - } -} - -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()) -} - /// Performs transformations on the ItemImpl to make it mockable fn mockable_item_impl(mut impl_: ItemImpl, name: &Ident, generics: &Generics) -> ItemImpl @@ -230,21 +156,30 @@ fn mockable_trait(trait_: ItemTrait, name: &Ident, generics: &Generics) } } }).collect::>(); - let mut path = Path::from(trait_.ident); - let (_, tg, _) = trait_.generics.split_for_impl(); - if let Ok(abga) = parse2::(quote!(#tg)) { - path.segments.last_mut().unwrap().arguments = + let mut trait_path = Path::from(trait_.ident); + let mut struct_path = Path::from(name.clone()); + let (_, stg, _) = generics.split_for_impl(); + let (_, ttg, _) = trait_.generics.split_for_impl(); + if let Ok(abga) = parse2::(quote!(#stg)) { + struct_path.segments.last_mut().unwrap().arguments = PathArguments::AngleBracketed(abga); } + if let Ok(abga) = parse2::(quote!(#ttg)) { + trait_path.segments.last_mut().unwrap().arguments = + PathArguments::AngleBracketed(abga); + } + let self_ty = Box::new(Type::Path(TypePath{ + qself: None, + path: struct_path, + })); ItemImpl { attrs: trait_.attrs, defaultness: None, unsafety: trait_.unsafety, impl_token: ::default(), - generics: trait_.generics, - trait_: Some((None, path, ::default())), - // For now, self_ty is unused - self_ty: Box::new(Type::Verbatim(TokenStream::new())), + generics: generics.clone(), + trait_: Some((None, trait_path, ::default())), + self_ty, brace_token: trait_.brace_token, items } @@ -357,8 +292,8 @@ impl From<(Attrs, ItemTrait)> for MockableStruct { } impl From for MockableStruct { - fn from(item_impl: ItemImpl) -> MockableStruct { - let name = match *item_impl.self_ty { + fn from(mut item_impl: ItemImpl) -> MockableStruct { + let name = match &*item_impl.self_ty { Type::Path(type_path) => { let n = find_ident_from_path(&type_path.path).0; gen_mock_ident(&n) @@ -369,81 +304,36 @@ impl From for MockableStruct { Ident::new("", Span::call_site()) } }; + let attrs = item_impl.attrs.clone(); let mut consts = Vec::new(); + let generics = item_impl.generics.clone(); let mut methods = Vec::new(); let pub_token = Token![pub](Span::call_site()); let vis = Visibility::Public(VisPublic{pub_token}); let mut impls = Vec::new(); - if let Some((bang, path, _)) = item_impl.trait_ { + if let Some((bang, _path, _)) = &item_impl.trait_ { if bang.is_some() { compile_error(bang.span(), "Unsupported by automock"); } - // Regenerate the trait that this block is implementing - let generics = &item_impl.generics; - let traitname = &path.segments.last().unwrap().ident; - let trait_path_args = &path.segments.last().unwrap().arguments; - let mut items = Vec::new(); + // Substitute any associated types in this ItemImpl. + // NB: this would not be necessary if the user always fully + // qualified them, e.g. `::MyType` let mut attrs = Attrs::default(); - for item in item_impl.items.into_iter() { + for item in item_impl.items.iter() { match item { - ImplItem::Const(iic) => - items.push( - TraitItem::Const(TraitItemConst { - attrs: iic.attrs, - const_token: iic.const_token, - ident: iic.ident, - colon_token: iic.colon_token, - ty: iic.ty, - default: Some(( - iic.eq_token, - iic.expr - )), - semi_token: iic.semi_token - }) - ), - ImplItem::Method(meth) => - items.push( - TraitItem::Method(TraitItemMethod { - attrs: meth.attrs, - sig: meth.sig, - default: None, - semi_token: Some(Token![;](Span::call_site())) - }) - ), + ImplItem::Const(_iic) => + (), + ImplItem::Method(_meth) => + (), ImplItem::Type(ty) => { attrs.attrs.insert(ty.ident.clone(), ty.ty.clone()); - items.push( - TraitItem::Type(TraitItemType { - attrs: ty.attrs, - type_token: ty.type_token, - ident: ty.ident, - generics: ty.generics, - colon_token: None, - bounds: Punctuated::new(), - default: Some((ty.eq_token, ty.ty.clone())), - semi_token: ty.semi_token - }) - ); }, x => compile_error(x.span(), "Unsupported by automock") } } - let trait_ = ItemTrait { - attrs: item_impl.attrs.clone(), - vis: vis.clone(), - unsafety: item_impl.unsafety, - auto_token: None, - trait_token: Token![trait](Span::call_site()), - ident: traitname.clone(), - generics: filter_generics(&item_impl.generics, trait_path_args), - colon_token: None, - supertraits: Punctuated::new(), - brace_token: token::Brace::default(), - items - }; - let trait_ = attrs.substitute_trait(&trait_); - impls.push(mockable_trait(trait_, traitname, &generics)); + attrs.substitute_item_impl(&mut item_impl); + impls.push(mockable_item_impl(item_impl, &name, &generics)); } else { for item in item_impl.items.into_iter() { match item { @@ -459,13 +349,13 @@ impl From for MockableStruct { } }; MockableStruct { - attrs: item_impl.attrs, + attrs, consts, - name, - generics: item_impl.generics, + generics, methods, + name, + vis, impls, - vis } } } From 2ffc8f4ad264984cc1ed8fd2ec5be8bc3b55458e Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 23 Apr 2021 14:04:10 -0600 Subject: [PATCH 2/3] Fix some broken but previously-accepted tests. 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 {...} impl Foo for MyStruct {...} } ``` should now be written as ```rust mock!{ MyStruct {...} impl Foo for MyStruct {...} } ``` --- CHANGELOG.md | 20 +++++++++++++++++++ mockall/src/lib.rs | 2 +- ...ith_generic_trait_with_different_bounds.rs | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2491077c..b971394c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [ Unreleased ] - ReleaseDate +### 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 {...} + impl Foo for MyStruct {...} + } + ``` + should now be written as + ```rust + mock!{ + MyStruct {...} + impl Foo for MyStruct {...} + } + ``` + ([#274](https://github.com/asomers/mockall/pull/274)) + ### Fixed - Fixed setting simultaneous expectations with different generic types on diff --git a/mockall/src/lib.rs b/mockall/src/lib.rs index 97ee802a..6f5425a6 100644 --- a/mockall/src/lib.rs +++ b/mockall/src/lib.rs @@ -1262,7 +1262,7 @@ pub use mockall_derive::automock; /// pub MyStruct { /// fn bar(&self) -> u8; /// } -/// impl Foo for MyStruct { +/// impl Foo for MyStruct { /// fn foo(&self, x: u32); /// } /// } diff --git a/mockall/tests/mock_generic_struct_with_generic_trait_with_different_bounds.rs b/mockall/tests/mock_generic_struct_with_generic_trait_with_different_bounds.rs index 96106b90..025d4636 100644 --- a/mockall/tests/mock_generic_struct_with_generic_trait_with_different_bounds.rs +++ b/mockall/tests/mock_generic_struct_with_generic_trait_with_different_bounds.rs @@ -8,7 +8,7 @@ trait Foo { } mock! { Bar {} - impl Foo for Bar { + impl Foo for Bar { fn foo(&self, x: T) -> T; } } From 150c422a66e460b4d4076f324a1efc209754f640 Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Fri, 23 Apr 2021 12:11:30 -0600 Subject: [PATCH 3/3] Support specific impls A specific impl is an implementation of a trait on a generic struct with specific generic parameters, like `impl Foo for Bar {}` Issue #271 --- CHANGELOG.md | 9 +++++ mockall/tests/specific_impl.rs | 49 +++++++++++++++++++++++ mockall_derive/src/lib.rs | 29 ++++++++++++++ mockall_derive/src/mock_function.rs | 32 ++++++++++----- mockall_derive/src/mock_item_struct.rs | 21 +++++++++- mockall_derive/src/mock_trait.rs | 54 ++++++++++++++++++-------- mockall_derive/src/mockable_struct.rs | 20 ++++++++++ 7 files changed, 186 insertions(+), 28 deletions(-) create mode 100644 mockall/tests/specific_impl.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index b971394c..32506727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ 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` as opposed to `impl Foo for Bar`. 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. diff --git a/mockall/tests/specific_impl.rs b/mockall/tests/specific_impl.rs new file mode 100644 index 00000000..6ea9506b --- /dev/null +++ b/mockall/tests/specific_impl.rs @@ -0,0 +1,49 @@ +// vim: ts=80 +#![deny(warnings)] + +use mockall::*; + +trait Bar { + fn bar(&self); +} +//trait Baz { + //fn baz(&self, y: Y); +//} + +mock! { + pub Foo { + } + impl Bar for Foo { + fn bar(&self); + } + impl Bar for Foo { + fn bar(&self); + } + // TODO: support specific impls with generic methods, like this + //impl Baz for Foo { + //fn baz(&self, y: Y); + //} +} + +#[test] +fn return_const() { + let mut mocku = MockFoo::::new(); + mocku.expect_bar() + .return_const(()); + let mut mocki = MockFoo::::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::::new(); + //mocku.expect_baz::() + //.withf(|y| y == 3.14159) + //.return_const(()); + //mocku.baz::(3.14159); +//} diff --git a/mockall_derive/src/lib.rs b/mockall_derive/src/lib.rs index 78673dab..85cd3f1e 100644 --- a/mockall_derive/src/lib.rs +++ b/mockall_derive/src/lib.rs @@ -1119,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 {} + impl Bar for Foo { + fn bar(&self); + } + impl Bar for Foo { + 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)); + assert_contains(&output, quote!(impl Bar for MockFoo)); + // 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 + )); + assert_contains(&output, quote!( + pub fn expect_bar(&mut self) -> &mut __mock_MockFoo_Bar::__bar::Expectation + )); + } } /// Various tests for overall code generation that are hard or impossible to diff --git a/mockall_derive/src/mock_function.rs b/mockall_derive/src/mock_function.rs index 8a3e4cdd..0642d66b 100644 --- a/mockall_derive/src/mock_function.rs +++ b/mockall_derive/src/mock_function.rs @@ -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`. // 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 @@ -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 @@ -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() diff --git a/mockall_derive/src/mock_item_struct.rs b/mockall_derive/src/mock_item_struct.rs index 115c4a1c..c8682ee9 100644 --- a/mockall_derive/src/mock_item_struct.rs +++ b/mockall_derive/src/mock_item_struct.rs @@ -2,6 +2,7 @@ use super::*; use quote::ToTokens; +use std::collections::HashSet; use crate::{ mock_function::MockFunction, @@ -51,6 +52,22 @@ fn phantom_fields(generics: &Generics) -> Vec { }).collect() } +/// Filter out multiple copies of the same trait, even if they're implemented on +/// different types. +fn unique_trait_iter<'a, I: Iterator>(i: I) + -> impl Iterator +{ + let mut hs = HashSet::::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); @@ -205,12 +222,12 @@ impl ToTokens for MockItemStruct { .collect::>(); let expects = self.methods.0.iter() .filter(|meth| !meth.is_static()) - .map(|meth| meth.expect(modname)) + .map(|meth| meth.expect(modname, None)) .collect::>(); 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(), diff --git a/mockall_derive/src/mock_trait.rs b/mockall_derive/src/mock_trait.rs index e4a3dd1a..f0b22833 100644 --- a/mockall_derive/src/mock_trait.rs +++ b/mockall_derive/src/mock_trait.rs @@ -1,4 +1,5 @@ // vim: tw=80 +use proc_macro2::Span; use quote::{ToTokens, format_ident, quote}; use syn::{ *, @@ -13,16 +14,19 @@ use crate::{ pub(crate) struct MockTrait { pub attrs: Vec, pub consts: Vec, - pub struct_generics: Generics, + pub generics: Generics, pub methods: Vec, - pub path: Path, - structname: Ident, + /// Fully-qualified name of the trait + pub trait_path: Path, + /// Path on which the trait is implemented. Usually will be the same as + /// structname, but might include concrete generic parameters. + self_path: PathSegment, pub types: Vec } impl MockTrait { pub fn name(&self) -> &Ident { - &self.path.segments.last().unwrap().ident + &self.trait_path.segments.last().unwrap().ident } /// Create a new MockTrait @@ -40,13 +44,22 @@ impl MockTrait { let mut consts = Vec::new(); let mut methods = Vec::new(); let mut types = Vec::new(); - let path = if let Some((_, path, _)) = impl_.trait_ { + let trait_path = if let Some((_, path, _)) = impl_.trait_ { path } else { compile_error(impl_.span(), "impl block must implement a trait"); Path::from(format_ident!("__mockall_invalid")) }; - let ident = &path.segments.last().unwrap().ident; + let trait_ident = &trait_path.segments.last().unwrap().ident; + let self_path = match *impl_.self_ty { + Type::Path(mut type_path) => + type_path.path.segments.pop().unwrap().into_value(), + x => { + compile_error(x.span(), + "mockall_derive only supports mocking traits and structs"); + PathSegment::from(Ident::new("", Span::call_site())) + } + }; for ii in impl_.items.into_iter() { match ii { @@ -60,7 +73,7 @@ impl MockTrait { .call_levels(0) .struct_(structname) .struct_generics(struct_generics) - .trait_(ident) + .trait_(trait_ident) .build(); methods.push(mf); }, @@ -76,10 +89,10 @@ impl MockTrait { MockTrait { attrs: impl_.attrs, consts, - struct_generics: struct_generics.clone(), + generics: impl_.generics, methods, - path, - structname: structname.clone(), + trait_path, + self_path, types } } @@ -93,8 +106,9 @@ impl MockTrait { // wouldn't need to know that. pub fn trait_impl(&self, modname: &Ident) -> impl ToTokens { let attrs = &self.attrs; - let (ig, tg, wc) = self.struct_generics.split_for_impl(); + let (ig, _tg, wc) = self.generics.split_for_impl(); let consts = &self.consts; + let path_args = &self.self_path.arguments; let calls = self.methods.iter() .map(|meth| meth.call(Some(modname))) .collect::>(); @@ -104,19 +118,25 @@ impl MockTrait { .collect::>(); let expects = self.methods.iter() .filter(|meth| !meth.is_static()) - .map(|meth| meth.expect(&modname)) - .collect::>(); - let path = &self.path; - let structname = &self.structname; + .map(|meth| { + if meth.is_method_generic() { + // Specific impls with generic methods are TODO. + meth.expect(&modname, None) + } else { + meth.expect(&modname, Some(path_args)) + } + }).collect::>(); + let trait_path = &self.trait_path; + let self_path = &self.self_path; let types = &self.types; quote!( #(#attrs)* - impl #ig #path for #structname #tg #wc { + impl #ig #trait_path for #self_path #wc { #(#consts)* #(#types)* #(#calls)* } - impl #ig #structname #tg #wc { + impl #ig #self_path #wc { #(#expects)* #(#contexts)* } diff --git a/mockall_derive/src/mockable_struct.rs b/mockall_derive/src/mockable_struct.rs index f324bdc6..99e83f8a 100644 --- a/mockall_derive/src/mockable_struct.rs +++ b/mockall_derive/src/mockable_struct.rs @@ -92,10 +92,30 @@ fn add_lifetime_parameters(sig: &mut Signature) { } } +/// Add "Mock" to the front of the named type +fn mock_ident_in_type(ty: &mut Type) { + match ty { + Type::Path(type_path) => { + if type_path.path.segments.len() != 1 { + compile_error(type_path.path.span(), + "mockall_derive only supports structs defined in the current module"); + return; + } + let ident = &mut type_path.path.segments.last_mut().unwrap().ident; + *ident = gen_mock_ident(ident) + }, + x => { + compile_error(x.span(), + "mockall_derive only supports mocking traits and structs"); + } + }; +} + /// Performs transformations on the ItemImpl to make it mockable fn mockable_item_impl(mut impl_: ItemImpl, name: &Ident, generics: &Generics) -> ItemImpl { + mock_ident_in_type(&mut impl_.self_ty); for item in impl_.items.iter_mut() { if let ImplItem::Method(ref mut iim) = item { mockable_method(iim, name, generics);