| 1 | // vim: tw=80 |
| 2 | use proc_macro2::Span; |
| 3 | use quote::{ToTokens, format_ident, quote}; |
| 4 | use std::{ |
| 5 | collections::hash_map::DefaultHasher, |
| 6 | hash::{Hash, Hasher} |
| 7 | }; |
| 8 | use syn::{ |
| 9 | *, |
| 10 | spanned::Spanned |
| 11 | }; |
| 12 | |
| 13 | use crate::{ |
| 14 | AttrFormatter, |
| 15 | mock_function::{self, MockFunction}, |
| 16 | compile_error |
| 17 | }; |
| 18 | |
| 19 | pub(crate) struct MockTrait { |
| 20 | pub attrs: Vec<Attribute>, |
| 21 | pub consts: Vec<ImplItemConst>, |
| 22 | pub generics: Generics, |
| 23 | pub methods: Vec<MockFunction>, |
| 24 | /// Internally-used name of the trait used. |
| 25 | pub ss_name: Ident, |
| 26 | /// Fully-qualified name of the trait |
| 27 | pub trait_path: Path, |
| 28 | /// Path on which the trait is implemented. Usually will be the same as |
| 29 | /// structname, but might include concrete generic parameters. |
| 30 | self_path: PathSegment, |
| 31 | pub types: Vec<ImplItemType>, |
| 32 | pub unsafety: Option<Token![unsafe]> |
| 33 | } |
| 34 | |
| 35 | impl MockTrait { |
| 36 | fn ss_name_priv(trait_path: &Path) -> Ident { |
| 37 | let path_args = &trait_path.segments.last().unwrap().arguments; |
| 38 | if path_args.is_empty() { |
| 39 | // Skip the hashing step for easie debugging of generated code |
| 40 | format_ident!("{}" , trait_path.segments.last().unwrap().ident) |
| 41 | } else { |
| 42 | // Hash the path args to permit mocking structs that implement |
| 43 | // multiple traits distinguished only by their path args |
| 44 | let mut hasher = DefaultHasher::new(); |
| 45 | path_args.hash(&mut hasher); |
| 46 | format_ident!("{}_{}" , trait_path.segments.last().unwrap().ident, |
| 47 | hasher.finish()) |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | pub fn ss_name(&self) -> &Ident { |
| 52 | &self.ss_name |
| 53 | } |
| 54 | |
| 55 | /// Create a new MockTrait |
| 56 | /// |
| 57 | /// # Arguments |
| 58 | /// * `structname` - name of the struct that implements this trait |
| 59 | /// * `struct_generics` - Generics of the parent structure |
| 60 | /// * `impl_` - Mockable ItemImpl for a trait |
| 61 | /// * `vis` - Visibility of the struct |
| 62 | pub fn new(structname: &Ident, |
| 63 | struct_generics: &Generics, |
| 64 | impl_: ItemImpl, |
| 65 | vis: &Visibility) -> Self |
| 66 | { |
| 67 | let mut consts = Vec::new(); |
| 68 | let mut methods = Vec::new(); |
| 69 | let mut types = Vec::new(); |
| 70 | let trait_path = if let Some((_, path, _)) = impl_.trait_ { |
| 71 | path |
| 72 | } else { |
| 73 | compile_error(impl_.span(), "impl block must implement a trait" ); |
| 74 | Path::from(format_ident!("__mockall_invalid" )) |
| 75 | }; |
| 76 | let ss_name = MockTrait::ss_name_priv(&trait_path); |
| 77 | let self_path = match *impl_.self_ty { |
| 78 | Type::Path(mut type_path) => |
| 79 | type_path.path.segments.pop().unwrap().into_value(), |
| 80 | x => { |
| 81 | compile_error(x.span(), |
| 82 | "mockall_derive only supports mocking traits and structs" ); |
| 83 | PathSegment::from(Ident::new("" , Span::call_site())) |
| 84 | } |
| 85 | }; |
| 86 | |
| 87 | for ii in impl_.items.into_iter() { |
| 88 | match ii { |
| 89 | ImplItem::Const(iic) => { |
| 90 | consts.push(iic); |
| 91 | }, |
| 92 | ImplItem::Method(iim) => { |
| 93 | let mf = mock_function::Builder::new(&iim.sig, vis) |
| 94 | .attrs(&iim.attrs) |
| 95 | .levels(2) |
| 96 | .call_levels(0) |
| 97 | .struct_(structname) |
| 98 | .struct_generics(struct_generics) |
| 99 | .trait_(&ss_name) |
| 100 | .build(); |
| 101 | methods.push(mf); |
| 102 | }, |
| 103 | ImplItem::Type(iit) => { |
| 104 | types.push(iit); |
| 105 | }, |
| 106 | _ => { |
| 107 | compile_error(ii.span(), |
| 108 | "This impl item is not yet supported by MockAll" ); |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | MockTrait { |
| 113 | attrs: impl_.attrs, |
| 114 | consts, |
| 115 | generics: impl_.generics, |
| 116 | methods, |
| 117 | ss_name, |
| 118 | trait_path, |
| 119 | self_path, |
| 120 | types, |
| 121 | unsafety: impl_.unsafety |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | /// Generate code for the trait implementation on the mock struct |
| 126 | /// |
| 127 | /// # Arguments |
| 128 | /// |
| 129 | /// * `modname`: Name of the parent struct's private module |
| 130 | // Supplying modname is an unfortunately hack. Ideally MockTrait |
| 131 | // wouldn't need to know that. |
| 132 | pub fn trait_impl(&self, modname: &Ident) -> impl ToTokens { |
| 133 | let trait_impl_attrs = &self.attrs; |
| 134 | let impl_attrs = AttrFormatter::new(&self.attrs) |
| 135 | .async_trait(false) |
| 136 | .doc(false) |
| 137 | .format(); |
| 138 | let (ig, _tg, wc) = self.generics.split_for_impl(); |
| 139 | let consts = &self.consts; |
| 140 | let path_args = &self.self_path.arguments; |
| 141 | let calls = self.methods.iter() |
| 142 | .map(|meth| meth.call(Some(modname))) |
| 143 | .collect::<Vec<_>>(); |
| 144 | let contexts = self.methods.iter() |
| 145 | .filter(|meth| meth.is_static()) |
| 146 | .map(|meth| meth.context_fn(Some(modname))) |
| 147 | .collect::<Vec<_>>(); |
| 148 | let expects = self.methods.iter() |
| 149 | .filter(|meth| !meth.is_static()) |
| 150 | .map(|meth| { |
| 151 | if meth.is_method_generic() { |
| 152 | // Specific impls with generic methods are TODO. |
| 153 | meth.expect(modname, None) |
| 154 | } else { |
| 155 | meth.expect(modname, Some(path_args)) |
| 156 | } |
| 157 | }).collect::<Vec<_>>(); |
| 158 | let trait_path = &self.trait_path; |
| 159 | let self_path = &self.self_path; |
| 160 | let types = &self.types; |
| 161 | let unsafety = &self.unsafety; |
| 162 | quote!( |
| 163 | #(#trait_impl_attrs)* |
| 164 | #unsafety impl #ig #trait_path for #self_path #wc { |
| 165 | #(#consts)* |
| 166 | #(#types)* |
| 167 | #(#calls)* |
| 168 | } |
| 169 | #(#impl_attrs)* |
| 170 | impl #ig #self_path #wc { |
| 171 | #(#expects)* |
| 172 | #(#contexts)* |
| 173 | } |
| 174 | ) |
| 175 | } |
| 176 | } |
| 177 | |