1// vim: tw=80
2use super::*;
3
4/// Performs transformations on a function to make it mockable
5fn mockable_fn(mut item_fn: ItemFn) -> ItemFn {
6 demutify(&mut item_fn.sig.inputs);
7 deimplify(&mut item_fn.sig.output);
8 item_fn
9}
10
11/// Performs transformations on an Item to make it mockable
12fn mockable_item(item: Item) -> Item {
13 match item {
14 Item::Fn(item_fn) => Item::Fn(mockable_fn(item_fn)),
15 x => x
16 }
17}
18
19/// An item that's ready to be mocked.
20///
21/// It should be functionally identical or near-identical to the original item,
22/// but with minor alterations that make it suitable for mocking, such as
23/// altered lifetimes.
24pub(crate) enum MockableItem {
25 Module(MockableModule),
26 Struct(MockableStruct)
27}
28
29impl From<(Attrs, Item)> for MockableItem {
30 fn from((attrs, item): (Attrs, Item)) -> MockableItem {
31 match item {
32 Item::Impl(item_impl) =>
33 MockableItem::Struct(MockableStruct::from(item_impl)),
34 Item::ForeignMod(item_foreign_mod) =>
35 MockableItem::Module(
36 MockableModule::from((attrs, item_foreign_mod))
37 ),
38 Item::Mod(item_mod) =>
39 MockableItem::Module(MockableModule::from(item_mod)),
40 Item::Trait(trait_) =>
41 MockableItem::Struct(MockableStruct::from((attrs, trait_))),
42 _ => panic!("automock does not support this item type")
43 }
44 }
45}
46
47impl From<MockableStruct> for MockableItem {
48 fn from(mock: MockableStruct) -> MockableItem {
49 MockableItem::Struct(mock)
50 }
51}
52
53pub(crate) struct MockableModule {
54 pub attrs: TokenStream,
55 pub vis: Visibility,
56 pub mock_ident: Ident,
57 /// Ident of the original module, if any
58 pub orig_ident: Option<Ident>,
59 pub content: Vec<Item>
60}
61
62impl From<(Attrs, ItemForeignMod)> for MockableModule {
63 fn from((attrs, foreign): (Attrs, ItemForeignMod)) -> MockableModule {
64 let orig_ident = None;
65 let mock_ident = attrs.modname.expect(concat!(
66 "module name is required when mocking foreign functions,",
67 " like `#[automock(mod mock_ffi)]`"
68 ));
69 let vis = Visibility::Public(VisPublic{
70 pub_token: <Token![pub]>::default()
71 });
72 let attrs = quote!(
73 #[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 #[automock] mod ffi { extern \"C\" { fn foo ... } }")]
74 );
75 let mut content = vec![
76 // When mocking extern blocks, we pretend that they're modules, so
77 // we need a "use super::*;" to ensure that types can resolve
78 Item::Use(ItemUse {
79 attrs: Vec::new(),
80 vis: Visibility::Inherited,
81 use_token: token::Use::default(),
82 leading_colon: None,
83 tree: UseTree::Path(UsePath {
84 ident: Ident::new("super", Span::call_site()),
85 colon2_token: token::Colon2::default(),
86 tree: Box::new(UseTree::Glob(UseGlob {
87 star_token: token::Star::default()
88 }))
89 }),
90 semi_token: token::Semi::default()
91 })
92 ];
93 content.extend(foreign.items.into_iter()
94 .map(|foreign_item| {
95 match foreign_item {
96 ForeignItem::Fn(f) => {
97 let span = f.sig.span();
98 let mut sig = f.sig;
99
100 // When mocking extern blocks, we pretend that they're
101 // modules. So we must supersuperfy everything by one
102 // level.
103 let vis = expectation_visibility(&f.vis, 1);
104
105 for arg in sig.inputs.iter_mut() {
106 if let FnArg::Typed(pt) = arg {
107 *pt.ty = supersuperfy(pt.ty.as_ref(), 1);
108 }
109 }
110 if let ReturnType::Type(_, ty) = &mut sig.output {
111 **ty = supersuperfy(&*ty, 1);
112 }
113
114 // Foreign functions are always unsafe. Mock foreign
115 // functions should be unsafe too, to prevent "warning:
116 // unused unsafe" messages.
117 sig.unsafety = Some(Token![unsafe](span));
118 let block = Box::new(Block {
119 brace_token: token::Brace::default(),
120 stmts: Vec::new()
121 });
122
123 Item::Fn(
124 ItemFn {
125 attrs: f.attrs,
126 vis,
127 sig,
128 block
129 }
130 )
131 },
132 _ => {
133 compile_error(foreign_item.span(),
134 "Unsupported foreign item type"
135 );
136 Item::Verbatim(TokenStream::default())
137 }
138 }
139 }));
140 MockableModule {
141 attrs,
142 vis,
143 mock_ident,
144 orig_ident,
145 content
146 }
147 }
148}
149
150impl From<ItemMod> for MockableModule {
151 fn from(mod_: ItemMod) -> MockableModule {
152 let span = mod_.span();
153 let vis = mod_.vis;
154 let mock_ident = format_ident!("mock_{}", mod_.ident);
155 let orig_ident = Some(mod_.ident);
156 let content = if let Some((_, content)) = mod_.content {
157 content.into_iter()
158 .map(mockable_item)
159 .collect()
160 } else {
161 compile_error(span,
162 "automock can only mock inline modules, not modules from another file");
163 Vec::new()
164 };
165 MockableModule {
166 attrs: TokenStream::new(),
167 vis,
168 mock_ident,
169 orig_ident,
170 content
171 }
172 }
173}
174