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