| 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)); |
| 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 | |