1 // vim: tw=80
2 use super::*;
3 use syn::parse::{Parse, ParseStream};
4 
5 /// Make any implicit lifetime parameters explicit
add_lifetime_parameters(sig: &mut Signature)6 fn add_lifetime_parameters(sig: &mut Signature) {
7     fn add_to_trait_object(generics: &mut Generics, var: &Pat, to: &mut TypeTraitObject) {
8         let mut has_lifetime = false;
9         for bound in to.bounds.iter() {
10             if let TypeParamBound::Lifetime(_) = bound {
11                 has_lifetime = true;
12             }
13         }
14         if ! has_lifetime {
15             let arg_ident = match *var {
16                 Pat::Wild(_) => {
17                     compile_error(var.span(),
18                         "Mocked methods must have named arguments");
19                     format_ident!("dont_care")
20                 },
21                 Pat::Ident(ref pat_ident) => {
22                     if let Some(r) = &pat_ident.by_ref {
23                         compile_error(r.span(),
24                             "Mockall does not support by-reference argument bindings");
25                     }
26                     if let Some((_at, subpat)) = &pat_ident.subpat {
27                         compile_error(subpat.span(),
28                             "Mockall does not support subpattern bindings");
29                     }
30                     pat_ident.ident.clone()
31                 },
32                 _ => {
33                     compile_error(var.span(),
34                         "Unsupported argument type");
35                     format_ident!("dont_care")
36                 }
37             };
38             let s = format!("'__mockall_{arg_ident}");
39             let span = Span::call_site();
40             let lt = Lifetime::new(&s, span);
41             to.bounds.push(TypeParamBound::Lifetime(lt.clone()));
42             generics.lt_token.get_or_insert(Token![<](span));
43             generics.gt_token.get_or_insert(Token![>](span));
44             let gpl = GenericParam::Lifetime(LifetimeParam::new(lt));
45             generics.params.push(gpl);
46         }
47     }
48 
49     fn add_to_type(generics: &mut Generics, var: &Pat, ty: &mut Type) {
50         match ty {
51             Type::Array(ta) => add_to_type(generics, var, ta.elem.as_mut()),
52             Type::BareFn(_) => (),
53             Type::ImplTrait(_) => (),
54             Type::Path(_) => (),
55             Type::Ptr(_) => (),
56             Type::Reference(tr) => {
57                 match tr.elem.as_mut() {
58                     Type::Paren(tp) => {
59                         if let Type::TraitObject(to) = tp.elem.as_mut() {
60                             add_to_trait_object(generics, var, to);
61                         } else {
62                             add_to_type(generics, var, tr.elem.as_mut());
63                         }
64                     },
65                     Type::TraitObject(to) => {
66                         add_to_trait_object(generics, var, to);
67                         // We need to wrap it in a Paren.  Otherwise it won't be
68                         // syntactically valid after we add a lifetime bound,
69                         // due to a "ambiguous `+` in a type" error
70                         *tr.elem = Type::Paren(TypeParen {
71                             paren_token: token::Paren::default(),
72                             elem: Box::new(Type::TraitObject(to.clone()))
73                         });
74                     },
75                     _ => add_to_type(generics, var, tr.elem.as_mut()),
76                 }
77             },
78             Type::Slice(ts) => add_to_type(generics, var, ts.elem.as_mut()),
79             Type::Tuple(tt) => {
80                 for ty in tt.elems.iter_mut() {
81                     add_to_type(generics, var, ty)
82                 }
83             },
84             _ => compile_error(ty.span(), "unsupported type in this position")
85         }
86     }
87 
88     for arg in sig.inputs.iter_mut() {
89         if let FnArg::Typed(pt) = arg {
90             add_to_type(&mut sig.generics, &pt.pat, &mut pt.ty)
91         }
92     }
93 }
94 
95 /// Generate a #[derive(Debug)] Attribute
derive_debug() -> Attribute96 fn derive_debug() -> Attribute {
97     let ml = parse2(quote!(derive(Debug))).unwrap();
98     Attribute {
99         pound_token: <Token![#]>::default(),
100         style: AttrStyle::Outer,
101         bracket_token: token::Bracket::default(),
102         meta: Meta::List(ml)
103     }
104 }
105 
106 /// Add "Mock" to the front of the named type
mock_ident_in_type(ty: &mut Type)107 fn mock_ident_in_type(ty: &mut Type) {
108     match ty {
109         Type::Path(type_path) => {
110             if type_path.path.segments.len() != 1 {
111                 compile_error(type_path.path.span(),
112                     "mockall_derive only supports structs defined in the current module");
113                 return;
114             }
115             let ident = &mut type_path.path.segments.last_mut().unwrap().ident;
116             *ident = gen_mock_ident(ident)
117         },
118         x => {
119             compile_error(x.span(),
120                 "mockall_derive only supports mocking traits and structs");
121         }
122     };
123 }
124 
125 /// Performs transformations on the ItemImpl to make it mockable
mockable_item_impl(mut impl_: ItemImpl, name: &Ident, generics: &Generics) -> ItemImpl126 fn mockable_item_impl(mut impl_: ItemImpl, name: &Ident, generics: &Generics)
127     -> ItemImpl
128 {
129     mock_ident_in_type(&mut impl_.self_ty);
130     for item in impl_.items.iter_mut() {
131         if let ImplItem::Fn(ref mut iim) = item {
132             mockable_method(iim, name, generics);
133         }
134     }
135     impl_
136 }
137 
138 /// Performs transformations on the method to make it mockable
mockable_method(meth: &mut ImplItemFn, name: &Ident, generics: &Generics)139 fn mockable_method(meth: &mut ImplItemFn, name: &Ident, generics: &Generics)
140 {
141     demutify(&mut meth.sig.inputs);
142     deselfify_args(&mut meth.sig.inputs, name, generics);
143     add_lifetime_parameters(&mut meth.sig);
144     deimplify(&mut meth.sig.output);
145     dewhereselfify(&mut meth.sig.generics);
146     if let ReturnType::Type(_, ty) = &mut meth.sig.output {
147         deselfify(ty, name, generics);
148         deanonymize(ty);
149     }
150     sanity_check_sig(&meth.sig);
151 }
152 
153 /// Performs transformations on the method to make it mockable
mockable_trait_method( meth: &mut TraitItemFn, name: &Ident, generics: &Generics)154 fn mockable_trait_method(
155     meth: &mut TraitItemFn,
156     name: &Ident,
157     generics: &Generics)
158 {
159     demutify(&mut meth.sig.inputs);
160     deselfify_args(&mut meth.sig.inputs, name, generics);
161     add_lifetime_parameters(&mut meth.sig);
162     deimplify(&mut meth.sig.output);
163     dewhereselfify(&mut meth.sig.generics);
164     if let ReturnType::Type(_, ty) = &mut meth.sig.output {
165         deselfify(ty, name, generics);
166         deanonymize(ty);
167     }
168     sanity_check_sig(&meth.sig);
169 }
170 
171 /// Generates a mockable item impl from a trait method definition
mockable_trait(trait_: ItemTrait, name: &Ident, generics: &Generics) -> ItemImpl172 fn mockable_trait(trait_: ItemTrait, name: &Ident, generics: &Generics)
173     -> ItemImpl
174 {
175     let items = trait_.items.into_iter()
176     .map(|ti| {
177         match ti {
178             TraitItem::Fn(mut tif) => {
179                 mockable_trait_method(&mut tif, name, generics);
180                 ImplItem::Fn(tif2iif(tif, &Visibility::Inherited))
181             },
182             TraitItem::Const(tic) => {
183                 ImplItem::Const(tic2iic(tic, &Visibility::Inherited))
184             },
185             TraitItem::Type(tit) => {
186                 ImplItem::Type(tit2iit(tit, &Visibility::Inherited))
187             },
188             _ => {
189                 compile_error(ti.span(), "Unsupported in this context");
190                 ImplItem::Verbatim(TokenStream::new())
191             }
192         }
193     }).collect::<Vec<_>>();
194     let mut trait_path = Path::from(trait_.ident);
195     let mut struct_path = Path::from(name.clone());
196     let (_, stg, _) = generics.split_for_impl();
197     let (_, ttg, _) = trait_.generics.split_for_impl();
198     if let Ok(abga) = parse2::<AngleBracketedGenericArguments>(quote!(#stg)) {
199         struct_path.segments.last_mut().unwrap().arguments =
200             PathArguments::AngleBracketed(abga);
201     }
202     if let Ok(abga) = parse2::<AngleBracketedGenericArguments>(quote!(#ttg)) {
203         trait_path.segments.last_mut().unwrap().arguments =
204             PathArguments::AngleBracketed(abga);
205     }
206     let self_ty = Box::new(Type::Path(TypePath{
207         qself: None,
208         path: struct_path,
209     }));
210     ItemImpl {
211         attrs: trait_.attrs,
212         defaultness: None,
213         unsafety: trait_.unsafety,
214         impl_token: <Token![impl]>::default(),
215         generics: generics.clone(),
216         trait_: Some((None, trait_path, <Token![for]>::default())),
217         self_ty,
218         brace_token: trait_.brace_token,
219         items
220     }
221 }
222 
sanity_check_sig(sig: &Signature)223 fn sanity_check_sig(sig: &Signature) {
224     for arg in sig.inputs.iter() {
225         if let FnArg::Typed(pt) = arg {
226             if let Type::ImplTrait(it) = pt.ty.as_ref() {
227                 let bounds = &it.bounds;
228                 let s = format!(
229                     "Mockall does not support \"impl trait\" in argument position.  Use \"T: {}\" instead",
230                     quote!(#bounds)
231                 );
232                 compile_error(it.span(), &s);
233             }
234         }
235     }
236 }
237 
238 /// Converts a TraitItemConst into an ImplItemConst
tic2iic(tic: TraitItemConst, vis: &syn::Visibility) -> ImplItemConst239 fn tic2iic(tic: TraitItemConst, vis: &syn::Visibility) -> ImplItemConst {
240     let span = tic.span();
241     let (eq_token, expr) = tic.default.unwrap_or_else(|| {
242         compile_error(span,
243             "Mocked associated consts must have a default implementation");
244         (<Token![=]>::default(), Expr::Verbatim(TokenStream::new()))
245     });
246     ImplItemConst {
247         attrs: tic.attrs,
248         vis: vis.clone(),
249         defaultness: None,
250         const_token: tic.const_token,
251         generics: tic.generics,
252         ident: tic.ident,
253         colon_token: tic.colon_token,
254         ty: tic.ty,
255         eq_token,
256         expr,
257         semi_token: tic.semi_token
258     }
259 }
260 
261 /// Converts a TraitItemFn into an ImplItemFn
tif2iif(m: syn::TraitItemFn, vis: &syn::Visibility) -> syn::ImplItemFn262 fn tif2iif(m: syn::TraitItemFn, vis: &syn::Visibility)
263     -> syn::ImplItemFn
264 {
265     let empty_block = Block {
266         brace_token: token::Brace::default(),
267         stmts: Vec::new()
268     };
269     syn::ImplItemFn{
270         attrs: m.attrs,
271         vis: vis.clone(),
272         defaultness: None,
273         sig: m.sig,
274         block: empty_block
275     }
276 }
277 
278 /// Converts a TraitItemType into an ImplItemType
tit2iit(tit: TraitItemType, vis: &Visibility) -> ImplItemType279 fn tit2iit(tit: TraitItemType, vis: &Visibility) -> ImplItemType {
280     let span = tit.span();
281     let (eq_token, ty) = tit.default.unwrap_or_else(|| {
282         compile_error(span,
283             "associated types in mock! must be fully specified");
284         (token::Eq::default(), Type::Verbatim(TokenStream::new()))
285     });
286     ImplItemType {
287         attrs: tit.attrs,
288         vis: vis.clone(),
289         defaultness: None,
290         type_token: tit.type_token,
291         ident: tit.ident,
292         generics: tit.generics,
293         eq_token,
294         ty,
295         semi_token: tit.semi_token,
296     }
297 }
298 
299 /// Like a TraitItemFn, but with a visibility
300 struct TraitItemVFn {
301     pub vis: Visibility,
302     pub tif: TraitItemFn
303 }
304 
305 impl Parse for TraitItemVFn {
parse(input: ParseStream) -> syn::parse::Result<Self>306     fn parse(input: ParseStream) -> syn::parse::Result<Self> {
307         let attrs = input.call(Attribute::parse_outer)?;
308         let vis: syn::Visibility = input.parse()?;
309         let mut tif: TraitItemFn = input.parse()?;
310         tif.attrs = attrs;
311         Ok(Self{vis, tif})
312     }
313 }
314 
315 pub(crate) struct MockableStruct {
316     pub attrs: Vec<Attribute>,
317     pub consts: Vec<ImplItemConst>,
318     pub generics: Generics,
319     /// Inherent methods of the mockable struct
320     pub methods: Vec<ImplItemFn>,
321     pub name: Ident,
322     pub vis: Visibility,
323     pub impls: Vec<ItemImpl>,
324 }
325 
326 impl MockableStruct {
327     /// Does this struct derive Debug?
derives_debug(&self) -> bool328     pub fn derives_debug(&self) -> bool {
329         self.attrs.iter()
330         .any(|attr|{
331             let mut derive_debug = false;
332             if attr.path().is_ident("derive") {
333                 attr.parse_nested_meta(|meta| {
334                     if meta.path.is_ident("Debug") {
335                         derive_debug = true;
336                     }
337                     Ok(())
338                 }).unwrap();
339             }
340             derive_debug
341         })
342     }
343 }
344 
345 impl From<(Attrs, ItemTrait)> for MockableStruct {
from((attrs, item_trait): (Attrs, ItemTrait)) -> MockableStruct346     fn from((attrs, item_trait): (Attrs, ItemTrait)) -> MockableStruct {
347         let trait_ = attrs.substitute_trait(&item_trait);
348         let mut attrs = trait_.attrs.clone();
349         attrs.push(derive_debug());
350         let vis = trait_.vis.clone();
351         let name = gen_mock_ident(&trait_.ident);
352         let generics = trait_.generics.clone();
353         let impls = vec![mockable_trait(trait_, &name, &generics)];
354         MockableStruct {
355             attrs,
356             consts: Vec::new(),
357             vis,
358             name,
359             generics,
360             methods: Vec::new(),
361             impls
362         }
363     }
364 }
365 
366 impl From<ItemImpl> for MockableStruct {
from(mut item_impl: ItemImpl) -> MockableStruct367     fn from(mut item_impl: ItemImpl) -> MockableStruct {
368         let name = match &*item_impl.self_ty {
369             Type::Path(type_path) => {
370                 let n = find_ident_from_path(&type_path.path).0;
371                 gen_mock_ident(&n)
372             },
373             x => {
374                 compile_error(x.span(),
375                     "mockall_derive only supports mocking traits and structs");
376                 Ident::new("", Span::call_site())
377             }
378         };
379         let mut attrs = item_impl.attrs.clone();
380         attrs.push(derive_debug());
381         let mut consts = Vec::new();
382         let generics = item_impl.generics.clone();
383         let mut methods = Vec::new();
384         let vis = Visibility::Public(Token![pub](Span::call_site()));
385         let mut impls = Vec::new();
386         if let Some((bang, _path, _)) = &item_impl.trait_ {
387             if bang.is_some() {
388                 compile_error(bang.span(), "Unsupported by automock");
389             }
390 
391             // Substitute any associated types in this ItemImpl.
392             // NB: this would not be necessary if the user always fully
393             // qualified them, e.g. `<Self as MyTrait>::MyType`
394             let mut attrs = Attrs::default();
395             for item in item_impl.items.iter() {
396                 match item {
397                     ImplItem::Const(_iic) =>
398                         (),
399                     ImplItem::Fn(_meth) =>
400                         (),
401                     ImplItem::Type(ty) => {
402                         attrs.attrs.insert(ty.ident.clone(), ty.ty.clone());
403                     },
404                     x => compile_error(x.span(), "Unsupported by automock")
405                 }
406             }
407             attrs.substitute_item_impl(&mut item_impl);
408             impls.push(mockable_item_impl(item_impl, &name, &generics));
409         } else {
410             for item in item_impl.items.into_iter() {
411                 match item {
412                     ImplItem::Fn(mut meth) => {
413                         mockable_method(&mut meth, &name, &item_impl.generics);
414                         methods.push(meth)
415                     },
416                     ImplItem::Const(iic) => consts.push(iic),
417                     // Rust doesn't allow types in an inherent impl
418                     x => compile_error(x.span(),
419                         "Unsupported by Mockall in this context"),
420                 }
421             }
422         };
423         MockableStruct {
424             attrs,
425             consts,
426             generics,
427             methods,
428             name,
429             vis,
430             impls,
431         }
432     }
433 }
434 
435 impl Parse for MockableStruct {
parse(input: ParseStream) -> syn::parse::Result<Self>436     fn parse(input: ParseStream) -> syn::parse::Result<Self> {
437         let attrs = input.call(syn::Attribute::parse_outer)?;
438         let vis: syn::Visibility = input.parse()?;
439         let original_name: syn::Ident = input.parse()?;
440         let mut generics: syn::Generics = input.parse()?;
441         let wc: Option<syn::WhereClause> = input.parse()?;
442         generics.where_clause = wc;
443         let name = gen_mock_ident(&original_name);
444         let impl_content;
445         let _brace_token = braced!(impl_content in input);
446         let mut consts = Vec::new();
447         let mut methods = Vec::new();
448         while !impl_content.is_empty() {
449             let item: ImplItem = impl_content.parse()?;
450             match item {
451                 ImplItem::Verbatim(ts) => {
452                     let tivf: TraitItemVFn = parse2(ts)?;
453                     let mut iim = tif2iif(tivf.tif, &tivf.vis);
454                     mockable_method(&mut iim, &name, &generics);
455                     methods.push(iim);
456                 }
457                 ImplItem::Const(iic) => consts.push(iic),
458                 _ => {
459                     return Err(input.error("Unsupported in this context"));
460                 }
461             }
462         }
463 
464         let mut impls = Vec::new();
465         while !input.is_empty() {
466             let item: Item = input.parse()?;
467             match item {
468                 Item::Impl(mut ii) => {
469                     for item in ii.items.iter_mut() {
470                         // Convert any methods that syn couldn't parse as
471                         // ImplItemFn.
472                         if let ImplItem::Verbatim(ts) = item {
473                             let tif: TraitItemFn = parse2(ts.clone()).unwrap();
474                             let iim = tif2iif(tif, &Visibility::Inherited);
475                             *item = ImplItem::Fn(iim);
476                         }
477                     }
478                     impls.push(mockable_item_impl(ii, &name, &generics));
479                 }
480                 _ => return Err(input.error("Unsupported in this context")),
481             }
482         }
483 
484         Ok(
485             MockableStruct {
486                 attrs,
487                 consts,
488                 generics,
489                 methods,
490                 name,
491                 vis,
492                 impls
493             }
494         )
495     }
496 }
497 
498 #[cfg(test)]
499 mod t {
500     use super::*;
501 
502 mod add_lifetime_parameters {
503     use super::*;
504 
505     #[test]
array()506     fn array() {
507         let mut meth: TraitItemFn = parse2(quote!(
508             fn foo(&self, x: [&dyn T; 1]);
509         )).unwrap();
510         add_lifetime_parameters(&mut meth.sig);
511         assert_eq!(
512             quote!(fn foo<'__mockall_x>(&self, x: [&(dyn T + '__mockall_x); 1]);)
513                 .to_string(),
514             quote!(#meth).to_string()
515         );
516     }
517 
518     #[test]
bare_fn_with_named_args()519     fn bare_fn_with_named_args() {
520         let mut meth: TraitItemFn = parse2(quote!(
521             fn foo(&self, x: fn(&dyn T));
522         )).unwrap();
523         add_lifetime_parameters(&mut meth.sig);
524         assert_eq!(
525             quote!(fn foo(&self, x: fn(&dyn T));).to_string(),
526             quote!(#meth).to_string()
527         );
528     }
529 
530     #[test]
plain()531     fn plain() {
532         let mut meth: TraitItemFn = parse2(quote!(
533             fn foo(&self, x: &dyn T);
534         )).unwrap();
535         add_lifetime_parameters(&mut meth.sig);
536         assert_eq!(
537             quote!(fn foo<'__mockall_x>(&self, x: &(dyn T + '__mockall_x));)
538                 .to_string(),
539             quote!(#meth).to_string()
540         );
541     }
542 
543     #[test]
slice()544     fn slice() {
545         let mut meth: TraitItemFn = parse2(quote!(
546             fn foo(&self, x: &[&dyn T]);
547         )).unwrap();
548         add_lifetime_parameters(&mut meth.sig);
549         assert_eq!(
550             quote!(fn foo<'__mockall_x>(&self, x: &[&(dyn T + '__mockall_x)]);)
551                 .to_string(),
552             quote!(#meth).to_string()
553         );
554     }
555 
556     #[test]
tuple()557     fn tuple() {
558         let mut meth: TraitItemFn = parse2(quote!(
559             fn foo(&self, x: (&dyn T, u32));
560         )).unwrap();
561         add_lifetime_parameters(&mut meth.sig);
562         assert_eq!(
563             quote!(fn foo<'__mockall_x>(&self, x: (&(dyn T + '__mockall_x), u32));)
564                 .to_string(),
565             quote!(#meth).to_string()
566         );
567     }
568 
569     #[test]
with_anonymous_lifetime()570     fn with_anonymous_lifetime() {
571         let mut meth: TraitItemFn = parse2(quote!(
572             fn foo(&self, x: &(dyn T + '_));
573         )).unwrap();
574         add_lifetime_parameters(&mut meth.sig);
575         assert_eq!(
576             quote!(fn foo(&self, x: &(dyn T + '_));).to_string(),
577             quote!(#meth).to_string()
578         );
579     }
580 
581     #[test]
with_parens()582     fn with_parens() {
583         let mut meth: TraitItemFn = parse2(quote!(
584             fn foo(&self, x: &(dyn T));
585         )).unwrap();
586         add_lifetime_parameters(&mut meth.sig);
587         assert_eq!(
588             quote!(fn foo<'__mockall_x>(&self, x: &(dyn T + '__mockall_x));)
589                 .to_string(),
590             quote!(#meth).to_string()
591         );
592     }
593 
594     #[test]
with_lifetime_parameter()595     fn with_lifetime_parameter() {
596         let mut meth: TraitItemFn = parse2(quote!(
597             fn foo<'a>(&self, x: &(dyn T + 'a));
598         )).unwrap();
599         add_lifetime_parameters(&mut meth.sig);
600         assert_eq!(
601             quote!(fn foo<'a>(&self, x: &(dyn T + 'a));).to_string(),
602             quote!(#meth).to_string()
603         );
604     }
605 
606     #[test]
with_static_lifetime()607     fn with_static_lifetime() {
608         let mut meth: TraitItemFn = parse2(quote!(
609             fn foo(&self, x: &(dyn T + 'static));
610         )).unwrap();
611         add_lifetime_parameters(&mut meth.sig);
612         assert_eq!(
613             quote!(fn foo(&self, x: &(dyn T + 'static));).to_string(),
614             quote!(#meth).to_string()
615         );
616     }
617 
618 }
619 
620 mod sanity_check_sig {
621     use super::*;
622 
623     #[test]
624     #[should_panic(expected = "Mockall does not support \"impl trait\" in argument position.  Use \"T: SomeTrait\" instead.")]
impl_trait()625     fn impl_trait() {
626         let meth: ImplItemFn = parse2(quote!(
627             fn foo(&self, x: impl SomeTrait) {}
628         )).unwrap();
629         sanity_check_sig(&meth.sig);
630     }
631 }
632 }
633