I'm trying to specify specific return types based on the values of properties passed to a method.
This works with a single signature, however fails when I try to define different overloads restricted on parameter values.
class Cat{}
class Dog{}
type MyType = {
// This works
//call: ((method: 'cat') => Cat)
// this errors
call: ((method: 'cat') => Cat) | ((method: 'dog') => Dog)
};
var s:MyType = {
call(method:string): Cat | Dog
{
return new Cat()
}
}
// err: Argument of type 'string' is not assignable to parameter of type 'never'
let result = s.call('cat');
Is it possible to achieve what I'm after?
Thanks in advance.
CodePudding user response:
You've got your variance backward.
call: ((method: 'cat') => Cat) | ((method: 'dog') => Dog)
This says "call is either a function that takes the string 'cat' or a function that takes the string 'dog'". There's no way to know which type of function it is, so we can never call it. Typescript performed the following simplifications.
((method: 'cat') => Cat) | ((method: 'dog') => Dog)
((method: 'cat') => Cat | Dog) | ((method: 'dog') => Cat | Dog)
(method: 'cat' & 'dog') => Cat | Dog
(method: never) => Cat | Dog
The first simplification is by covariance of return type. A function that returns Cat can be said to return a supertype (Cat | Dog), and the same of a function which returns Dog. Then we have distributivity of arguments. If we have a value of type ((A) => R) | ((B) => R), then that value can only be called with an argument that is both an A and a B, hence what we really have is (A & B) => R. Finally, 'cat' & 'dog' is never since there are no strings that are equal to both 'cat' and 'dog' simultaneously.
Consider using an intersection instead.
call: ((method: 'cat') => Cat) & ((method: 'dog') => Dog)
This way, call is a function which can be called in one of two ways, and each way will produce a different return type. Typescript performs similar simplifications.
((method: 'cat') => Cat) & ((method: 'dog') => Dog)
((method: 'cat') => Cat | Dog) & ((method: 'dog') => Cat | Dog)
(method: 'cat' | 'dog') => Cat | Dog
The first simplification is the same. The second changes our intersection & to a union | (since argument types are contravariant), so the thing we end up with is a function that takes either 'cat' or 'dog' and produces something that is either a Cat or a Dog.
