Typescript: Passing typeof fn[0] to ReturnType

Created on 4 Feb 2019  路  2Comments  路  Source: microsoft/TypeScript

This is odd behavior:

const fns = [
    (): number => 1,
    (): string => '12345',
    (): Date => new Date(),
]

type e = ReturnType<typeof fns[0]> //string | number | Date
Question

Most helpful comment

It's the expected behavior. Typescript will not infer tuple types for an array literal by default, it will infer an array. Given that fns has to be an array, the type of an item in the array has to be a union of all item types in the array literal. So the type of fns is Array< (() => number) | (() => string) | (() => Date))>. This means that as far as TS is concerned the type of any item (be it at index 0 or any other index) will (() => number) | (() => string) | (() => Date)).

ReturnType is a conditional type, its behavior distributes over unions, so when applied to the union (() => number) | (() => string) | (() => Date)) it will extract the return value of each member of the union and the result will be the union of all return values.

If you want to preserve the exact type of fns[0] you will need to convince the compiler to infer a tuple type. This can be done in 3.4 using const, although that will make the tuple readonly

const fns = [ //types as readonly [() => number, () => string, () => Date]
    (): number => 1,
    (): string => '12345',
    (): Date => new Date(),
] as const 

type e = ReturnType<typeof fns[0]> //number 

Or you can use an extra function:

function tuple<T extends Array<any>>(...t: T){
    return t
}

const fns = tuple( // typed as [() => number, () => string, () => Date]
    (): number => 1,
    (): string => '12345',
    (): Date => new Date(),
)

type e = ReturnType<typeof fns[0]> //number 

All 2 comments

It's the expected behavior. Typescript will not infer tuple types for an array literal by default, it will infer an array. Given that fns has to be an array, the type of an item in the array has to be a union of all item types in the array literal. So the type of fns is Array< (() => number) | (() => string) | (() => Date))>. This means that as far as TS is concerned the type of any item (be it at index 0 or any other index) will (() => number) | (() => string) | (() => Date)).

ReturnType is a conditional type, its behavior distributes over unions, so when applied to the union (() => number) | (() => string) | (() => Date)) it will extract the return value of each member of the union and the result will be the union of all return values.

If you want to preserve the exact type of fns[0] you will need to convince the compiler to infer a tuple type. This can be done in 3.4 using const, although that will make the tuple readonly

const fns = [ //types as readonly [() => number, () => string, () => Date]
    (): number => 1,
    (): string => '12345',
    (): Date => new Date(),
] as const 

type e = ReturnType<typeof fns[0]> //number 

Or you can use an extra function:

function tuple<T extends Array<any>>(...t: T){
    return t
}

const fns = tuple( // typed as [() => number, () => string, () => Date]
    (): number => 1,
    (): string => '12345',
    (): Date => new Date(),
)

type e = ReturnType<typeof fns[0]> //number 

Thanks for the nice responses today @dragomirtitian 馃憤 馃憤 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Antony-Jones picture Antony-Jones  路  3Comments

zhuravlikjb picture zhuravlikjb  路  3Comments

siddjain picture siddjain  路  3Comments

Roam-Cooper picture Roam-Cooper  路  3Comments

manekinekko picture manekinekko  路  3Comments