1// vim: tw=80
2use proc_macro2::Span;
3use quote::{ToTokens, format_ident, quote};
4use std::{
5 collections::hash_map::DefaultHasher,
6 hash::{Hash, Hasher}
7};
8use syn::{
9 *,
10 spanned::Spanned
11};
12
13use crate::{
14 AttrFormatter,
15 mock_function::{self, MockFunction},
16 compile_error
17};
18
19pub(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
35impl MockTrait {
36 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
51 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
62 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::Method(iim) => {
93 let mf = mock_function::Builder::new(&iim.sig, vis)
94 .attrs(&iim.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.
132 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