The following code will not compile in TypeScript 2.1.4 giving the error:
Error
Error:(6, 31) TS2349:Cannot invoke an expression whose type lacks a call signature. Type '((args: string[], action: A) => string[]) | ((args: string[], action: C) => string[])' has no compatible call signatures.
Code
/*
* Set up a function to take some arguments
* and an action and use the map to run the appropriate
* function bases on the type property of the action
*/
const caller = (args: string[] = [], action): string[] => {
return map[action.type] ? map[action.type](args, action) : args;
};
interface Action {
type: any;
}
const TYPE_A = "type_a";
interface A extends Action {
from: number;
to: number;
id?: number; // optional parameters causing the issue.
prop1?: number;
}
const TYPE_B = "type_b";
interface B extends Action {
from: number;
to: number;
}
const TYPE_C = "type_c";
interface C extends Action {
id: number;
prop1: number;
}
const map = {
[TYPE_A]: (args: string[], action: A) => {
return ["a"];
},
[TYPE_B]: (args: string[], action: B) => {
return ["b"];
},
[TYPE_C]: (args: string[], action: C) => {
return ["c"];
}
};
caller([], {type: TYPE_A, from: 2, to: 1});
Motivation
My motivation for using an expression as the property in the map is so that I can change the value of the property constants without needing to refactor the map.
Solutions
There are two ways of solving this:
a) Remove the optional fields in interface A.
interface A extends Action {
from: number;
to: number;
id: number; // optional parameters causing the issue not optional.
prop1: number;
}
b) Change the map properties declarations to values and not expressions and keep optional fields.
const map = {
"type_a" : (args: string[], action: A) => {
return ["a"];
},
"type_b": (args: string[], action: B) => {
return ["b"];
},
"type_c": (args: string[], action: C) => {
return ["c"];
}
};
Question
My question is why is the error shown in the first place, can someone explain this to me?
The reason is that
AandCare incompatible becauseprop1is optional inAand required inC. So you can't use function that takesCin place where a function that takesAis needed:errors:
When you declare a map with literal property names, type inference can figure out when you do
caller([], {type: TYPE_A, from: 2, to: 1});, you are actually accessing a value with"type_a"key, so it knows that function parameter type is exactlyA. It can't do that when map is declared with calculated keys, probably because it just does not evaluate expressions for keys at compile time, so it infers a union type for map values, and two members of the union are incompatible with each other becauseAandCare incompatible.You can also get around this by just explicitly declaring type for
map:also works.