#[macro_export] macro_rules! mock { ( $mock: ident; impl $mocked_trait: ident for $mocked_trait_target: ident { $( fn $func: ident$(< $($type_param: tt$(: ($($type_param_bound: tt +)+))?),* >)?( self: ($($self_type: tt)+), $($func_param: ident: $func_param_type: ty),* )$( -> $return_type: ty)? $(where ($($where: tt)*))?; )* } ) => { $crate::__private::paste! { mod [<__ $mock>] { use super::*; pub struct $mock { $( [<$func _expectations>]: std::collections::HashMap< Vec<$crate::__private::type_id::TypeID>, [<$mock Expectation _ $func>]$(<$($crate::__to_unit!($type_param)),*>)? >, )* } impl $mock { pub fn new() -> Self { Self { $( [<$func _expectations>]: std::collections::HashMap::new() ),* } } $( #[allow(unused)] pub(crate) fn []$(< $($type_param$(: $($type_param_bound +)*)?),* >)?(&mut self) -> &mut [<$mock Expectation _ $func>]$(<$($type_param),*>)? $(where $($where)*)? { let ids = vec![ $($($crate::__private::type_id::TypeID::of::<$type_param>()),*)? ]; let expectation = [<$mock Expectation _ $func>]$(::<$($type_param),*>)?::new() .strip_type_params(); self.[<$func _expectations>].insert(ids.clone(), expectation); self.[<$func _expectations>] .get_mut(&ids) .unwrap() .with_type_params_mut() } )* } impl $mocked_trait for $mock { $( fn $func$(<$($type_param$(: $($type_param_bound +)+)?),*>)?( self: $($self_type)+, $($func_param: $func_param_type),* )$( -> $return_type)? $(where $($where)*)? { let ids = vec![ $($($crate::__private::type_id::TypeID::of::<$type_param>()),*)? ]; let expectation = self .[<$func _expectations>] .get(&ids) .expect(concat!( "No expectation found for function ", stringify!($func) )) .with_type_params$(::<$($type_param),*>)?(); let Some(returning) = &expectation.returning else { panic!(concat!( "Expectation for function", stringify!($func), " is missing a function to call") ); }; returning(self, $($func_param),*) } )* } $( #[allow(non_camel_case_types, non_snake_case)] pub struct [<$mock Expectation _ $func>]$(<$($type_param),*>)? { returning: Option< fn( $crate::__replace_ref_type!($($self_type)*, $mock), $($func_param_type),* )$( -> $return_type)?>, $( $([<$type_param _phantom>]: std::marker::PhantomData<$type_param>,)* )? } impl$(<$($type_param),*>)? [<$mock Expectation _ $func>]$(< $($type_param),* >)? { #[allow(unused)] fn new() -> Self { Self { returning: None, $( $([<$type_param _phantom>]: std::marker::PhantomData,)* )? } } #[allow(unused)] pub fn returning( &mut self, func: fn( $crate::__replace_ref_type!($($self_type)*, $mock), $($func_param_type),* )$( -> $return_type)? ) -> &mut Self { self.returning = Some(func); self } #[allow(unused)] fn strip_type_params( self, ) -> [<$mock Expectation _ $func>]$(<$($crate::__to_unit!($type_param)),*>)? { $crate::__if_empty_else!(($($($type_param)*)?); { // No type parameters are present self }, { // Type parameters are present unsafe { std::mem::transmute(self) } }) } } impl [<$mock Expectation _ $func>]$(<$($crate::__to_unit!($type_param)),*>)? { fn with_type_params$(<$($type_param),*>)?( &self, ) -> &[<$mock Expectation _ $func>]$(<$($type_param),*>)? { unsafe { &*(self as *const Self).cast() } } #[allow(unused)] fn with_type_params_mut$(<$($type_param),*>)?( &mut self, ) -> &mut [<$mock Expectation _ $func>]$(<$($type_param),*>)? { unsafe { &mut *(self as *mut Self).cast() } } } )* } use [<__ $mock>]::$mock; } }; } #[doc(hidden)] pub mod __private { pub use paste::paste; pub mod type_id { #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct TypeID { id: usize, } impl TypeID { #[inline] pub fn of() -> Self { Self { id: Self::of:: as usize, } } } } #[macro_export] #[doc(hidden)] macro_rules! __to_unit { ($anything: tt) => { () }; } #[macro_export] #[doc(hidden)] macro_rules! __replace_ref_type { (&$old_type: tt, $new_type: tt) => { &$new_type }; (&mut $old_type: tt, $new_type: tt) => { &mut $new_type }; } #[macro_export] #[doc(hidden)] macro_rules! __if_empty_else { (($($input: tt)*); $if_empty: block, $else: block) => { $crate::__if_empty_else!(@($($input)*); $if_empty, $else) }; // Empty (@(); $if_empty: block, $else: block) => { $if_empty }; // Not empty (@($($input: tt)+); $if_empty: block, $else: block) => { $else }; } }