Home > database >  Defining function signatures for specific parameter values - argument of type 'string' is
Defining function signatures for specific parameter values - argument of type 'string' is

Time:12-29

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.

  • Related