diff --git a/src/build.rs b/src/build.rs index 6e62861..e9d5fc2 100644 --- a/src/build.rs +++ b/src/build.rs @@ -3,7 +3,7 @@ use core::marker::PhantomData; use crate::call_pattern::*; use crate::clause::{self}; use crate::fn_mocker::PatternMatchMode; -use crate::output::{IntoCloneResponder, IntoOnceResponder, IntoResponse, Respond, StaticRef}; +use crate::output::{IntoCloneResponder, IntoOnceResponder, Respond}; use crate::private::lib::vec; use crate::property::*; use crate::Clause; @@ -253,8 +253,8 @@ macro_rules! define_response_common_impl { ::Type: Default, { self.wrapper.push_responder( - FunctionResponder:: { - func: crate::private::lib::Box::new(|_, _| Default::default()), + InputsFnResponder:: { + func: crate::private::lib::Box::new(|_| Default::default()), } .into_dyn_responder(), ); @@ -262,85 +262,19 @@ macro_rules! define_response_common_impl { } /// Specify the response of the call pattern by invoking the given closure that can then compute it based on input parameters. - pub fn answers(mut self, func: C) -> Quantify<'p, F, O> - where - C: (Fn(F::Inputs<'_>) -> R) + Send + Sync + 'static, - R: IntoResponse, - { - self.wrapper.push_responder( - FunctionResponder:: { - func: crate::private::lib::Box::new(move |inputs, _ctx| { - func(inputs).into_response() - }), - } - .into_dyn_responder(), - ); + pub fn responds(mut self, respond_fn: &'static F::RespondFn) -> Quantify<'p, F, O> { + self.wrapper + .push_responder(StaticFnResponder:: { respond_fn }.into_dyn_responder()); self.quantify() } /// Specify the response of the call pattern by invoking the given closure that can then compute it based on input parameters. - /// - /// This variant passes an [AnswerContext] as the second parameter. - pub fn answers_ctx(mut self, func: C) -> Quantify<'p, F, O> - where - C: (Fn(F::Inputs<'_>, AnswerContext<'_, '_, '_, F>) -> R) + Send + Sync + 'static, - R: IntoResponse, - { - self.wrapper.push_responder( - FunctionResponder:: { - func: crate::private::lib::Box::new(move |inputs, ctx| { - func(inputs, ctx).into_response() - }), - } - .into_dyn_responder(), - ); - self.quantify() - } - - /// Specify the response of the call pattern by invoking the given closure that supports mutating _one_ `&mut` parameter from the mocked signature. - pub fn mutates(mut self, func: C) -> Quantify<'p, F, O> - where - C: (Fn(&mut F::Mutation<'_>, F::Inputs<'_>) -> R) + Send + Sync + 'static, - R: IntoResponse, - { - self.wrapper.push_responder( - FunctionResponder:: { - func: crate::private::lib::Box::new(move |inputs, ctx| { - func(ctx.mutation, inputs).into_response() - }), - } - .into_dyn_responder(), - ); - self.quantify() - } - - /// Specify the response of the call pattern to be a static reference to leaked memory. - /// - /// The value may be based on the value of input parameters. - /// - /// This version will produce a new memory leak for _every invocation_ of the answer function. - /// - /// This method should only be used when computing a reference based - /// on input parameters is necessary, which should not be a common use case. - pub fn answers_leaked_ref(mut self, func: C) -> Quantify<'p, F, O> - where - F: MockFn>, - C: (Fn(F::Inputs<'_>) -> R) + Send + Sync + 'static, - R: core::borrow::Borrow + 'static, - T: 'static, - { - use crate::private::lib::Box; - self.wrapper.push_responder( - FunctionResponder:: { - func: Box::new(move |inputs, _| { - let value = func(inputs); - let leaked_ref = Box::leak(Box::new(value)); - - >::borrow(leaked_ref) - }), - } - .into_dyn_responder(), - ); + pub fn responds_boxed( + mut self, + respond_fn: crate::private::lib::Box, + ) -> Quantify<'p, F, O> { + self.wrapper + .push_responder(BoxedFnResponder:: { respond_fn }.into_dyn_responder()); self.quantify() } @@ -595,18 +529,3 @@ where sink.push(F::info(), self.wrapper.into_owned()) } } - -/// AnswerContext represents known information in the current mocking context. -pub struct AnswerContext<'u, 'm0, 'm1, F: MockFn> { - pub(crate) unimock: &'u Unimock, - - /// The mutation of the currently executing mock function. - pub mutation: &'m0 mut F::Mutation<'m1>, -} - -impl<'u, 'm0, 'm1, F: MockFn> AnswerContext<'u, 'm0, 'm1, F> { - /// Construct a new unimock instance as a clone of the one currently in the context. - pub fn clone_instance(&self) -> Unimock { - self.unimock.clone() - } -} diff --git a/src/call_pattern.rs b/src/call_pattern.rs index 397e989..600a543 100644 --- a/src/call_pattern.rs +++ b/src/call_pattern.rs @@ -1,7 +1,6 @@ use crate::private::lib::{Box, String, Vec}; use core::any::Any; -use crate::build; use crate::cell::{Cell, CloneCell, FactoryCell}; use crate::debug; use crate::output::{Respond, ResponderError}; @@ -103,7 +102,9 @@ pub(crate) struct DynCallOrderResponder { pub(crate) enum DynResponder { Cell(DynCellResponder), Borrow(DynBorrowResponder), - Function(DynFunctionResponder), + InputsFn(DynInputsFnResponder), + StaticFn(DynStaticFnResponder), + BoxedFn(DynBoxedFnResponder), Panic(String), Unmock, CallDefaultImpl, @@ -169,7 +170,9 @@ impl DynResponder { pub(crate) struct DynCellResponder(AnyBox); pub(crate) struct DynBorrowResponder(AnyBox); -pub(crate) struct DynFunctionResponder(AnyBox); +pub(crate) struct DynInputsFnResponder(AnyBox); +pub(crate) struct DynStaticFnResponder(AnyBox); +pub(crate) struct DynBoxedFnResponder(AnyBox); pub trait DowncastResponder { type Downcasted; @@ -193,8 +196,24 @@ impl DowncastResponder for DynBorrowResponder { } } -impl DowncastResponder for DynFunctionResponder { - type Downcasted = FunctionResponder; +impl DowncastResponder for DynInputsFnResponder { + type Downcasted = InputsFnResponder; + + fn downcast(&self) -> PatternResult<&Self::Downcasted> { + downcast_box(&self.0) + } +} + +impl DowncastResponder for DynStaticFnResponder { + type Downcasted = StaticFnResponder; + + fn downcast(&self) -> PatternResult<&Self::Downcasted> { + downcast_box(&self.0) + } +} + +impl DowncastResponder for DynBoxedFnResponder { + type Downcasted = BoxedFnResponder; fn downcast(&self) -> PatternResult<&Self::Downcasted> { downcast_box(&self.0) @@ -209,13 +228,17 @@ pub(crate) struct BorrowResponder { pub borrowable: ::Type, } -pub(crate) struct FunctionResponder { +pub(crate) struct InputsFnResponder { #[allow(clippy::type_complexity)] - pub func: Box< - dyn (Fn(F::Inputs<'_>, build::AnswerContext<'_, '_, '_, F>) -> ::Type) - + Send - + Sync, - >, + pub func: Box) -> ::Type) + Send + Sync>, +} + +pub(crate) struct StaticFnResponder { + pub respond_fn: &'static F::RespondFn, +} + +pub(crate) struct BoxedFnResponder { + pub respond_fn: Box, } impl CellResponder { @@ -233,9 +256,21 @@ where } } -impl FunctionResponder { +impl InputsFnResponder { + pub fn into_dyn_responder(self) -> DynResponder { + DynResponder::InputsFn(DynInputsFnResponder(Box::new(self))) + } +} + +impl StaticFnResponder { + pub fn into_dyn_responder(self) -> DynResponder { + DynResponder::StaticFn(DynStaticFnResponder(Box::new(self))) + } +} + +impl BoxedFnResponder { pub fn into_dyn_responder(self) -> DynResponder { - DynResponder::Function(DynFunctionResponder(Box::new(self))) + DynResponder::BoxedFn(DynBoxedFnResponder(Box::new(self))) } } diff --git a/src/error.rs b/src/error.rs index 9ab34e5..6893622 100644 --- a/src/error.rs +++ b/src/error.rs @@ -49,6 +49,9 @@ pub(crate) enum MockError { NoDefaultImpl { info: MockFnInfo, }, + NoAnswer { + info: MockFnInfo, + }, ExplicitPanic { fn_call: debug::FnActualCall, pattern: debug::CallPatternDebug, @@ -127,6 +130,13 @@ impl core::fmt::Display for MockError { path = info.path ) } + Self::NoAnswer { info } => { + write!( + f, + "{path} did not produce an answer, this is a bug.", + path = info.path + ) + } Self::ExplicitPanic { fn_call, pattern, diff --git a/src/eval.rs b/src/eval.rs index 301561b..4bbce59 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -1,4 +1,3 @@ -use crate::build::AnswerContext; use crate::call_pattern::{ CallPattern, DowncastResponder, DynResponder, PatIndex, PatternError, PatternResult, }; @@ -9,6 +8,7 @@ use crate::mismatch::Mismatches; use crate::output::Output; use crate::private::lib::{String, Vec}; use crate::private::{Evaluation, MismatchReporter}; +use crate::respond::RespondFn; use crate::state::SharedState; use crate::{debug, MockFnInfo, Unimock}; use crate::{FallbackMode, MockFn}; @@ -28,7 +28,7 @@ struct EvalResponder<'u> { pub(crate) fn eval<'u, 'i, F: MockFn>( unimock: &'u Unimock, inputs: F::Inputs<'i>, - mutation: &mut F::Mutation<'_>, + _mutation: &mut F::Mutation<'_>, ) -> MockResult> { let dyn_ctx = DynCtx { info: F::info(), @@ -75,15 +75,37 @@ pub(crate) fn eval<'u, 'i, F: MockFn>( ), } } - DynResponder::Function(dyn_fn_responder) => { + DynResponder::InputsFn(dyn_fn_responder) => { let fn_responder = dyn_ctx.downcast_responder::(dyn_fn_responder, &eval_responder)?; let output = as Output<'u, F::Response>>::from_response( - (fn_responder.func)(inputs, AnswerContext { unimock, mutation }), + (fn_responder.func)(inputs), &unimock.value_chain, ); Ok(Evaluation::Evaluated(output)) } + DynResponder::StaticFn(dyn_responder) => { + let answer_fn_responder = + dyn_ctx.downcast_responder::(dyn_responder, &eval_responder)?; + Ok(Evaluation::Respond( + RespondFn { + unimock, + respond_fn: answer_fn_responder.respond_fn, + }, + inputs, + )) + } + DynResponder::BoxedFn(dyn_responder) => { + let answer_fn_responder = + dyn_ctx.downcast_responder::(dyn_responder, &eval_responder)?; + Ok(Evaluation::Respond( + RespondFn { + unimock, + respond_fn: answer_fn_responder.respond_fn.as_ref(), + }, + inputs, + )) + } DynResponder::Panic(msg) => Err(MockError::ExplicitPanic { fn_call: dyn_ctx.fn_call(), pattern: eval_responder diff --git a/src/lib.rs b/src/lib.rs index 47f6fe8..854f2d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -439,6 +439,8 @@ pub mod build; pub mod output; /// Traits and types used for describing the properties of various mock types. pub mod property; +/// Answer functions. +pub mod respond; /// Mock APIs for various crates. pub mod mock; @@ -1028,6 +1030,9 @@ pub trait MockFn: Sized + 'static { /// A type that describes the mocked function's actual output type. type Output<'u>: output::Output<'u, Self::Response>; + /// Experimental: respond Fn + type RespondFn: ?Sized + Send + Sync; + /// Static information about the mocked method fn info() -> MockFnInfo; diff --git a/src/mock/std.rs b/src/mock/std.rs index aaaa200..9202dcc 100644 --- a/src/mock/std.rs +++ b/src/mock/std.rs @@ -75,7 +75,10 @@ pub mod process { /// Unimock mock API for [std::process::Termination]. #[allow(non_snake_case)] pub mod TerminationMock { - use crate::{output::Owned, MockFn}; + use crate::{ + output::{Owned, Respond}, + MockFn, + }; #[allow(non_camel_case_types)] /// MockFn for [`Termination::report() -> ExitCode`](std::process::Termination::report). @@ -87,6 +90,7 @@ pub mod process { impl MockFn for report { type Inputs<'i> = (); type Mutation<'m> = (); + type RespondFn = dyn Fn() -> ::Type + Send + Sync; type Response = Owned; type Output<'u> = Self::Response; diff --git a/src/private.rs b/src/private.rs index aa3c1fe..8b67d32 100644 --- a/src/private.rs +++ b/src/private.rs @@ -2,6 +2,7 @@ use crate::call_pattern::InputIndex; use crate::debug; use crate::mismatch::{Mismatch, MismatchKind}; use crate::output::Output; +use crate::respond::RespondFn; use crate::{call_pattern::MatchingFn, *}; use lib::{Box, String, Vec}; @@ -17,6 +18,8 @@ pub use crate::default_impl_delegator::*; pub enum Evaluation<'u, 'i, F: MockFn> { /// Function evaluated to its output. Evaluated( as Output<'u, F::Response>>::Type), + /// Function should be answered + Respond(RespondFn<'u, F>, F::Inputs<'i>), /// Function not yet evaluated, should be unmocked. Unmocked(F::Inputs<'i>), /// Function not yet evaluated, should call default implementation. @@ -30,6 +33,7 @@ impl<'u, 'i, F: MockFn> Evaluation<'u, 'i, F> { pub fn unwrap(self, unimock: &Unimock) -> as Output<'u, F::Response>>::Type { let error = match self { Self::Evaluated(output) => return output, + Self::Respond(..) => error::MockError::NoAnswer { info: F::info() }, Self::Unmocked(_) => error::MockError::CannotUnmock { info: F::info() }, Self::CallDefaultImpl(_) => error::MockError::NoDefaultImpl { info: F::info() }, }; diff --git a/src/respond.rs b/src/respond.rs new file mode 100644 index 0000000..be2a338 --- /dev/null +++ b/src/respond.rs @@ -0,0 +1,107 @@ +use crate::private::lib::Box; +use core::{marker::PhantomData, ops::Deref}; + +use crate::{ + output::{self, IntoResponse, Output, StaticRef}, + MockFn, Unimock, +}; + +/// A borrowed Answering function. +pub struct RespondFn<'u, F: MockFn> { + pub(crate) unimock: &'u Unimock, + pub(crate) respond_fn: &'u F::RespondFn, +} + +impl<'u, F: MockFn> Deref for RespondFn<'u, F> { + type Target = F::RespondFn; + + fn deref(&self) -> &Self::Target { + self.respond_fn + } +} + +impl<'u, F: MockFn> RespondFn<'u, F> { + /// Run the respond function + pub fn run(&self, func: G) -> as Output<'u, F::Response>>::Type + where + G: FnOnce(ResponseTx), + { + let mut output = None; + let response_tx = ResponseTx { + ph: PhantomData, + unimock: self.unimock, + output: &mut output, + }; + func(response_tx); + + output.expect("No response sent") + } +} + +/// A response sender +#[must_use = "Must send the response"] +pub struct ResponseTx<'u, 'o, F: MockFn> { + ph: PhantomData<&'u F>, + unimock: &'u Unimock, + output: &'o mut Option< as Output<'u, F::Response>>::Type>, +} + +impl<'u, 'o, F: MockFn> ResponseTx<'u, 'o, F> { + /// Send a response. + pub fn send(self, input: T) + where + T: IntoResponse<::Response>, + { + let response = input.into_response(); + let output = as output::Output<'u, ::Response>>::from_response( + response, + &self.unimock.value_chain, + ); + *self.output = Some(output); + } + + /// Put a response. + pub fn put(self, input: T) + where + T: IntoResponse<::Response>, + { + let response = input.into_response(); + let output = as output::Output<'u, ::Response>>::from_response( + response, + &self.unimock.value_chain, + ); + *self.output = Some(output); + } + + /// Send a response as a static reference to leaked memory. + /// + /// The value may be based on the value of input parameters. + /// + /// This version will produce a new memory leak for _every invocation_ of the answer function. + /// + /// This method should only be used when computing a reference based + /// on input parameters is necessary, which should not be a common use case. + pub fn send_leaked_ref(self, input: T) + where + F: MockFn>, + T: core::borrow::Borrow + 'static, + R: 'static, + { + let response = >::borrow(Box::leak(Box::new(input))); + let output = as output::Output<'u, ::Response>>::from_response( + response, + &self.unimock.value_chain, + ); + + *self.output = Some(output); + } + + /// Send the unimock mocker itself as the response. + pub fn send_unimock(self) + where + Unimock: IntoResponse<::Response>, + { + let unimock = self.unimock.clone(); + self.send(unimock); + } +} diff --git a/tests/it/basic.rs b/tests/it/basic.rs index 9714531..1365dce 100644 --- a/tests/it/basic.rs +++ b/tests/it/basic.rs @@ -56,7 +56,7 @@ fn owned_output_works() { &Unimock::new( OwnedMock::foo .next_call(matching!(_, _)) - .answers(|(a, b)| format!("{a}{b}")) + .responds(&|(a, b), tx| tx.send(format!("{a}{b}"))) .once() ), "a", @@ -305,7 +305,7 @@ mod no_debug { fn can_match_a_non_debug_argument() { let unimock = Unimock::new(VeryPrimitiveMock::primitive.stub(|each| { each.call(matching!(PrimitiveEnum::Bar, _)) - .answers(|_| PrimitiveEnum::Foo); + .responds(&|_, tx| tx.send(PrimitiveEnum::Foo)); })); match unimock.primitive(PrimitiveEnum::Bar, "") { @@ -319,7 +319,7 @@ mod no_debug { fn should_format_non_debug_input_with_a_question_mark() { Unimock::new(VeryPrimitiveMock::primitive.stub(|each| { each.call(matching!(PrimitiveEnum::Bar, _)) - .answers(|_| PrimitiveEnum::Foo); + .responds(&|_, tx| tx.send(PrimitiveEnum::Foo)); })) .primitive(PrimitiveEnum::Foo, ""); } @@ -408,7 +408,7 @@ fn various_borrowing() { &Unimock::new( BorrowingMock::borrow .next_call(matching!(_)) - .answers(|input| format!("{input}{input}")) + .responds(&|input, tx| tx.send(format!("{input}{input}"))) .once() ), "yo" @@ -419,7 +419,7 @@ fn various_borrowing() { ::borrow_static(&Unimock::new( BorrowingMock::borrow_static .next_call(matching!(_)) - .answers_leaked_ref(|_| "yoyoyo".to_string()) + .responds(&|_, tx| tx.send_leaked_ref("yoyoyo".to_string())) .once() )) ); @@ -908,7 +908,7 @@ fn non_sync_return() { let u = Unimock::new( NonSendMock::return_cell .next_call(matching!()) - .answers(|_| Cell::new(42)), + .responds(&|_, tx| tx.send(Cell::new(42))), ); assert_eq!(Cell::new(42), u.return_cell()); } @@ -929,10 +929,10 @@ mod mutated_args { #[test] fn can_mutate1() { - let u = Unimock::new(Mut1Mock::mut1_a.next_call(matching!(2, _, 21)).mutates( - |b, (a, _, c)| { + let u = Unimock::new(Mut1Mock::mut1_a.next_call(matching!(2, _, 21)).responds( + &|(a, b, c), tx| { *b = a * c; - a + c + tx.send(a + c); }, )); @@ -951,7 +951,7 @@ mod mutated_args { } } - struct LifetimeArg<'a> { + pub struct LifetimeArg<'a> { data: PhantomData<&'a ()>, } @@ -967,9 +967,9 @@ mod mutated_args { let u = Unimock::new( ImpossibleMutableLifetimeArgMock::mut_b_impossible .next_call(matching!(2, _, _)) - .mutates(|c, (a, _impossible, _phantom_mut)| { + .responds(&|(a, _, c), tx| { *c *= a; - *c + a + tx.send(*c + a); }), ); @@ -1006,10 +1006,10 @@ mod borrow_dyn { let u = Unimock::new(( BorrowDynMock::borrow_dyn .next_call(matching!()) - .answers_ctx(|_, ctx| ctx.clone_instance()), + .responds(&|_, tx| tx.send_unimock()), BorrowDynMock::borrow_dyn_opt .next_call(matching!()) - .answers(|_| None::<&dyn BorrowDyn>), + .responds(&|_, tx| tx.send(None::<&dyn BorrowDyn>)), )); let u2 = u.borrow_dyn(); @@ -1072,7 +1072,11 @@ mod no_verify_in_drop { } fn mock() -> Unimock { - Unimock::new(TraitMock::foo.next_call(matching!()).answers(|_| ())) + Unimock::new( + TraitMock::foo + .next_call(matching!()) + .responds(&|_, tx| tx.send(())), + ) } fn mock_no_verify_in_drop() -> Unimock { @@ -1110,3 +1114,27 @@ mod debug_mut_arg { fn f(&self, arg1: &mut Arg, arg2: &mut Arg); } } + +mod answer_fn { + use unimock::*; + + #[unimock(api = TraitMock)] + trait Trait { + fn foo(&self, a: &i32, b: &mut i32, c: &mut i32) -> i32; + } + + #[test] + fn test() { + let u = Unimock::new(TraitMock::foo.next_call(matching!(42, _, _)).responds( + &|(_, b, c), tx| { + *b += 1; + *c += 1; + tx.send(1337); + }, + )); + let mut b = 0; + let mut c = 0; + assert_eq!(1337, u.foo(&42, &mut b, &mut c)); + assert_eq!(1, b); + } +} diff --git a/tests/it/default_impl.rs b/tests/it/default_impl.rs index 1bf484d..d95c33c 100644 --- a/tests/it/default_impl.rs +++ b/tests/it/default_impl.rs @@ -30,7 +30,7 @@ mod default_impl_borrowed { Unimock::new( DefaultBodyMock::default_body .next_call(matching!(21)) - .answers(|_| 777) + .responds(&|_, tx| tx.send(777)) ) .default_body(21) ); @@ -43,7 +43,7 @@ mod default_impl_borrowed { Unimock::new( DefaultBodyMock::core .next_call(matching!(42)) - .answers(|_| 666) + .responds(&|_, tx| tx.send(666)) ) .default_body(21) ); @@ -69,7 +69,7 @@ mod default_impl_mut { Unimock::new( MutDefaultBodyMock::default_body .next_call(matching!(21)) - .answers(|_| 777) + .responds(&|_, tx| tx.send(777)) ) .default_body(21) ); @@ -82,7 +82,7 @@ mod default_impl_mut { Unimock::new( MutDefaultBodyMock::core .next_call(matching!(42)) - .answers(|_| 666) + .responds(&|_, tx| tx.send(666)) ) .default_body(21) ); @@ -105,7 +105,7 @@ mod default_impl_mut { Unimock::new( MutDefaultBodyBorrowMock::borrow_ret .next_call(matching!(42)) - .answers(|_| 666) + .responds(&|_, tx| tx.send(666)) ) .default_borrow_ret(21) ); @@ -133,7 +133,7 @@ mod default_impl_rc { Rc::new(Unimock::new( DefaultBodyMock::default_body .next_call(matching!(21)) - .answers(|_| 777) + .responds(&|_, tx| tx.send(777)) )) .default_body(21) ); @@ -161,7 +161,7 @@ mod default_impl_arc { Arc::new(Unimock::new( DefaultBodyMock::default_body .next_call(matching!(21)) - .answers(|_| 777) + .responds(&|_, tx| tx.send(777)) )) .default_body(21) ); @@ -256,7 +256,7 @@ mod default_impl_pin { let mut unimock = Unimock::new( PinDefault1Mock::pinned .next_call(matching!(123)) - .answers(|_| 666), + .responds(&|_, tx| tx.send(666)), ); assert_eq!( &666, diff --git a/tests/it/generic.rs b/tests/it/generic.rs index 14d8e19..2b3349f 100644 --- a/tests/it/generic.rs +++ b/tests/it/generic.rs @@ -334,8 +334,9 @@ mod issue_37_mutation_with_generics { MockMock::func .with_types::<()>() .next_call(matching!()) - .mutates(|foo, _| { + .responds(&|(_, foo), tx| { foo.baz += 1; + tx.send(()); }); } } diff --git a/tests/it/mixed.rs b/tests/it/mixed.rs index 1bac2e5..561184d 100644 --- a/tests/it/mixed.rs +++ b/tests/it/mixed.rs @@ -180,7 +180,7 @@ fn mixed_tuple_clone_combinatorics_many() { let u = Unimock::new( MixedTupleMock::tuple4 .each_call(matching!()) - .answers(|_| (clone::Nope, clone::Nope, clone::Sure, clone::Sure)), + .responds(&|_, tx| tx.send((clone::Nope, clone::Nope, clone::Sure, clone::Sure))), ); for _ in 0..3 { diff --git a/tests/it/std.rs b/tests/it/std.rs index ac3c252..61845c0 100644 --- a/tests/it/std.rs +++ b/tests/it/std.rs @@ -16,8 +16,8 @@ fn test_display() { "u", Unimock::new( DisplayMock::fmt - .next_call(matching!()) - .mutates(|f, _| write!(f, "u")) + .next_call(matching!(_)) + .responds(&|f, rsp| { rsp.put(write!(f, "u")) }) ) .to_string() ); @@ -39,7 +39,7 @@ fn test_debug() { let unimock = Unimock::new( DebugMock::fmt .next_call(matching!()) - .mutates(|f, _| write!(f, "u")), + .responds(&|f, tx| tx.send(write!(f, "u"))), ); assert_eq!("u", format!("{unimock:?}")); @@ -50,10 +50,10 @@ fn test_read() { let mut reader = BufReader::new(Unimock::new(( ReadMock::read .next_call(matching!(_)) - .mutates(|mut f, _| f.write(b"ok")), + .responds(&|mut f, tx| tx.send(f.write(b"ok"))), ReadMock::read .next_call(matching!(_)) - .mutates(|mut f, _| f.write(b"\n")), + .responds(&|mut f, tx| tx.send(f.write(b"\n"))), ))); let mut line = String::new(); @@ -95,7 +95,7 @@ fn test_fmt_io_duplex_default_impl_implicit() { let unimock = Unimock::new(( DisplayMock::fmt .next_call(matching!()) - .mutates(|f, _| write!(f, "hello {}", "unimock".to_string())), + .responds(&|f, tx| tx.send(write!(f, "hello {}", "unimock".to_string()))), // NOTE: write! calls `write_all` which should get re-routed to `write`: WriteMock::write .next_call(matching!(eq!(b"hello "))) @@ -115,7 +115,7 @@ fn test_fmt_io_duplex_default_impl_explicit() { let unimock = Unimock::new(( DisplayMock::fmt .next_call(matching!()) - .mutates(|f, _| write!(f, "hello {}", "unimock".to_string())), + .responds(&|f, tx| tx.send(write!(f, "hello {}", "unimock".to_string()))), WriteMock::write_all .next_call(matching!(eq!(b"hello "))) .default_implementation(), diff --git a/tests/it/test_mock_tokio.rs b/tests/it/test_mock_tokio.rs index ecb8fa8..cf59ada 100644 --- a/tests/it/test_mock_tokio.rs +++ b/tests/it/test_mock_tokio.rs @@ -9,19 +9,19 @@ async fn test_tokio_read() { let mut u = Unimock::new(( AsyncReadMock::poll_read .next_call(matching!()) - .mutates(|buf, (_, _)| { + .responds(&|(_, buf), tx| { buf.put_slice(&[1, 2, 3]); // Can return Poll::Ready explicitly - Poll::Ready(Ok(())) + tx.send(Poll::Ready(Ok(()))); }), AsyncReadMock::poll_read .next_call(matching!()) - .mutates(|buf, _| { + .responds(&|(_, buf), tx| { buf.put_slice(&[5, 6, 7]); // Also can return just Ok(()) because of `impl From for Poll` - Ok(()) + tx.send(Ok(())); }), )); let mut buf = [0; 10]; diff --git a/unimock_macros/src/unimock/method.rs b/unimock_macros/src/unimock/method.rs index 2361db2..1773494 100644 --- a/unimock_macros/src/unimock/method.rs +++ b/unimock_macros/src/unimock/method.rs @@ -337,7 +337,7 @@ pub fn extract_methods<'s>( ident: pat_ident.ident.clone(), ty: util::substitute_lifetimes( type_ref.elem.as_ref().clone(), - &syn::parse_quote!('m), + Some(&syn::parse_quote!('m)), ), }) } diff --git a/unimock_macros/src/unimock/mod.rs b/unimock_macros/src/unimock/mod.rs index ed41eb4..a31fcff 100644 --- a/unimock_macros/src/unimock/mod.rs +++ b/unimock_macros/src/unimock/mod.rs @@ -1,5 +1,5 @@ use quote::{quote, quote_spanned, ToTokens}; -use syn::parse_quote; +use syn::{parse_quote, FnArg}; mod associated_future; mod attr; @@ -239,13 +239,6 @@ fn def_mock_fn( let input_lifetime = &attr.input_lifetime; let input_types_tuple = InputTypesTuple::new(method, trait_info, attr); - let mutation = if let Some(mutated_arg) = &method.mutated_arg { - let ty = &mutated_arg.ty; - quote! { #ty } - } else { - quote! { () } - }; - let generic_params = util::Generics::fn_params(trait_info, Some(method)); let generic_args = util::Generics::fn_args(trait_info, Some(method), InferImplTrait(false)); let where_clause = &trait_info.input_trait.generics.where_clause; @@ -256,8 +249,37 @@ fn def_mock_fn( method.mockfn_doc_attrs(&trait_info.trait_path) }; - let response_associated_type = method.output_structure.response_associated_type(prefix); - let output_associated_type = method.output_structure.output_associated_type(prefix); + let response_assoc_type = method.output_structure.response_associated_type(prefix); + let output_assoc_type = method.output_structure.output_associated_type(prefix); + let respond_fn_params = { + let params: Vec<_> = method + .adapted_sig + .inputs + .iter() + .filter_map(|input| match input { + FnArg::Receiver(_) => None, + FnArg::Typed(pat_type) => Some(pat_type.ty.as_ref().clone()), + }) + .map(|mut ty| { + ty = util::substitute_lifetimes(ty, None); + ty = util::self_type_to_unimock(ty, trait_info.input_trait, attr); + ty + }) + .collect(); + + if params.len() == 1 { + quote! { #(#params)* } + } else { + quote! { (#(#params),*) } + } + }; + + let mutation = if let Some(mutated_arg) = &method.mutated_arg { + let ty = &mutated_arg.ty; + quote! { #ty } + } else { + quote! { () } + }; let debug_inputs_fn = method.generate_debug_inputs_fn(attr); @@ -280,8 +302,9 @@ fn def_mock_fn( impl #generic_params #prefix::MockFn for #mock_fn_path #generic_args #where_clause { type Inputs<#input_lifetime> = #input_types_tuple; type Mutation<'m> = #mutation; - type Response = #response_associated_type; - type Output<'u> = #output_associated_type; + type Response = #response_assoc_type; + type Output<'u> = #output_assoc_type; + type RespondFn = dyn Fn(#respond_fn_params, #prefix::respond::ResponseTx) + Send + Sync; fn info() -> #prefix::MockFnInfo { #prefix::MockFnInfo::new::() @@ -312,7 +335,8 @@ fn def_mock_fn( ) -> impl for<#input_lifetime, 'm> #prefix::MockFn< Inputs<#input_lifetime> = #input_types_tuple, Mutation<'m> = #mutation, - Response = #response_associated_type, + Response = #response_assoc_type, + RespondFn = <#mock_fn_ident #generic_args as #prefix::MockFn>::RespondFn, > #where_clause { @@ -466,12 +490,29 @@ fn def_method_impl( match &receiver { Receiver::MutRef { .. } | Receiver::Pin { .. } => { - let default_impl_delegate_arm_polonius = if method.method.default.is_some() { - let eval_pattern = method.inputs_destructuring( - InputsSyntax::EvalPatternMutAsWildcard, - Tupled(true), + let eval_pattern = method.inputs_destructuring( + InputsSyntax::EvalPatternMutAsWildcard, + Tupled(true), + attr, + ); + + let answer_arm = { + let fn_params = method.inputs_destructuring( + InputsSyntax::FnParams, + Tupled(false), attr, ); + quote! { + #prefix::private::Evaluation::Respond(__respond_fn, #eval_pattern) => { + #prefix::polonius::_return!( + __respond_fn.run(|__tx| { + __respond_fn((#fn_params), __tx) + }) + ) + } + } + }; + let default_impl_delegate_arm_polonius = if method.method.default.is_some() { let args = method.inputs_destructuring(InputsSyntax::FnParams, Tupled(true), attr); Some(quote! { @@ -486,13 +527,14 @@ fn def_method_impl( let polonius_return_type: syn::Type = match method.method.sig.output.clone() { syn::ReturnType::Default => syn::parse_quote!(()), syn::ReturnType::Type(_arrow, ty) => { - util::substitute_lifetimes(*ty, &syn::parse_quote!('polonius)) + util::substitute_lifetimes(*ty, Some(&syn::parse_quote!('polonius))) } }; let polonius = quote_spanned! { span=> #prefix::polonius::_polonius!(|#self_ref| -> #polonius_return_type { match #prefix::private::eval::<#mock_fn_path #eval_generic_args>(#self_ref, #inputs_eval_params, #mutated_param) { + #answer_arm #unmock_arm #default_impl_delegate_arm_polonius e => #prefix::polonius::_return!(e.unwrap(#self_ref)) @@ -516,12 +558,27 @@ fn def_method_impl( } } _ => { - let default_impl_delegate_arm = if method.method.default.is_some() { - let eval_pattern = method.inputs_destructuring( - InputsSyntax::EvalPatternMutAsWildcard, - Tupled(true), + let eval_pattern = method.inputs_destructuring( + InputsSyntax::EvalPatternMutAsWildcard, + Tupled(true), + attr, + ); + + let answer_arm = { + let fn_params = method.inputs_destructuring( + InputsSyntax::FnParams, + Tupled(false), attr, ); + quote! { + #prefix::private::Evaluation::Respond(__respond_fn, #eval_pattern) => { + __respond_fn.run(|__tx| { + __respond_fn((#fn_params), __tx) + }) + } + } + }; + let default_impl_delegate_arm = if method.method.default.is_some() { Some(quote! { #prefix::private::Evaluation::CallDefaultImpl(#eval_pattern) => { #default_delegator_call @@ -533,6 +590,7 @@ fn def_method_impl( quote_spanned! { span=> match #prefix::private::eval::<#mock_fn_path #eval_generic_args>(#self_ref, #inputs_eval_params, #mutated_param) { + #answer_arm #unmock_arm #default_impl_delegate_arm e => e.unwrap(#self_ref) @@ -661,7 +719,7 @@ impl InputTypesTuple { }, ) .map(|mut ty| { - ty = util::substitute_lifetimes(ty, input_lifetime); + ty = util::substitute_lifetimes(ty, Some(input_lifetime)); ty = util::self_type_to_unimock(ty, trait_info.input_trait, attr); ty }) diff --git a/unimock_macros/src/unimock/util.rs b/unimock_macros/src/unimock/util.rs index ca70ae6..13c4638 100644 --- a/unimock_macros/src/unimock/util.rs +++ b/unimock_macros/src/unimock/util.rs @@ -270,19 +270,22 @@ impl<'t> quote::ToTokens for TypedPhantomData<'t> { } } -pub fn substitute_lifetimes(mut ty: syn::Type, lifetime: &syn::Lifetime) -> syn::Type { +pub fn substitute_lifetimes(mut ty: syn::Type, lifetime: Option<&syn::Lifetime>) -> syn::Type { struct LifetimeReplace<'s> { - lifetime: &'s syn::Lifetime, + lifetime: Option<&'s syn::Lifetime>, } impl<'s> syn::visit_mut::VisitMut for LifetimeReplace<'s> { fn visit_type_reference_mut(&mut self, reference: &mut syn::TypeReference) { - reference.lifetime = Some(self.lifetime.clone()); + reference.lifetime = self.lifetime.cloned(); syn::visit_mut::visit_type_reference_mut(self, reference); } fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) { - *lifetime = self.lifetime.clone(); + *lifetime = match self.lifetime { + Some(lt) => lt.clone(), + None => syn::Lifetime::new("'_", lifetime.span()), + }; syn::visit_mut::visit_lifetime_mut(self, lifetime); } }