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 {
from(mockable: MockableItem) -> MockItem16     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 {
to_tokens(&self, tokens: &mut TokenStream)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 {
from(mod_: MockableModule) -> MockItemModule51     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 
89                             // Set the ABI to match the ForeignMod's ABI
90                             // for proper function linkage with external code.
91                             f.sig.abi = Some(ifm.abi.clone());
92 
93                             let mf = mock_function::Builder::new(&f.sig, &f.vis)
94                                 .attrs(&f.attrs)
95                                 .parent(&mock_ident)
96                                 .levels(1)
97                                 .call_levels(0)
98                                 .build();
99                             content.push(MockItemContent::Fn(Box::new(mf)));
100                         } else {
101                             compile_error(item.span(),
102                                 "Mockall does not yet support  this type in this position.  Please open an issue with your use case at https://github.com/asomers/mockall");
103                         }
104                     }
105                 },
106                 Item::Mod(_)
107                     | Item::Struct(_) | Item::Enum(_)
108                     | Item::Union(_) | Item::Trait(_) =>
109                 {
110                     compile_error(span,
111                         "Mockall does not yet support deriving nested mocks");
112                 },
113                 Item::Type(ty) => {
114                     content.push(
115                         MockItemContent::Tokens(ty.into_token_stream())
116                     );
117                 },
118                 Item::TraitAlias(ta) => {
119                     content.push
120                         (MockItemContent::Tokens(ta.into_token_stream())
121                     );
122                 },
123                 Item::Use(u) => {
124                     content.push(
125                         MockItemContent::Tokens(u.into_token_stream())
126                     );
127                 },
128                 _ => compile_error(span, "Unsupported item")
129             }
130         }
131         MockItemModule {
132             attrs: mod_.attrs,
133             vis: mod_.vis,
134             mock_ident: mod_.mock_ident,
135             orig_ident,
136             content
137         }
138     }
139 }
140 
141 impl ToTokens for MockItemModule {
to_tokens(&self, tokens: &mut TokenStream)142     fn to_tokens(&self, tokens: &mut TokenStream) {
143         let mut body = TokenStream::new();
144         let mut cp_body = TokenStream::new();
145         let attrs = &self.attrs;
146         let modname = &self.mock_ident;
147         let vis = &self.vis;
148 
149         for item in self.content.iter() {
150             match item {
151                 MockItemContent::Tokens(ts) => ts.to_tokens(&mut body),
152                 MockItemContent::Fn(f) => {
153                     let call = f.call(None);
154                     let ctx_fn = f.context_fn(None);
155                     let priv_mod = f.priv_module();
156                     quote!(
157                         #priv_mod
158                         #call
159                         #ctx_fn
160                     ).to_tokens(&mut body);
161                     f.checkpoint().to_tokens(&mut cp_body);
162                 },
163             }
164         }
165 
166         quote!(
167             /// Verify that all current expectations for every function in
168             /// this module are satisfied and clear them.
169             pub fn checkpoint() { #cp_body }
170         ).to_tokens(&mut body);
171         let docstr = {
172             if let Some(ident) = &self.orig_ident {
173                 let inner = format!("Mock version of the `{ident}` module");
174                 quote!( #[doc = #inner])
175             } else {
176                 // Typically an extern FFI block.  Not really anything good we
177                 // can put in the doc string.
178                 quote!(#[allow(missing_docs)])
179             }
180         };
181         quote!(
182             #[allow(unused_imports)]
183             #attrs
184             #docstr
185             #vis mod #modname {
186                 #body
187         }).to_tokens(tokens);
188     }
189 }
190