0

I'm trying to use a union type of overloaded functions with generic return type and it seems that TypeScript is lost when a tuple type is passed as a generic:

type Fn1<R> = (one: string) => R;
type Fn2<R> = (one: string, two: string) => R;
type Fn3<R> = (one: string, two: string, three: string) => R;
type GenericOverloadingFn<R> = Fn1<R> | Fn2<R> | Fn3<R>;

type TupleOfPrimitives = [
  string,
  number
];

type StructWithKeys = {
  one: string;
  two: number;
}

type UnionFn = GenericOverloadingFn<TupleOfPrimitives>
  | GenericOverloadingFn<string>
  | GenericOverloadingFn<StructWithKeys>; 

type UnionGenericFn = GenericOverloadingFn<
  TupleOfPrimitives
  | string
  | StructWithKeys
>; 

const union2Fn0: UnionFn = (one: string, two: string) => "hey"; // works
const union2Fn1: UnionFn = (one: string, two: string) => ({ one: "hey", two: 1 }); // works
const union2Fn2: UnionFn = (one: string, two: string) => ["hey", 2]; // error

const union1Fn0: UnionFn = (one: string) => ["hey", 2]; // error
const union3Fn0: UnionFn = (one: string, two: string, three: string) => ["hey", 2]; // works


const unionGeneric2Fn0: UnionGenericFn = (one: string, two: string) => "hey"; // works
const unionGeneric2Fn1: UnionGenericFn = (one: string, two: string) => ({ one: "hey", two: 1 }); // works
const unionGeneric2Fn2: UnionGenericFn = (one: string, two: string) => ["hey", 2]; // error

const unionGeneric3Fn2: UnionGenericFn = (one: string, two: string, three: string) => ["hey", 2]; // works


const fn20: Fn2<TupleOfPrimitives> = (one: string, two: string) => ["hey", 2]; // works
const fn21: Fn2<string | TupleOfPrimitives> = (one: string, two: string) => "hey"; // works
const fn22: Fn2<string | TupleOfPrimitives> = (one: string, two: string) => ["hey", 2]; // works


const genericOverloading2Fn: GenericOverloadingFn<TupleOfPrimitives> =
  (one: string, two: string) => ["hey", 2]; // error

const genericOverloading3Fn: GenericOverloadingFn<TupleOfPrimitives> =
  (one: string, two: string, three: string) => ["hey", 2]; // works

Errors mostly look like this

Type '(one: string, two: string) => (string | number)[]' is not assignable to type 'UnionFn'.
  Type '(one: string, two: string) => (string | number)[]' is not assignable to type 'Fn1<TupleOfPrimitives>'.

I'm not sure if doing something wrong or is it a limitation/bug of TypeScript?

1 Answer 1

1

The issue here is TypeScript inference, which is wider then your type needs. We can do the simple check, what type has such construct:

const a = ['a', 1];
type A = typeof a; // (string | number)[]

So clearly (string | number)[] is more wide type then [string, number] and there is the issue. To fix it simply we need to apply type assertion:

 (one: string) => ['a', 1] as TupleOfPrimitives

Also take into consideration that in the field most probably you will not return const only, but there will be some other function calling, and in this situation probably you will avoid assertion. Consider example:

const g = (one: string): TupleOfPrimitives => [one, 1]; 
const f: GenericOverloadingFn<TupleOfPrimitives> = (one: string) => g(one)

Function f does not need to have assertion inside as it is calling function g which clearly defines correct type as a return type.The issue will come only when you will want to put constant value by hand, as you do in your examples.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.