Description
Let's say that Cat extends Animal – all Cats are Animals, just with even more restrictions on top. A Dispatchable<Animal> should be assignable to Dispatchable<Cat>. Only Cats will be dispatched to Dispatchable<Cat>, and all Cats are Animals, so a Dispatchable<Animal> is suited to process all Cats that is dispatched to it. Cat is a variant of Animal, but Dispatchable<Animal> is a variant of Dispatchable<Cat>, not the other way around. This means that the relationship between Dispatchable and its type parameter Msg is contravariant.
type LeastRestrictedType = {
field1: "field1";
};
type MiddleRestrictedType = LeastRestrictedType & { field2: "field2" };
type MostRestrictedType = MiddleRestrictedType & { field3: "field3" };
const system = start();
const actor = spawn(system,
(state: undefined, message: MiddleRestrictedType): undefined => {
console.log(message.field1, message.field2);
return state;
}
);
// This should fail because the actor actually uses restrictions
// for MiddleRestrictedType that are not present on LeastRestrictedType.
const smallerActorRef: Dispatchable<LeastRestrictedType> = actor;
dispatch(smallerActorRef, {
field1: "field1",
});
// This fails, but shouldn't because all restrictions on MiddleRestrictedType
// are also present on MostRestrictedType.
const biggerActorRef: Dispatchable<MostRestrictedType> = actor;
dispatch(biggerActorRef, {
field1: "field1",
field2: "field2",
field3: "field3",
});
Expected Behavior
- The TypeScript type system should realize that Dispatchable and its type parameter Msg are contravariant.
- Trying to assign Dispatchable<Cat> to Dispatchable<Animal> should fail.
- Trying to assign Dispatchable<Animal> to Dispatchable<Cat> should succeed.
Current Behavior
- Trying to assign Dispatchable<Cat> to Dispatchable<Animal> succeeds, which leads to the bug illustrated above where the type system allows a message to be passed to a Dispatchable that doesn't have all the restrictions it assumes/needs.
- Trying to assign Dispatchable<Animal> to Dispatchable<Cat> fails, even though all Dispatchable<Animal> are perfectly able to process all Cats.
Possible Solution
Probably some in/out annotations on type parameters in Dispatchable and associated types, to caress the type system into realizing the relationship between Dispatchable and its type parameter Msg is contravariant.
Context
I have met this roadblock many times. Most recently, I tried to create a testing function that expected a LocalActorRef<A | B>, to which I tried to pass a LocalActorRef<A | B | C>. If it can process A, B, and C, it will have no trouble if it only gets A and B, but the type system doesn't like this.
Activity