audunhalland / unimock

A versatile and developer-friendly trait mocking library
MIT License
71 stars 3 forks source link

Generic output redesign #46

Closed audunhalland closed 7 months ago

audunhalland commented 8 months ago

fixes #29

It's a major redesign of the responder/output-related traits.

~It (currently) comes with the following caveat:~ (edit: this is fixed for one generics-level)

type MyResult<T> = Result<T, MyError>;

#[unimock]
trait MyTrait {
     // compile error:
     fn something(&self) -> MyResult<i32>;

     // must instead write:
     fn something<T>(&self) -> Result<i32, MyError>;
}

There exists a possible solution for this. Unimock selects a responder type based on the return signature. The new algorithm would select Generic<Result<Owned<i32>, Owned<i32>>> naively (for Result<i32, i32>. But that's just the semantics of the Generic responder: its inner types also need to implement Respond (so they can actually be nested, which is the whole point).

But there could also be a LeafGeneric responder. The structure -> SomethingGeneric<NonGenericType1, NonGenericType2> could select that instead. The semantics would be that the contained type is not interpreted as a responder, but as the direct response type.

Then the super-generic responder would only be chosen at the outer level in this scenario: -> SomethingGeneric<SomethingElseGeneric<NonGeneric>>.

status

This is now implemented and it works!

The "type hack" is also working. I've chosen some other names for the types, all of the output/responder code has been completely rewritten with a new fundamental architecture and foundational traits.

The core trait is output::Kind, a kind-of higher-kinded descriptor for output categories. The MockFn how has an OutputKind: Kind associated type.

Output kinds have to specify two associated types: Return and Respond types. The former is for storing a value in unimock and the latter is for responding through ApplyFn. The latter represents opt-out of Send + Sync.

Then there are the kinds themselves. The type-hack is about type kinds with generic parameters. A type signature with Option<T> has the Shallow kind, where the T itself is not a kind. A type signature with Option<Option<T>> has a Deep kind, applied in the following way: Deep<Option<Shallow<Option<T>>>>. So the innermost generic type has no restrictions on type aliasing (type MyResult<T> = Result<T, MyError>;), but the outermost has.