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