1// vim: tw=80
2use super::*;
3
4use crate::{
5 mock_function::MockFunction,
6 mockable_item::{MockableItem, MockableModule}
7};
8
9/// A Mock item
10pub(crate) enum MockItem {
11 Module(MockItemModule),
12 Struct(MockItemStruct)
13}
14
15impl 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
28impl 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
37enum MockItemContent {
38 Fn(Box<MockFunction>),
39 Tokens(TokenStream)
40}
41
42pub(crate) struct MockItemModule {
43 attrs: TokenStream,
44 vis: Visibility,
45 mock_ident: Ident,
46 orig_ident: Option<Ident>,
47 content: Vec<MockItemContent>
48}
49
50impl 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
136impl 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