1 | // vim: tw=80 |
2 | use super::*; |
3 | |
4 | use crate::{ |
5 | mock_function::MockFunction, |
6 | mockable_item::{MockableItem, MockableModule} |
7 | }; |
8 | |
9 | /// A Mock item |
10 | pub(crate) enum MockItem { |
11 | Module(MockItemModule), |
12 | Struct(MockItemStruct) |
13 | } |
14 | |
15 | impl From<MockableItem> for MockItem { |
16 | fn from(mockable: MockableItem) -> MockItem { |
17 | match mockable { |
18 | MockableItem::Struct(s) => MockItem::Struct( |
19 | MockItemStruct::from(s) |
20 | ), |
21 | MockableItem::Module(mod_) => MockItem::Module( |
22 | MockItemModule::from(mod_) |
23 | ) |
24 | } |
25 | } |
26 | } |
27 | |
28 | impl ToTokens for MockItem { |
29 | fn to_tokens(&self, tokens: &mut TokenStream) { |
30 | match self { |
31 | MockItem::Module(mod_) => mod_.to_tokens(tokens), |
32 | MockItem::Struct(s) => s.to_tokens(tokens) |
33 | } |
34 | } |
35 | } |
36 | |
37 | enum MockItemContent { |
38 | Fn(Box<MockFunction>), |
39 | Tokens(TokenStream) |
40 | } |
41 | |
42 | pub(crate) struct MockItemModule { |
43 | attrs: TokenStream, |
44 | vis: Visibility, |
45 | mock_ident: Ident, |
46 | orig_ident: Option<Ident>, |
47 | content: Vec<MockItemContent> |
48 | } |
49 | |
50 | impl From<MockableModule> for MockItemModule { |
51 | fn from(mod_: MockableModule) -> MockItemModule { |
52 | let mock_ident = mod_.mock_ident.clone(); |
53 | let orig_ident = mod_.orig_ident; |
54 | let mut content = Vec::new(); |
55 | for item in mod_.content.into_iter() { |
56 | let span = item.span(); |
57 | match item { |
58 | Item::ExternCrate(_) | Item::Impl(_) => |
59 | { |
60 | // Ignore |
61 | }, |
62 | Item::Static(is) => { |
63 | content.push( |
64 | MockItemContent::Tokens(is.into_token_stream()) |
65 | ); |
66 | }, |
67 | Item::Const(ic) => { |
68 | content.push( |
69 | MockItemContent::Tokens(ic.into_token_stream()) |
70 | ); |
71 | }, |
72 | Item::Fn(f) => { |
73 | let mf = mock_function::Builder::new(&f.sig, &f.vis) |
74 | .attrs(&f.attrs) |
75 | .parent(&mock_ident) |
76 | .levels(1) |
77 | .call_levels(0) |
78 | .build(); |
79 | content.push(MockItemContent::Fn(Box::new(mf))); |
80 | }, |
81 | Item::ForeignMod(ifm) => { |
82 | for item in ifm.items { |
83 | if let ForeignItem::Fn(mut f) = item { |
84 | // Foreign functions are always unsafe. Mock |
85 | // foreign functions should be unsafe too, to |
86 | // prevent "warning: unused unsafe" messages. |
87 | f.sig.unsafety = Some(Token![unsafe](f.span())); |
88 | let mf = mock_function::Builder::new(&f.sig, &f.vis) |
89 | .attrs(&f.attrs) |
90 | .parent(&mock_ident) |
91 | .levels(1) |
92 | .call_levels(0) |
93 | .build(); |
94 | content.push(MockItemContent::Fn(Box::new(mf))); |
95 | } else { |
96 | compile_error(item.span(), |
97 | "Mockall does not yet support this type in this position. Please open an issue with your use case at https://github.com/asomers/mockall" ); |
98 | } |
99 | } |
100 | }, |
101 | Item::Mod(_) |
102 | | Item::Struct(_) | Item::Enum(_) |
103 | | Item::Union(_) | Item::Trait(_) => |
104 | { |
105 | compile_error(span, |
106 | "Mockall does not yet support deriving nested mocks" ); |
107 | }, |
108 | Item::Type(ty) => { |
109 | content.push( |
110 | MockItemContent::Tokens(ty.into_token_stream()) |
111 | ); |
112 | }, |
113 | Item::TraitAlias(ta) => { |
114 | content.push |
115 | (MockItemContent::Tokens(ta.into_token_stream()) |
116 | ); |
117 | }, |
118 | Item::Use(u) => { |
119 | content.push( |
120 | MockItemContent::Tokens(u.into_token_stream()) |
121 | ); |
122 | }, |
123 | _ => compile_error(span, "Unsupported item" ) |
124 | } |
125 | } |
126 | MockItemModule { |
127 | attrs: mod_.attrs, |
128 | vis: mod_.vis, |
129 | mock_ident: mod_.mock_ident, |
130 | orig_ident, |
131 | content |
132 | } |
133 | } |
134 | } |
135 | |
136 | impl ToTokens for MockItemModule { |
137 | fn to_tokens(&self, tokens: &mut TokenStream) { |
138 | let mut body = TokenStream::new(); |
139 | let mut cp_body = TokenStream::new(); |
140 | let attrs = &self.attrs; |
141 | let modname = &self.mock_ident; |
142 | let vis = &self.vis; |
143 | |
144 | for item in self.content.iter() { |
145 | match item { |
146 | MockItemContent::Tokens(ts) => ts.to_tokens(&mut body), |
147 | MockItemContent::Fn(f) => { |
148 | let call = f.call(None); |
149 | let ctx_fn = f.context_fn(None); |
150 | let priv_mod = f.priv_module(); |
151 | quote!( |
152 | #priv_mod |
153 | #call |
154 | #ctx_fn |
155 | ).to_tokens(&mut body); |
156 | f.checkpoint().to_tokens(&mut cp_body); |
157 | }, |
158 | } |
159 | } |
160 | |
161 | quote!( |
162 | /// Verify that all current expectations for every function in |
163 | /// this module are satisfied and clear them. |
164 | pub fn checkpoint() { #cp_body } |
165 | ).to_tokens(&mut body); |
166 | let docstr = { |
167 | if let Some(ident) = &self.orig_ident { |
168 | let inner = format!("Mock version of the `{}` module" , ident); |
169 | quote!( #[doc = #inner]) |
170 | } else { |
171 | // Typically an extern FFI block. Not really anything good we |
172 | // can put in the doc string. |
173 | quote!(#[allow(missing_docs)]) |
174 | } |
175 | }; |
176 | quote!( |
177 | #[allow(unused_imports)] |
178 | #attrs |
179 | #docstr |
180 | #vis mod #modname { |
181 | #body |
182 | }).to_tokens(tokens); |
183 | } |
184 | } |
185 | |