1// vim: tw=80
2use super::*;
3use syn::parse::{Parse, ParseStream};
4
5/// Make any implicit lifetime parameters explicit
6fn 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(LifetimeDef::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
96fn derive_debug() -> Attribute {
97 Attribute {
98 pound_token: <Token![#]>::default(),
99 style: AttrStyle::Outer,
100 bracket_token: token::Bracket::default(),
101 path: Path::from(format_ident!("derive")),
102 tokens: quote!((Debug))
103 }
104}
105
106/// Add "Mock" to the front of the named type
107fn 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
126fn 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::Method(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
139fn mockable_method(meth: &mut ImplItemMethod, 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
154fn mockable_trait_method(
155 meth: &mut TraitItemMethod,
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
172fn 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::Method(mut tim) => {
179 mockable_trait_method(&mut tim, name, generics);
180 ImplItem::Method(tim2iim(tim, &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
223fn 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
239fn 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 ident: tic.ident,
252 colon_token: tic.colon_token,
253 ty: tic.ty,
254 eq_token,
255 expr,
256 semi_token: tic.semi_token
257 }
258}
259
260/// Converts a TraitItemMethod into an ImplItemMethod
261fn tim2iim(m: syn::TraitItemMethod, vis: &syn::Visibility)
262 -> syn::ImplItemMethod
263{
264 let empty_block = Block {
265 brace_token: token::Brace::default(),
266 stmts: Vec::new()
267 };
268 syn::ImplItemMethod{
269 attrs: m.attrs,
270 vis: vis.clone(),
271 defaultness: None,
272 sig: m.sig,
273 block: empty_block
274 }
275}
276
277/// Converts a TraitItemType into an ImplItemType
278fn tit2iit(tit: TraitItemType, vis: &Visibility) -> ImplItemType {
279 let span = tit.span();
280 let (eq_token, ty) = tit.default.unwrap_or_else(|| {
281 compile_error(span,
282 "associated types in mock! must be fully specified");
283 (token::Eq::default(), Type::Verbatim(TokenStream::new()))
284 });
285 ImplItemType {
286 attrs: tit.attrs,
287 vis: vis.clone(),
288 defaultness: None,
289 type_token: tit.type_token,
290 ident: tit.ident,
291 generics: tit.generics,
292 eq_token,
293 ty,
294 semi_token: tit.semi_token,
295 }
296}
297
298pub(crate) struct MockableStruct {
299 pub attrs: Vec<Attribute>,
300 pub consts: Vec<ImplItemConst>,
301 pub generics: Generics,
302 /// Inherent methods of the mockable struct
303 pub methods: Vec<ImplItemMethod>,
304 pub name: Ident,
305 pub vis: Visibility,
306 pub impls: Vec<ItemImpl>
307}
308
309impl MockableStruct {
310 /// Does this struct derive Debug?
311 pub fn derives_debug(&self) -> bool {
312 self.attrs.iter()
313 .any(|attr| {
314 if let Ok(Meta::List(ml)) = attr.parse_meta() {
315 let i = ml.path.get_ident();
316 if i.map_or(false, |i| *i == "derive") {
317 ml.nested.iter()
318 .any(|nm| {
319 if let NestedMeta::Meta(m) = nm {
320 let i = m.path().get_ident();
321 i.map_or(false, |i| *i == "Debug")
322 } else {
323 false
324 }
325 })
326 } else {
327 false
328 }
329 } else {
330 false
331 }
332 })
333 }
334}
335
336impl From<(Attrs, ItemTrait)> for MockableStruct {
337 fn from((attrs, item_trait): (Attrs, ItemTrait)) -> MockableStruct {
338 let trait_ = attrs.substitute_trait(&item_trait);
339 let mut attrs = trait_.attrs.clone();
340 attrs.push(derive_debug());
341 let vis = trait_.vis.clone();
342 let name = gen_mock_ident(&trait_.ident);
343 let generics = trait_.generics.clone();
344 let impls = vec![mockable_trait(trait_, &name, &generics)];
345 MockableStruct {
346 attrs,
347 consts: Vec::new(),
348 vis,
349 name,
350 generics,
351 methods: Vec::new(),
352 impls
353 }
354 }
355}
356
357impl From<ItemImpl> for MockableStruct {
358 fn from(mut item_impl: ItemImpl) -> MockableStruct {
359 let name = match &*item_impl.self_ty {
360 Type::Path(type_path) => {
361 let n = find_ident_from_path(&type_path.path).0;
362 gen_mock_ident(&n)
363 },
364 x => {
365 compile_error(x.span(),
366 "mockall_derive only supports mocking traits and structs");
367 Ident::new("", Span::call_site())
368 }
369 };
370 let mut attrs = item_impl.attrs.clone();
371 attrs.push(derive_debug());
372 let mut consts = Vec::new();
373 let generics = item_impl.generics.clone();
374 let mut methods = Vec::new();
375 let pub_token = Token![pub](Span::call_site());
376 let vis = Visibility::Public(VisPublic{pub_token});
377 let mut impls = Vec::new();
378 if let Some((bang, _path, _)) = &item_impl.trait_ {
379 if bang.is_some() {
380 compile_error(bang.span(), "Unsupported by automock");
381 }
382
383 // Substitute any associated types in this ItemImpl.
384 // NB: this would not be necessary if the user always fully
385 // qualified them, e.g. `<Self as MyTrait>::MyType`
386 let mut attrs = Attrs::default();
387 for item in item_impl.items.iter() {
388 match item {
389 ImplItem::Const(_iic) =>
390 (),
391 ImplItem::Method(_meth) =>
392 (),
393 ImplItem::Type(ty) => {
394 attrs.attrs.insert(ty.ident.clone(), ty.ty.clone());
395 },
396 x => compile_error(x.span(), "Unsupported by automock")
397 }
398 }
399 attrs.substitute_item_impl(&mut item_impl);
400 impls.push(mockable_item_impl(item_impl, &name, &generics));
401 } else {
402 for item in item_impl.items.into_iter() {
403 match item {
404 ImplItem::Method(mut meth) => {
405 mockable_method(&mut meth, &name, &item_impl.generics);
406 methods.push(meth)
407 },
408 ImplItem::Const(iic) => consts.push(iic),
409 // Rust doesn't allow types in an inherent impl
410 x => compile_error(x.span(),
411 "Unsupported by Mockall in this context"),
412 }
413 }
414 };
415 MockableStruct {
416 attrs,
417 consts,
418 generics,
419 methods,
420 name,
421 vis,
422 impls,
423 }
424 }
425}
426
427impl Parse for MockableStruct {
428 fn parse(input: ParseStream) -> syn::parse::Result<Self> {
429 let attrs = input.call(syn::Attribute::parse_outer)?;
430 let vis: syn::Visibility = input.parse()?;
431 let original_name: syn::Ident = input.parse()?;
432 let mut generics: syn::Generics = input.parse()?;
433 let wc: Option<syn::WhereClause> = input.parse()?;
434 generics.where_clause = wc;
435 let name = gen_mock_ident(&original_name);
436 let impl_content;
437 let _brace_token = braced!(impl_content in input);
438 let mut consts = Vec::new();
439 let mut methods = Vec::new();
440 while !impl_content.is_empty() {
441 let item: ImplItem = impl_content.parse()?;
442 match item {
443 ImplItem::Method(mut iim) => {
444 mockable_method(&mut iim, &name, &generics);
445 methods.push(iim);
446 },
447 ImplItem::Const(iic) => consts.push(iic),
448 _ => {
449 return Err(input.error("Unsupported in this context"));
450 }
451 }
452 }
453
454 let mut impls = Vec::new();
455 while !input.is_empty() {
456 let item: Item = input.parse()?;
457 match item {
458 Item::Trait(it) => {
459 let note = "Deprecated mock! syntax. Instead of \"trait X\", write \"impl X for Y\". See PR #205";
460 let mut impl_ = mockable_trait(it, &name, &generics);
461 impl_.attrs.push(Attribute {
462 pound_token: <token::Pound>::default(),
463 style: AttrStyle::Outer,
464 bracket_token: token::Bracket::default(),
465 path: Path::from(format_ident!("deprecated")),
466 tokens: quote!((since = "0.9.0", note = #note)),
467 });
468 impls.push(impl_)
469 },
470 Item::Impl(ii) =>
471 impls.push(mockable_item_impl(ii, &name, &generics)),
472 _ => return Err(input.error("Unsupported in this context")),
473 }
474 }
475
476 Ok(
477 MockableStruct {
478 attrs,
479 consts,
480 generics,
481 methods,
482 name,
483 vis,
484 impls
485 }
486 )
487 }
488}
489
490#[cfg(test)]
491mod t {
492 use super::*;
493
494mod add_lifetime_parameters {
495 use super::*;
496
497 #[test]
498 fn array() {
499 let mut meth: TraitItemMethod = parse2(quote!(
500 fn foo(&self, x: [&dyn T; 1]);
501 )).unwrap();
502 add_lifetime_parameters(&mut meth.sig);
503 assert_eq!(
504 quote!(fn foo<'__mockall_x>(&self, x: [&(dyn T + '__mockall_x); 1]);)
505 .to_string(),
506 quote!(#meth).to_string()
507 );
508 }
509
510 #[test]
511 fn bare_fn_with_named_args() {
512 let mut meth: TraitItemMethod = parse2(quote!(
513 fn foo(&self, x: fn(&dyn T));
514 )).unwrap();
515 add_lifetime_parameters(&mut meth.sig);
516 assert_eq!(
517 quote!(fn foo(&self, x: fn(&dyn T));).to_string(),
518 quote!(#meth).to_string()
519 );
520 }
521
522 #[test]
523 fn plain() {
524 let mut meth: TraitItemMethod = parse2(quote!(
525 fn foo(&self, x: &dyn T);
526 )).unwrap();
527 add_lifetime_parameters(&mut meth.sig);
528 assert_eq!(
529 quote!(fn foo<'__mockall_x>(&self, x: &(dyn T + '__mockall_x));)
530 .to_string(),
531 quote!(#meth).to_string()
532 );
533 }
534
535 #[test]
536 fn slice() {
537 let mut meth: TraitItemMethod = parse2(quote!(
538 fn foo(&self, x: &[&dyn T]);
539 )).unwrap();
540 add_lifetime_parameters(&mut meth.sig);
541 assert_eq!(
542 quote!(fn foo<'__mockall_x>(&self, x: &[&(dyn T + '__mockall_x)]);)
543 .to_string(),
544 quote!(#meth).to_string()
545 );
546 }
547
548 #[test]
549 fn tuple() {
550 let mut meth: TraitItemMethod = parse2(quote!(
551 fn foo(&self, x: (&dyn T, u32));
552 )).unwrap();
553 add_lifetime_parameters(&mut meth.sig);
554 assert_eq!(
555 quote!(fn foo<'__mockall_x>(&self, x: (&(dyn T + '__mockall_x), u32));)
556 .to_string(),
557 quote!(#meth).to_string()
558 );
559 }
560
561 #[test]
562 fn with_anonymous_lifetime() {
563 let mut meth: TraitItemMethod = parse2(quote!(
564 fn foo(&self, x: &(dyn T + '_));
565 )).unwrap();
566 add_lifetime_parameters(&mut meth.sig);
567 assert_eq!(
568 quote!(fn foo(&self, x: &(dyn T + '_));).to_string(),
569 quote!(#meth).to_string()
570 );
571 }
572
573 #[test]
574 fn with_parens() {
575 let mut meth: TraitItemMethod = parse2(quote!(
576 fn foo(&self, x: &(dyn T));
577 )).unwrap();
578 add_lifetime_parameters(&mut meth.sig);
579 assert_eq!(
580 quote!(fn foo<'__mockall_x>(&self, x: &(dyn T + '__mockall_x));)
581 .to_string(),
582 quote!(#meth).to_string()
583 );
584 }
585
586 #[test]
587 fn with_lifetime_parameter() {
588 let mut meth: TraitItemMethod = parse2(quote!(
589 fn foo<'a>(&self, x: &(dyn T + 'a));
590 )).unwrap();
591 add_lifetime_parameters(&mut meth.sig);
592 assert_eq!(
593 quote!(fn foo<'a>(&self, x: &(dyn T + 'a));).to_string(),
594 quote!(#meth).to_string()
595 );
596 }
597
598 #[test]
599 fn with_static_lifetime() {
600 let mut meth: TraitItemMethod = parse2(quote!(
601 fn foo(&self, x: &(dyn T + 'static));
602 )).unwrap();
603 add_lifetime_parameters(&mut meth.sig);
604 assert_eq!(
605 quote!(fn foo(&self, x: &(dyn T + 'static));).to_string(),
606 quote!(#meth).to_string()
607 );
608 }
609
610}
611
612mod sanity_check_sig {
613 use super::*;
614
615 #[test]
616 #[should_panic(expected = "Mockall does not support \"impl trait\" in argument position. Use \"T: SomeTrait\" instead.")]
617 fn impl_trait() {
618 let meth: ImplItemMethod = parse2(quote!(
619 fn foo(&self, x: impl SomeTrait);
620 )).unwrap();
621 sanity_check_sig(&meth.sig);
622 }
623}
624}
625