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