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);
43 generics.gt_token.get_or_insert(Token);
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));
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