Reputation: 8484
I have the following definition:
function foo(arg: number[] | number) {
if (Array.isArray(arg)) {
return [1]
}
return 0
}
I would expect typescript to figure out automatically what the return type is. Because of the type guard isArray()
it knows if arg is an Array and can show the return type as number[]
. However, using foo(...)
shows its return value as number[] | 0
even when passing an array.
foo([]).push() // error because push doesnt exist on type 0
Is this a design limitation, a bug, just not implemented yet, or some other issue?
Upvotes: 3
Views: 156
Reputation: 3485
An alternative are Generics:
function foo<T extends number[] | number>(arg:T):T {
if (Array.isArray(arg)) { return [1] as T }
return 0 as T
}
foo([] as number[]).push(10) // works
foo(42).push(10) // error as expected
The consumer of foo
gets perfect types. Inside we use a type assertion, as the return
ed values are not sound. The shape of T
is specified by the caller, so we cannot know, what exactly it is.
Imagine, the club of number 42
fetishists owns foo
- all other numbers are mehh... :)
function foo<T extends number[] | number>(arg:T):T {
if (Array.isArray(arg)) { return [1]}
return 1
}
foo(42); // T instantiated as 42
clearly wouldn't be tolerated.
While the example is contrived - the club sounds neat though -, you probably get the point, why first example needs to be type asserted.
A more realistic, sound sample:function ensureDefined<T extends number[] | number>(initializer: () => T, arg?: T): T {
if (arg === undefined || Array.isArray(arg) && arg.length === 0) {
return initializer()
} else return arg
}
ensureDefined(() => [42], [] as number[]).push(10) // works
ensureDefined(()=> 42, undefined) // error as expected
Upvotes: 0
Reputation: 156
I think what you're looking for is overloads.
Using overloading as followed would solve your problem:
function foo(arg1: number[]): number[];
function foo(arg1: number): 0;
function foo(arg1: any){
if(Array.isArray(arg1)){
return [0]
}
return 0
}
Upvotes: 0
Reputation: 1074949
I can't point to something definitively saying it's a design limitation, but I've seen experts like jcalz and Titian Cernicova-Dragomir citing various places where type inference is limited sometimes not because it couldn't do what we want, but because it would be too costly to do it (in terms of runtime cost or code complexity in the compiler). I suspect this fits into that category.
You probably know this, but for your specific example, you can use overloads to get the result you want:
function foo(arg: number[]): number[];
function foo(arg: number): number;
function foo(arg: number[] | number) {
if (Array.isArray(arg)) {
return arg.map(v => v * 2);
}
return arg * 2;
}
foo([]).push(10);
Upvotes: 5