4
votes

This pattern is throwing the TypeScript error:

Argument of type '(string | number)[]' is not assignable to parameter of type 'string[] | number[]'

function foo(value: string | number) {
  return bar([value]); // <- TypeScript error
}

function bar(valueList: string[] | number[]) {
  ..does something...
}

I understand this is because TypeScript compiler will see this as an array with a mix of strings and numbers.

Is there a type-safe way to accomplish this? I can only think to cast to any[] which feels bad:

function foo(value: string | number) {
  const valueList: any[] = [value];
  return bar(valueList);
}
3
Did you notice the difference between string and string[] ?Antti

3 Answers

4
votes

One way to do this is declaration merging; define two separate interfaces for your function (see function types), then a single implementation of it:

interface Foo {
    (value: string): void;
}

interface Foo {
    (value: number): void;
}

const foo: Foo = function (value) {
  return bar([value]);
}

This will keep the two types separate, as only one of those interfaces can be called through at any given time.

2
votes

This is inspired by jonrsharpe's answer but instead of using a separate interface, it uses function overloading.

Function overloads allow us to specify multiple signatures for a single implementation: https://www.typescriptlang.org/docs/handbook/functions.html#overloads

function foo(value: string);
function foo(value: number);
function foo(value: any) {
    return bar([value]);
}

function bar(valueList: number[] | string[]) {
}
1
votes

The other answer is good (I've upvoted it), but I would personally go with

return bar([value] as string[] | number[]);

especially, if I am short on time. This does not mean that it's wrong or hackish, you are just hinting to silly (in this case) Typescript a bit.

P.S. As @paleo mentioned, this is not completely type-safe.