blob: 701e308e7a5fe7e3ce91614e255b243d75a39487 [file] [log] [blame]
// vim: tw=80
use super::*;
/// Performs transformations on a function to make it mockable
fn mockable_fn(mut item_fn: ItemFn) -> ItemFn {
demutify(&mut item_fn.sig.inputs);
deimplify(&mut item_fn.sig.output);
item_fn
}
/// Performs transformations on an Item to make it mockable
fn mockable_item(item: Item) -> Item {
match item {
Item::Fn(item_fn) => Item::Fn(mockable_fn(item_fn)),
x => x
}
}
/// An item that's ready to be mocked.
///
/// It should be functionally identical or near-identical to the original item,
/// but with minor alterations that make it suitable for mocking, such as
/// altered lifetimes.
pub(crate) enum MockableItem {
Module(MockableModule),
Struct(MockableStruct)
}
impl From<(Attrs, Item)> for MockableItem {
fn from((attrs, item): (Attrs, Item)) -> MockableItem {
match item {
Item::Impl(item_impl) =>
MockableItem::Struct(MockableStruct::from(item_impl)),
Item::ForeignMod(item_foreign_mod) =>
MockableItem::Module(
MockableModule::from((attrs, item_foreign_mod))
),
Item::Mod(item_mod) =>
MockableItem::Module(MockableModule::from(item_mod)),
Item::Trait(trait_) =>
MockableItem::Struct(MockableStruct::from((attrs, trait_))),
_ => panic!("automock does not support this item type")
}
}
}
impl From<MockableStruct> for MockableItem {
fn from(mock: MockableStruct) -> MockableItem {
MockableItem::Struct(mock)
}
}
pub(crate) struct MockableModule {
pub attrs: TokenStream,
pub vis: Visibility,
pub mock_ident: Ident,
pub mod_token: token::Mod,
/// Ident of the original module, if any
pub orig_ident: Option<Ident>,
pub content: Vec<Item>
}
impl From<(Attrs, ItemForeignMod)> for MockableModule {
fn from((attrs, foreign): (Attrs, ItemForeignMod)) -> MockableModule {
let orig_ident = None;
let mock_ident = attrs.modname.expect(concat!(
"module name is required when mocking foreign functions,",
" like `#[automock(mod mock_ffi)]`"
));
let mod_token = <Token![mod]>::default();
let vis = Visibility::Public(VisPublic{
pub_token: <Token![pub]>::default()
});
let attrs = quote!(
#[deprecated(since = "0.9.0", note = "Using automock directly on an extern block is deprecated. Instead, wrap the extern block in a module, and automock that, like #[automoock] mod ffi { extern \"C\" { fn foo ... } }")]
);
let mut content = vec![
// When mocking extern blocks, we pretend that they're modules, so
// we need a "use super::*;" to ensure that types can resolve
Item::Use(ItemUse {
attrs: Vec::new(),
vis: Visibility::Inherited,
use_token: token::Use::default(),
leading_colon: None,
tree: UseTree::Path(UsePath {
ident: Ident::new("super", Span::call_site()),
colon2_token: token::Colon2::default(),
tree: Box::new(UseTree::Glob(UseGlob {
star_token: token::Star::default()
}))
}),
semi_token: token::Semi::default()
})
];
content.extend(foreign.items.into_iter()
.map(|foreign_item| {
match foreign_item {
ForeignItem::Fn(f) => {
let span = f.sig.span();
let mut sig = f.sig;
// When mocking extern blocks, we pretend that they're
// modules. So we must supersuperfy everything by one
// level.
let vis = expectation_visibility(&f.vis, 1);
for arg in sig.inputs.iter_mut() {
if let FnArg::Typed(pt) = arg {
*pt.ty = supersuperfy(&*pt.ty, 1);
}
}
if let ReturnType::Type(_, ty) = &mut sig.output {
**ty = supersuperfy(&*ty, 1);
}
// Foreign functions are always unsafe. Mock foreign
// functions should be unsafe too, to prevent "warning:
// unused unsafe" messages.
sig.unsafety = Some(Token![unsafe](span));
let block = Box::new(Block {
brace_token: token::Brace::default(),
stmts: Vec::new()
});
Item::Fn(
ItemFn {
attrs: f.attrs,
vis,
sig,
block
}
)
},
_ => {
compile_error(foreign_item.span(),
"Unsupported foreign item type"
);
Item::Verbatim(TokenStream::default())
}
}
}));
MockableModule {
attrs,
vis,
mock_ident,
mod_token,
orig_ident,
content
}
}
}
impl From<ItemMod> for MockableModule {
fn from(mod_: ItemMod) -> MockableModule {
let span = mod_.span();
let vis = mod_.vis;
let mock_ident = format_ident!("mock_{}", mod_.ident);
let orig_ident = Some(mod_.ident);
let mod_token = mod_.mod_token;
let content = if let Some((_, content)) = mod_.content {
content.into_iter()
.map(mockable_item)
.collect()
} else {
compile_error(span,
"automock can only mock inline modules, not modules from another file");
Vec::new()
};
MockableModule {
attrs: TokenStream::new(),
vis,
mock_ident,
mod_token,
orig_ident,
content
}
}
}