blob: 9fe42388a545b96409b812c44596842b9538df85 [file] [log] [blame]
// vim: tw=80
use super::*;
use crate::{
mock_function::MockFunction,
mockable_item::{MockableItem, MockableModule}
};
/// A Mock item
pub(crate) enum MockItem {
Module(MockItemModule),
Struct(MockItemStruct)
}
impl From<MockableItem> for MockItem {
fn from(mockable: MockableItem) -> MockItem {
match mockable {
MockableItem::Struct(s) => MockItem::Struct(
MockItemStruct::from(s)
),
MockableItem::Module(mod_) => MockItem::Module(
MockItemModule::from(mod_)
)
}
}
}
impl ToTokens for MockItem {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
MockItem::Module(mod_) => mod_.to_tokens(tokens),
MockItem::Struct(s) => s.to_tokens(tokens)
}
}
}
enum MockItemContent {
Fn(MockFunction),
Tokens(TokenStream)
}
pub(crate) struct MockItemModule {
attrs: TokenStream,
vis: Visibility,
mock_ident: Ident,
orig_ident: Option<Ident>,
content: Vec<MockItemContent>
}
impl From<MockableModule> for MockItemModule {
fn from(mod_: MockableModule) -> MockItemModule {
let mock_ident = mod_.mock_ident.clone();
let orig_ident = mod_.orig_ident;
let mut content = Vec::new();
for item in mod_.content.into_iter() {
let span = item.span();
match item {
Item::ExternCrate(_) | Item::Impl(_) =>
{
// Ignore
},
Item::Static(is) => {
content.push(
MockItemContent::Tokens(is.into_token_stream())
);
},
Item::Const(ic) => {
content.push(
MockItemContent::Tokens(ic.into_token_stream())
);
},
Item::Fn(f) => {
let mf = mock_function::Builder::new(&f.sig, &f.vis)
.attrs(&f.attrs)
.parent(&mock_ident)
.levels(1)
.call_levels(0)
.build();
content.push(MockItemContent::Fn(mf));
},
Item::ForeignMod(ifm) => {
for item in ifm.items {
if let ForeignItem::Fn(mut f) = item {
// Foreign functions are always unsafe. Mock
// foreign functions should be unsafe too, to
// prevent "warning: unused unsafe" messages.
f.sig.unsafety = Some(Token![unsafe](f.span()));
let mf = mock_function::Builder::new(&f.sig, &f.vis)
.attrs(&f.attrs)
.parent(&mock_ident)
.levels(1)
.call_levels(0)
.build();
content.push(MockItemContent::Fn(mf));
} else {
compile_error(item.span(),
"Mockall does not yet support this type in this position. Please open an issue with your use case at https://github.com/asomers/mockall");
}
}
},
Item::Mod(_)
| Item::Struct(_) | Item::Enum(_)
| Item::Union(_) | Item::Trait(_) =>
{
compile_error(span,
"Mockall does not yet support deriving nested mocks");
},
Item::Type(ty) => {
content.push(
MockItemContent::Tokens(ty.into_token_stream())
);
},
Item::TraitAlias(ta) => {
content.push
(MockItemContent::Tokens(ta.into_token_stream())
);
},
Item::Use(u) => {
content.push(
MockItemContent::Tokens(u.into_token_stream())
);
},
_ => compile_error(span, "Unsupported item")
}
}
MockItemModule {
attrs: mod_.attrs,
vis: mod_.vis,
mock_ident: mod_.mock_ident,
orig_ident,
content
}
}
}
impl ToTokens for MockItemModule {
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut body = TokenStream::new();
let mut cp_body = TokenStream::new();
let attrs = &self.attrs;
let modname = &self.mock_ident;
let vis = &self.vis;
for item in self.content.iter() {
match item {
MockItemContent::Tokens(ts) => ts.to_tokens(&mut body),
MockItemContent::Fn(f) => {
let call = f.call(None);
let ctx_fn = f.context_fn(None);
let priv_mod = f.priv_module();
quote!(
#priv_mod
#call
#ctx_fn
).to_tokens(&mut body);
f.checkpoint().to_tokens(&mut cp_body);
},
}
}
quote!(
/// Verify that all current expectations for every function in
/// this module are satisfied and clear them.
pub fn checkpoint() { #cp_body }
).to_tokens(&mut body);
let docstr = {
if let Some(ident) = &self.orig_ident {
let inner = format!("Mock version of the `{}` module", ident);
quote!( #[doc = #inner])
} else {
// Typically an extern FFI block. Not really anything good we
// can put in the doc string.
quote!(#[allow(missing_docs)])
}
};
quote!(
#attrs
#docstr
#vis mod #modname {
#body
}).to_tokens(tokens);
}
}