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 { |
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 | |