1 // vim: tw=80
2 use proc_macro2::Span;
3 use quote::{ToTokens, format_ident, quote};
4 use std::{
5    collections::hash_map::DefaultHasher,
6    hash::{Hash, Hasher}
7 };
8 use syn::{
9     *,
10     spanned::Spanned
11 };
12 
13 use crate::{
14     AttrFormatter,
15     mock_function::{self, MockFunction},
16     compile_error
17 };
18 
19 pub(crate) struct MockTrait {
20     pub attrs: Vec<Attribute>,
21     pub consts: Vec<ImplItemConst>,
22     pub generics: Generics,
23     pub methods: Vec<MockFunction>,
24     /// Internally-used name of the trait used.
25     pub ss_name: Ident,
26     /// Fully-qualified name of the trait
27     pub trait_path: Path,
28     /// Path on which the trait is implemented.  Usually will be the same as
29     /// structname, but might include concrete generic parameters.
30     self_path: PathSegment,
31     pub types: Vec<ImplItemType>,
32     pub unsafety: Option<Token![unsafe]>
33 }
34 
35 impl MockTrait {
ss_name_priv(trait_path: &Path) -> Ident36     fn ss_name_priv(trait_path: &Path) -> Ident {
37         let path_args = &trait_path.segments.last().unwrap().arguments;
38         if path_args.is_empty() {
39             // Skip the hashing step for easie debugging of generated code
40             format_ident!("{}", trait_path.segments.last().unwrap().ident)
41         } else {
42             // Hash the path args to permit mocking structs that implement
43             // multiple traits distinguished only by their path args
44             let mut hasher = DefaultHasher::new();
45             path_args.hash(&mut hasher);
46             format_ident!("{}_{}", trait_path.segments.last().unwrap().ident,
47                 hasher.finish())
48         }
49     }
50 
ss_name(&self) -> &Ident51     pub fn ss_name(&self) -> &Ident {
52         &self.ss_name
53     }
54 
55     /// Create a new MockTrait
56     ///
57     /// # Arguments
58     /// * `structname` - name of the struct that implements this trait
59     /// * `struct_generics` - Generics of the parent structure
60     /// * `impl_`  -    Mockable ItemImpl for a trait
61     /// * `vis`     -   Visibility of the struct
new(structname: &Ident, struct_generics: &Generics, impl_: ItemImpl, vis: &Visibility) -> Self62     pub fn new(structname: &Ident,
63                struct_generics: &Generics,
64                impl_: ItemImpl,
65                vis: &Visibility) -> Self
66     {
67         let mut consts = Vec::new();
68         let mut methods = Vec::new();
69         let mut types = Vec::new();
70         let trait_path = if let Some((_, path, _)) = impl_.trait_ {
71             path
72         } else {
73             compile_error(impl_.span(), "impl block must implement a trait");
74             Path::from(format_ident!("__mockall_invalid"))
75         };
76         let ss_name = MockTrait::ss_name_priv(&trait_path);
77         let self_path = match *impl_.self_ty {
78             Type::Path(mut type_path) =>
79                 type_path.path.segments.pop().unwrap().into_value(),
80             x => {
81                 compile_error(x.span(),
82                     "mockall_derive only supports mocking traits and structs");
83                 PathSegment::from(Ident::new("", Span::call_site()))
84             }
85         };
86 
87         for ii in impl_.items.into_iter() {
88             match ii {
89                 ImplItem::Const(iic) => {
90                     consts.push(iic);
91                 },
92                 ImplItem::Fn(iif) => {
93                     let mf = mock_function::Builder::new(&iif.sig, vis)
94                         .attrs(&iif.attrs)
95                         .levels(2)
96                         .call_levels(0)
97                         .struct_(structname)
98                         .struct_generics(struct_generics)
99                         .trait_(&ss_name)
100                         .build();
101                     methods.push(mf);
102                 },
103                 ImplItem::Type(iit) => {
104                     types.push(iit);
105                 },
106                 _ => {
107                     compile_error(ii.span(),
108                     "This impl item is not yet supported by MockAll");
109                 }
110             }
111         }
112         MockTrait {
113             attrs: impl_.attrs,
114             consts,
115             generics: impl_.generics,
116             methods,
117             ss_name,
118             trait_path,
119             self_path,
120             types,
121             unsafety: impl_.unsafety
122         }
123     }
124 
125     /// Generate code for the trait implementation on the mock struct
126     ///
127     /// # Arguments
128     ///
129     /// * `modname`:    Name of the parent struct's private module
130     // Supplying modname is an unfortunately hack.  Ideally MockTrait
131     // wouldn't need to know that.
trait_impl(&self, modname: &Ident) -> impl ToTokens132     pub fn trait_impl(&self, modname: &Ident) -> impl ToTokens {
133         let trait_impl_attrs = &self.attrs;
134         let impl_attrs = AttrFormatter::new(&self.attrs)
135             .async_trait(false)
136             .doc(false)
137             .format();
138         let (ig, _tg, wc) = self.generics.split_for_impl();
139         let consts = &self.consts;
140         let path_args = &self.self_path.arguments;
141         let calls = self.methods.iter()
142                 .map(|meth| meth.call(Some(modname)))
143                 .collect::<Vec<_>>();
144         let contexts = self.methods.iter()
145             .filter(|meth| meth.is_static())
146             .map(|meth| meth.context_fn(Some(modname)))
147             .collect::<Vec<_>>();
148         let expects = self.methods.iter()
149             .filter(|meth| !meth.is_static())
150             .map(|meth| {
151                 if meth.is_method_generic() {
152                     // Specific impls with generic methods are TODO.
153                     meth.expect(modname, None)
154                 } else {
155                     meth.expect(modname, Some(path_args))
156                 }
157             }).collect::<Vec<_>>();
158         let trait_path = &self.trait_path;
159         let self_path = &self.self_path;
160         let types = &self.types;
161         let unsafety = &self.unsafety;
162         quote!(
163             #(#trait_impl_attrs)*
164             #unsafety impl #ig #trait_path for #self_path #wc {
165                 #(#consts)*
166                 #(#types)*
167                 #(#calls)*
168             }
169             #(#impl_attrs)*
170             impl #ig #self_path #wc {
171                 #(#expects)*
172                 #(#contexts)*
173             }
174         )
175     }
176 }
177