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