use super::*;
use syn::parse::{Parse, ParseStream};
fn add_lifetime_parameters(sig: &mut Signature) {
fn add_to_trait_object(generics: &mut Generics, var: &Pat, to: &mut TypeTraitObject) {
let mut has_lifetime = false;
for bound in to.bounds.iter() {
if let TypeParamBound::Lifetime(_) = bound {
has_lifetime = true;
}
}
if ! has_lifetime {
let arg_ident = match *var {
Pat::Wild(_) => {
compile_error(var.span(),
"Mocked methods must have named arguments");
format_ident!("dont_care")
},
Pat::Ident(ref pat_ident) => {
if let Some(r) = &pat_ident.by_ref {
compile_error(r.span(),
"Mockall does not support by-reference argument bindings");
}
if let Some((_at, subpat)) = &pat_ident.subpat {
compile_error(subpat.span(),
"Mockall does not support subpattern bindings");
}
pat_ident.ident.clone()
},
_ => {
compile_error(var.span(),
"Unsupported argument type");
format_ident!("dont_care")
}
};
let s = format!("'__mockall_{}", arg_ident);
let span = Span::call_site();
let lt = Lifetime::new(&s, span);
to.bounds.push(TypeParamBound::Lifetime(lt.clone()));
generics.lt_token.get_or_insert(Token![<](span));
generics.gt_token.get_or_insert(Token![>](span));
let gpl = GenericParam::Lifetime(LifetimeDef::new(lt));
generics.params.push(gpl);
}
}
fn add_to_type(generics: &mut Generics, var: &Pat, ty: &mut Type) {
match ty {
Type::Array(ta) => add_to_type(generics, var, ta.elem.as_mut()),
Type::BareFn(_) => (),
Type::ImplTrait(_) => (),
Type::Path(_) => (),
Type::Ptr(_) => (),
Type::Reference(tr) => {
match tr.elem.as_mut() {
Type::Paren(tp) => {
if let Type::TraitObject(to) = tp.elem.as_mut() {
add_to_trait_object(generics, var, to);
} else {
add_to_type(generics, var, tr.elem.as_mut());
}
},
Type::TraitObject(to) => {
add_to_trait_object(generics, var, to);
*tr.elem = Type::Paren(TypeParen {
paren_token: token::Paren::default(),
elem: Box::new(Type::TraitObject(to.clone()))
});
},
_ => add_to_type(generics, var, tr.elem.as_mut()),
}
},
Type::Slice(ts) => add_to_type(generics, var, ts.elem.as_mut()),
Type::Tuple(tt) => {
for ty in tt.elems.iter_mut() {
add_to_type(generics, var, ty)
}
},
_ => compile_error(ty.span(), "unsupported type in this position")
}
}
for arg in sig.inputs.iter_mut() {
if let FnArg::Typed(pt) = arg {
add_to_type(&mut sig.generics, &pt.pat, &mut pt.ty)
}
}
}
fn derive_debug() -> Attribute {
Attribute {
pound_token: <Token![#]>::default(),
style: AttrStyle::Outer,
bracket_token: token::Bracket::default(),
path: Path::from(format_ident!("derive")),
tokens: quote!((Debug))
}
}
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");
}
};
}
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);
}
}
impl_
}
fn mockable_method(meth: &mut ImplItemMethod, name: &Ident, generics: &Generics)
{
demutify(&mut meth.sig.inputs);
deselfify_args(&mut meth.sig.inputs, name, generics);
add_lifetime_parameters(&mut meth.sig);
deimplify(&mut meth.sig.output);
dewhereselfify(&mut meth.sig.generics);
if let ReturnType::Type(_, ty) = &mut meth.sig.output {
deselfify(ty, name, generics);
deanonymize(ty);
}
sanity_check_sig(&meth.sig);
}
fn mockable_trait_method(
meth: &mut TraitItemMethod,
name: &Ident,
generics: &Generics)
{
demutify(&mut meth.sig.inputs);
deselfify_args(&mut meth.sig.inputs, name, generics);
add_lifetime_parameters(&mut meth.sig);
deimplify(&mut meth.sig.output);
dewhereselfify(&mut meth.sig.generics);
if let ReturnType::Type(_, ty) = &mut meth.sig.output {
deselfify(ty, name, generics);
deanonymize(ty);
}
sanity_check_sig(&meth.sig);
}
fn mockable_trait(trait_: ItemTrait, name: &Ident, generics: &Generics)
-> ItemImpl
{
let items = trait_.items.into_iter()
.map(|ti| {
match ti {
TraitItem::Method(mut tim) => {
mockable_trait_method(&mut tim, name, generics);
ImplItem::Method(tim2iim(tim, &Visibility::Inherited))
},
TraitItem::Const(tic) => {
ImplItem::Const(tic2iic(tic, &Visibility::Inherited))
},
TraitItem::Type(tit) => {
ImplItem::Type(tit2iit(tit, &Visibility::Inherited))
},
_ => {
compile_error(ti.span(), "Unsupported in this context");
ImplItem::Verbatim(TokenStream::new())
}
}
}).collect::<Vec<_>>();
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::<AngleBracketedGenericArguments>(quote!(#stg)) {
struct_path.segments.last_mut().unwrap().arguments =
PathArguments::AngleBracketed(abga);
}
if let Ok(abga) = parse2::<AngleBracketedGenericArguments>(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: <Token![impl]>::default(),
generics: generics.clone(),
trait_: Some((None, trait_path, <Token![for]>::default())),
self_ty,
brace_token: trait_.brace_token,
items
}
}
fn sanity_check_sig(sig: &Signature) {
for arg in sig.inputs.iter() {
if let FnArg::Typed(pt) = arg {
if let Type::ImplTrait(it) = pt.ty.as_ref() {
let bounds = &it.bounds;
let s = format!(
"Mockall does not support \"impl trait\" in argument position. Use \"T: {}\" instead",
quote!(#bounds)
);
compile_error(it.span(), &s);
}
}
}
}
fn tic2iic(tic: TraitItemConst, vis: &syn::Visibility) -> ImplItemConst {
let span = tic.span();
let (eq_token, expr) = tic.default.unwrap_or_else(|| {
compile_error(span,
"Mocked associated consts must have a default implementation");
(<Token![=]>::default(), Expr::Verbatim(TokenStream::new()))
});
ImplItemConst {
attrs: tic.attrs,
vis: vis.clone(),
defaultness: None,
const_token: tic.const_token,
ident: tic.ident,
colon_token: tic.colon_token,
ty: tic.ty,
eq_token,
expr,
semi_token: tic.semi_token
}
}
fn tim2iim(m: syn::TraitItemMethod, vis: &syn::Visibility)
-> syn::ImplItemMethod
{
let empty_block = Block {
brace_token: token::Brace::default(),
stmts: Vec::new()
};
syn::ImplItemMethod{
attrs: m.attrs,
vis: vis.clone(),
defaultness: None,
sig: m.sig,
block: empty_block
}
}
fn tit2iit(tit: TraitItemType, vis: &Visibility) -> ImplItemType {
let span = tit.span();
let (eq_token, ty) = tit.default.unwrap_or_else(|| {
compile_error(span,
"associated types in mock! must be fully specified");
(token::Eq::default(), Type::Verbatim(TokenStream::new()))
});
ImplItemType {
attrs: tit.attrs,
vis: vis.clone(),
defaultness: None,
type_token: tit.type_token,
ident: tit.ident,
generics: tit.generics,
eq_token,
ty,
semi_token: tit.semi_token,
}
}
pub(crate) struct MockableStruct {
pub attrs: Vec<Attribute>,
pub consts: Vec<ImplItemConst>,
pub generics: Generics,
pub methods: Vec<ImplItemMethod>,
pub name: Ident,
pub vis: Visibility,
pub impls: Vec<ItemImpl>
}
impl MockableStruct {
pub fn derives_debug(&self) -> bool {
self.attrs.iter()
.any(|attr| {
if let Ok(Meta::List(ml)) = attr.parse_meta() {
let i = ml.path.get_ident();
if i.map_or(false, |i| *i == "derive") {
ml.nested.iter()
.any(|nm| {
if let NestedMeta::Meta(m) = nm {
let i = m.path().get_ident();
i.map_or(false, |i| *i == "Debug")
} else {
false
}
})
} else {
false
}
} else {
false
}
})
}
}
impl From<(Attrs, ItemTrait)> for MockableStruct {
fn from((attrs, item_trait): (Attrs, ItemTrait)) -> MockableStruct {
let trait_ = attrs.substitute_trait(&item_trait);
let mut attrs = trait_.attrs.clone();
attrs.push(derive_debug());
let vis = trait_.vis.clone();
let name = gen_mock_ident(&trait_.ident);
let generics = trait_.generics.clone();
let impls = vec![mockable_trait(trait_, &name, &generics)];
MockableStruct {
attrs,
consts: Vec::new(),
vis,
name,
generics,
methods: Vec::new(),
impls
}
}
}
impl From<ItemImpl> for MockableStruct {
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)
},
x => {
compile_error(x.span(),
"mockall_derive only supports mocking traits and structs");
Ident::new("", Span::call_site())
}
};
let mut attrs = item_impl.attrs.clone();
attrs.push(derive_debug());
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 bang.is_some() {
compile_error(bang.span(), "Unsupported by automock");
}
let mut attrs = Attrs::default();
for item in item_impl.items.iter() {
match item {
ImplItem::Const(_iic) =>
(),
ImplItem::Method(_meth) =>
(),
ImplItem::Type(ty) => {
attrs.attrs.insert(ty.ident.clone(), ty.ty.clone());
},
x => compile_error(x.span(), "Unsupported by automock")
}
}
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 {
ImplItem::Method(mut meth) => {
mockable_method(&mut meth, &name, &item_impl.generics);
methods.push(meth)
},
ImplItem::Const(iic) => consts.push(iic),
x => compile_error(x.span(),
"Unsupported by Mockall in this context"),
}
}
};
MockableStruct {
attrs,
consts,
generics,
methods,
name,
vis,
impls,
}
}
}
impl Parse for MockableStruct {
fn parse(input: ParseStream) -> syn::parse::Result<Self> {
let attrs = input.call(syn::Attribute::parse_outer)?;
let vis: syn::Visibility = input.parse()?;
let original_name: syn::Ident = input.parse()?;
let mut generics: syn::Generics = input.parse()?;
let wc: Option<syn::WhereClause> = input.parse()?;
generics.where_clause = wc;
let name = gen_mock_ident(&original_name);
let impl_content;
let _brace_token = braced!(impl_content in input);
let mut consts = Vec::new();
let mut methods = Vec::new();
while !impl_content.is_empty() {
let item: ImplItem = impl_content.parse()?;
match item {
ImplItem::Method(mut iim) => {
mockable_method(&mut iim, &name, &generics);
methods.push(iim);
},
ImplItem::Const(iic) => consts.push(iic),
_ => {
return Err(input.error("Unsupported in this context"));
}
}
}
let mut impls = Vec::new();
while !input.is_empty() {
let item: Item = input.parse()?;
match item {
Item::Trait(it) => {
let note = "Deprecated mock! syntax. Instead of \"trait X\", write \"impl X for Y\". See PR #205";
let mut impl_ = mockable_trait(it, &name, &generics);
impl_.attrs.push(Attribute {
pound_token: <token::Pound>::default(),
style: AttrStyle::Outer,
bracket_token: token::Bracket::default(),
path: Path::from(format_ident!("deprecated")),
tokens: quote!((since = "0.9.0", note = #note)),
});
impls.push(impl_)
},
Item::Impl(ii) =>
impls.push(mockable_item_impl(ii, &name, &generics)),
_ => return Err(input.error("Unsupported in this context")),
}
}
Ok(
MockableStruct {
attrs,
consts,
generics,
methods,
name,
vis,
impls
}
)
}
}
#[cfg(test)]
mod t {
use super::*;
mod add_lifetime_parameters {
use super::*;
#[test]
fn array() {
let mut meth: TraitItemMethod = parse2(quote!(
fn foo(&self, x: [&dyn T; 1]);
)).unwrap();
add_lifetime_parameters(&mut meth.sig);
assert_eq!(
quote!(fn foo<'__mockall_x>(&self, x: [&(dyn T + '__mockall_x); 1]);)
.to_string(),
quote!(#meth).to_string()
);
}
#[test]
fn bare_fn_with_named_args() {
let mut meth: TraitItemMethod = parse2(quote!(
fn foo(&self, x: fn(&dyn T));
)).unwrap();
add_lifetime_parameters(&mut meth.sig);
assert_eq!(
quote!(fn foo(&self, x: fn(&dyn T));).to_string(),
quote!(#meth).to_string()
);
}
#[test]
fn plain() {
let mut meth: TraitItemMethod = parse2(quote!(
fn foo(&self, x: &dyn T);
)).unwrap();
add_lifetime_parameters(&mut meth.sig);
assert_eq!(
quote!(fn foo<'__mockall_x>(&self, x: &(dyn T + '__mockall_x));)
.to_string(),
quote!(#meth).to_string()
);
}
#[test]
fn slice() {
let mut meth: TraitItemMethod = parse2(quote!(
fn foo(&self, x: &[&dyn T]);
)).unwrap();
add_lifetime_parameters(&mut meth.sig);
assert_eq!(
quote!(fn foo<'__mockall_x>(&self, x: &[&(dyn T + '__mockall_x)]);)
.to_string(),
quote!(#meth).to_string()
);
}
#[test]
fn tuple() {
let mut meth: TraitItemMethod = parse2(quote!(
fn foo(&self, x: (&dyn T, u32));
)).unwrap();
add_lifetime_parameters(&mut meth.sig);
assert_eq!(
quote!(fn foo<'__mockall_x>(&self, x: (&(dyn T + '__mockall_x), u32));)
.to_string(),
quote!(#meth).to_string()
);
}
#[test]
fn with_anonymous_lifetime() {
let mut meth: TraitItemMethod = parse2(quote!(
fn foo(&self, x: &(dyn T + '_));
)).unwrap();
add_lifetime_parameters(&mut meth.sig);
assert_eq!(
quote!(fn foo(&self, x: &(dyn T + '_));).to_string(),
quote!(#meth).to_string()
);
}
#[test]
fn with_parens() {
let mut meth: TraitItemMethod = parse2(quote!(
fn foo(&self, x: &(dyn T));
)).unwrap();
add_lifetime_parameters(&mut meth.sig);
assert_eq!(
quote!(fn foo<'__mockall_x>(&self, x: &(dyn T + '__mockall_x));)
.to_string(),
quote!(#meth).to_string()
);
}
#[test]
fn with_lifetime_parameter() {
let mut meth: TraitItemMethod = parse2(quote!(
fn foo<'a>(&self, x: &(dyn T + 'a));
)).unwrap();
add_lifetime_parameters(&mut meth.sig);
assert_eq!(
quote!(fn foo<'a>(&self, x: &(dyn T + 'a));).to_string(),
quote!(#meth).to_string()
);
}
#[test]
fn with_static_lifetime() {
let mut meth: TraitItemMethod = parse2(quote!(
fn foo(&self, x: &(dyn T + 'static));
)).unwrap();
add_lifetime_parameters(&mut meth.sig);
assert_eq!(
quote!(fn foo(&self, x: &(dyn T + 'static));).to_string(),
quote!(#meth).to_string()
);
}
}
mod sanity_check_sig {
use super::*;
#[test]
#[should_panic(expected = "Mockall does not support \"impl trait\" in argument position. Use \"T: SomeTrait\" instead.")]
fn impl_trait() {
let meth: ImplItemMethod = parse2(quote!(
fn foo(&self, x: impl SomeTrait);
)).unwrap();
sanity_check_sig(&meth.sig);
}
}
}