Reputation: 463
I can't seem to figure out how to discriminate between members of a discriminated union of function types. See the following example:
type _NumFunc = (n: number) => string;
type _StrFunc = (s: string) => string;
interface NumFunc extends _NumFunc { __type: 'NumFunc'; }
interface StrFunc extends _StrFunc { __type: 'StrFunc'; }
type Func = NumFunc | StrFunc;
let myNumFunc = ((n: number) => `Hello x${n}!`) as NumFunc;
let myStrFunc = ((s: string) => `Hello, ${s}!`) as StrFunc;
let funcGen = (n: number): Func => n % 2 == 0 ? myNumFunc : myStrFunc;
for (let i = 0; i < 2; i++)
{
let func = funcGen(i);
switch (func.__type)
{
case 'NumFunc':
console.log(func(3));
break;
case 'StrFunc':
console.log(func('World!'));
break;
default:
console.error(func);
console.error('How did this happen?');
break;
}
}
I expect the output of this program should be:
Hello x3!
Hello, World!
But if you run this code, you'll see that the default case is invoked for each iteration. Simply logging func
will show the function object, but attempting to access __type
on the object throws an error stating that the type of func
is never
. Why doesn't this approach work, and is there any approach that does allow the use of discriminated unions of function types?
Upvotes: 0
Views: 72
Reputation: 20132
Good question. We need to understand that TypeScript comes with no runtime. What does it mean is, there are no representation of types in the runtime. Types are only aliases on real structures existing in JS itself. In other words, when you define a TS type you need to tell the compiler what exact structure this type is representing in the runtime.
This means that if I say some type A
is represented as {x: string}
, then when I create a value of type A
I need to put there something which is object and has x property as string. TypeScript will never create such structure on its own. It needs to be created or given from lets say server response. So it is runtime responsibility.
Back to your question - the issue is that you are defining that your functions have discriminant in form of __type
property, but you never really set this property in any of these. So you are fooling the type system saying that f is NumFunc and g is StrFunc, but this has no real representation in the runtime. Switch works in the runtime, and there is no __type
property which you use.
To fix the issue u need to manually append this property to functions. For example:
let myStrFunc = ((s: string) => `Hello, ${s}!`) as StrFunc;
myStrFunc.__type = 'StrFunc'
let myNumFunc = ((n: number) => `Hello x${n}!`) as NumFunc;
myNumFunc .__type = 'NumFunc'
Now both functions will have wanted discriminant and should work. Hope it helps.
Upvotes: 2