From 697d341d7cd622e8ba4bf15cb15f6cacbb76220a Mon Sep 17 00:00:00 2001 From: Alan Somers Date: Tue, 30 Jul 2019 10:41:24 -0600 Subject: [PATCH] Fix mocking methods with "super::" in the signature. Fixes #8 --- mockall/tests/mock_nonpub.rs | 27 +++++++++++ mockall_derive/src/expectation.rs | 38 +++++++++++---- mockall_derive/src/lib.rs | 81 ++++++++++++++++++++++++++++++- 3 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 mockall/tests/mock_nonpub.rs diff --git a/mockall/tests/mock_nonpub.rs b/mockall/tests/mock_nonpub.rs new file mode 100644 index 00000000..54010a71 --- /dev/null +++ b/mockall/tests/mock_nonpub.rs @@ -0,0 +1,27 @@ +// vim: tw=80 +//! methods can use non-public types, as long as the object's visibility is +//! compatible. + +use mockall::*; + +#[allow(unused)] +mod outer { + struct SuperT(); + + mod inner { + use super::super::mock; + + pub(crate) struct PubCrateT(); + struct PrivT(); + + mock! { + Foo { + fn foo(&self, x: PubCrateT) -> PubCrateT; + fn bar(&self, x: PrivT) -> PrivT; + fn baz(&self, x: super::SuperT) -> super::SuperT; + fn bang(&self, x: crate::outer::SuperT) -> crate::outer::SuperT; + fn bean(&self, x: self::PrivT) -> self::PrivT; + } + } + } +} diff --git a/mockall_derive/src/expectation.rs b/mockall_derive/src/expectation.rs index cac2c221..72d5afc2 100644 --- a/mockall_derive/src/expectation.rs +++ b/mockall_derive/src/expectation.rs @@ -78,9 +78,13 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, let static_bound = Lifetime::new("'static", Span::call_site()); - let output = match return_type{ - ReturnType::Default => quote!(()), - ReturnType::Type(_, ty) => { + let mut rt2 = return_type.clone(); + let output = match rt2 { + ReturnType::Default => Box::new(Type::Tuple(TypeTuple { + paren_token: token::Paren::default(), + elems: Punctuated::new() + })), + ReturnType::Type(_, ref mut ty) => { let mut rt = ty.clone(); destrify(&mut rt); if let Some(i) = self_ident { @@ -90,18 +94,22 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, if tr.lifetime.as_ref().map_or(false, |lt| lt.ident == "static") { // Just a static expectation + rt } else { if !tr.mutability.is_some() { ref_expectation = true; } else { ref_mut_expectation = true; } - rt = tr.elem.clone(); + tr.elem.clone() } + } else { + rt } - quote!(#rt) } }; + let supersuper_output = supersuperfy(&output); + let output = quote!(#output); let mut egenerics = generics.clone(); let mut macro_g = Punctuated::::new(); @@ -137,6 +145,10 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, let selfless_args = strip_self(args); let (argnames, argty) = split_args(args); + let supersuper_argty = Punctuated::::from_iter( + argty.iter() + .map(|t| supersuperfy(&t)) + ); let mut argty_tp = argty.clone(); if !argty_tp.empty_or_trailing() { // The non-proc macro static_expectation! always uses trailing @@ -145,6 +157,10 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, argty_tp.push_punct(Token![,](Span::call_site())); } let (altargnames, altargty) = split_args(altargs); + let supersuper_altargty = Punctuated::::from_iter( + altargty.iter() + .map(|t| supersuperfy(&t)) + ); if ref_expectation { quote!( #attrs @@ -228,7 +244,9 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, where #output: Send + Sync {} - ::mockall::generic_expectation_methods!{#vis [#macro_g] [#argty] #output} + ::mockall::generic_expectation_methods!{ + #vis [#macro_g] [#argty] #supersuper_output + } impl GenericExpectations { /// Simulating calling the real method. #vis fn call #bounded_macro_g @@ -503,8 +521,8 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, #attrs pub mod #ident { ::mockall::static_expectation!{ - #vis [#macro_g] [#argnames] [#argty] [#altargnames] - [#altargty] [#matchexprs] #output + #vis [#macro_g] [#argnames] [#supersuper_argty] [#altargnames] + [#supersuper_altargty] [#matchexprs] #supersuper_output } /// Like an [`&Expectation`](struct.Expectation.html) but protected @@ -776,8 +794,8 @@ pub(crate) fn expectation(attrs: &TokenStream, vis: &Visibility, #attrs pub mod #ident { ::mockall::static_expectation!{ - #vis [#macro_g] [#argnames] [#argty] [#altargnames] - [#altargty] [#matchexprs] #output + #vis [#macro_g] [#argnames] [#supersuper_argty] [#altargnames] + [#supersuper_altargty] [#matchexprs] #supersuper_output } }) } diff --git a/mockall_derive/src/lib.rs b/mockall_derive/src/lib.rs index 181370eb..c5c8febd 100644 --- a/mockall_derive/src/lib.rs +++ b/mockall_derive/src/lib.rs @@ -12,7 +12,12 @@ extern crate proc_macro; use cfg_if::cfg_if; use proc_macro2::{Span, TokenStream}; use quote::quote; -use syn::{*, punctuated::Punctuated, spanned::Spanned}; +use syn::{ + *, + punctuated::Pair, + punctuated::Punctuated, + spanned::Spanned +}; mod automock; mod expectation; @@ -186,6 +191,80 @@ fn deselfify(literal_type: &mut Type, actual: &Ident) { } } +fn supersuperfy_path(path: &mut Path) { + if let Some(Pair::Punctuated(t, _)) = path.segments.first() { + if t.ident == "super" { + let ps = PathSegment { + ident: Ident::new("super", path.segments.span()), + arguments: PathArguments::None + }; + path.segments.insert(0, ps.clone()); + path.segments.insert(0, ps); + } + } +} + +/// Replace any references to `super::X` in `original` with `super::super::X`. +fn supersuperfy(original: &Type) -> Type { + let mut output = original.clone(); + fn recurse(t: &mut Type) { + match t { + Type::Slice(s) => { + supersuperfy(s.elem.as_mut()); + }, + Type::Array(a) => { + supersuperfy(a.elem.as_mut()); + }, + Type::Ptr(p) => { + supersuperfy(p.elem.as_mut()); + }, + Type::Reference(r) => { + supersuperfy(r.elem.as_mut()); + }, + Type::BareFn(_bfn) => { + unimplemented!() + }, + Type::Tuple(tuple) => { + for elem in tuple.elems.iter_mut() { + supersuperfy(elem); + } + } + Type::Path(type_path) => { + if let Some(ref _qself) = type_path.qself { + compile_error(type_path.span(), "QSelf is TODO"); + } + supersuperfy_path(&mut type_path.path) + }, + Type::Paren(p) => { + supersuperfy(p.elem.as_mut()); + }, + Type::Group(g) => { + supersuperfy(g.elem.as_mut()); + }, + Type::Macro(_) | Type::Verbatim(_) => { + compile_error(t.span(), + "mockall_derive does not support this type in this position"); + }, + Type::ImplTrait(_) => { + panic!("deimplify should've already been run on this output type"); + }, + Type::TraitObject(tto) => { + for bound in tto.bounds.iter_mut() { + if let TypeParamBound::Trait(tb) = bound { + supersuperfy_path(&mut tb.path) + } + } + }, + Type::Infer(_) | Type::Never(_) => + { + /* Nothing to do */ + } + } + } + recurse(&mut output); + output +} + /// Generate a mock identifier from the regular one: eg "Foo" => "MockFoo" fn gen_mock_ident(ident: &Ident) -> Ident { Ident::new(&format!("Mock{}", ident), ident.span())