blob: fc3253c83e618f38a9f3fe8a3511a05d1257b7ab [file] [log] [blame]
// vim: tw=80
use proc_macro2::Span;
use quote::{ToTokens, format_ident, quote};
use std::{
collections::hash_map::DefaultHasher,
fmt::Write,
hash::{Hash, Hasher}
};
use syn::{
*,
spanned::Spanned
};
use crate::{
AttrFormatter,
mock_function::{self, MockFunction},
compile_error
};
pub(crate) struct MockTrait {
pub attrs: Vec<Attribute>,
pub consts: Vec<ImplItemConst>,
pub generics: Generics,
pub methods: Vec<MockFunction>,
/// Internally-used name of the trait used.
pub ss_name: Ident,
/// Fully-qualified name of the trait
pub trait_path: Path,
/// Path on which the trait is implemented. Usually will be the same as
/// structname, but might include concrete generic parameters.
self_path: PathSegment,
pub types: Vec<ImplItemType>,
pub unsafety: Option<Token![unsafe]>
}
impl MockTrait {
fn ss_name_priv(trait_path: &Path) -> Ident {
let id = join_path_segments(trait_path, "__");
// Skip the hashing step for easy debugging of generated code
if let Some(hash) = hash_path_arguments(trait_path) {
// Hash the path args to permit mocking structs that implement
// multiple traits distinguished only by their path args
format_ident!("{id}_{hash}")
} else {
format_ident!("{id}")
}
}
pub fn ss_name(&self) -> &Ident {
&self.ss_name
}
/// Create a new MockTrait
///
/// # Arguments
/// * `structname` - name of the struct that implements this trait
/// * `struct_generics` - Generics of the parent structure
/// * `impl_` - Mockable ItemImpl for a trait
/// * `vis` - Visibility of the struct
pub fn new(structname: &Ident,
struct_generics: &Generics,
impl_: ItemImpl,
vis: &Visibility) -> Self
{
let mut consts = Vec::new();
let mut methods = Vec::new();
let mut types = Vec::new();
let trait_path = if let Some((_, path, _)) = impl_.trait_ {
path
} else {
compile_error(impl_.span(), "impl block must implement a trait");
Path::from(format_ident!("__mockall_invalid"))
};
let ss_name = MockTrait::ss_name_priv(&trait_path);
let self_path = match *impl_.self_ty {
Type::Path(mut type_path) =>
type_path.path.segments.pop().unwrap().into_value(),
x => {
compile_error(x.span(),
"mockall_derive only supports mocking traits and structs");
PathSegment::from(Ident::new("", Span::call_site()))
}
};
for ii in impl_.items.into_iter() {
match ii {
ImplItem::Const(iic) => {
consts.push(iic);
},
ImplItem::Fn(iif) => {
let mf = mock_function::Builder::new(&iif.sig, vis)
.attrs(&iif.attrs)
.levels(2)
.call_levels(0)
.struct_(structname)
.struct_generics(struct_generics)
.trait_(&ss_name)
.build();
methods.push(mf);
},
ImplItem::Type(iit) => {
types.push(iit);
},
_ => {
compile_error(ii.span(),
"This impl item is not yet supported by MockAll");
}
}
}
MockTrait {
attrs: impl_.attrs,
consts,
generics: impl_.generics,
methods,
ss_name,
trait_path,
self_path,
types,
unsafety: impl_.unsafety
}
}
/// Generate code for the trait implementation on the mock struct
///
/// # Arguments
///
/// * `modname`: Name of the parent struct's private module
// Supplying modname is an unfortunately hack. Ideally MockTrait
// wouldn't need to know that.
pub fn trait_impl(&self, modname: &Ident) -> impl ToTokens {
let trait_impl_attrs = AttrFormatter::new(&self.attrs)
.must_use(false)
.format();
let impl_attrs = AttrFormatter::new(&self.attrs)
.async_trait(false)
.doc(false)
.format();
let (ig, _tg, wc) = self.generics.split_for_impl();
let consts = &self.consts;
let path_args = &self.self_path.arguments;
let calls = self.methods.iter()
.map(|meth| meth.call(Some(modname)))
.collect::<Vec<_>>();
let contexts = self.methods.iter()
.filter(|meth| meth.is_static())
.map(|meth| meth.context_fn(Some(modname)))
.collect::<Vec<_>>();
let expects = self.methods.iter()
.filter(|meth| !meth.is_static())
.map(|meth| {
if meth.is_method_generic() {
// Specific impls with generic methods are TODO.
meth.expect(modname, None)
} else {
meth.expect(modname, Some(path_args))
}
}).collect::<Vec<_>>();
let trait_path = &self.trait_path;
let self_path = &self.self_path;
let types = &self.types;
let unsafety = &self.unsafety;
quote!(
#(#trait_impl_attrs)*
#unsafety impl #ig #trait_path for #self_path #wc {
#(#consts)*
#(#types)*
#(#calls)*
}
#(#impl_attrs)*
impl #ig #self_path #wc {
#(#expects)*
#(#contexts)*
}
)
}
}
fn join_path_segments(path: &Path, sep: &str) -> String {
let mut output = String::new();
for segment in &path.segments {
if write!(
output,
"{}{}",
if output.is_empty() { "" } else { sep },
segment.ident
)
.is_err()
{
break;
};
}
output
}
fn hash_path_arguments(path: &Path) -> Option<u64> {
let mut hasher = DefaultHasher::new();
let mut is_some = false;
for arguments in path
.segments
.iter()
.map(|segment| &segment.arguments)
.filter(|arguments| !arguments.is_empty())
{
arguments.hash(&mut hasher);
is_some = true;
}
is_some.then(|| hasher.finish())
}