Reputation: 861
I have the following code:
type Inferred<T> = T extends (...args: (infer UnionType)[]) => any ? UnionType : never
function f(first: 'first', second: 'second', bool: boolean) {}
type T = Inferred<typeof f> // never
I expect the inferred type to be the union type 'first' | 'second' | boolean
.
I am aware that I can get it this way:
type Inferred<T> = T extends (...args: infer A) => any ? A : never
function f(first: 'first', second: 'second', bool: boolean) {}
type T = Inferred<typeof f>[number] // union type
or equivalently:
type Inferred<T> = T extends (...args: infer A) => any ? A[number] : never
function f(first: 'first', second: 'second', bool: boolean) {}
type T = Inferred<typeof f> // union type
Why does the first way not work?
Upvotes: 1
Views: 554
Reputation: 732
To answer the question, let's step back and see it from broader perspective.
TypeScript uses the same syntax (ie. []
) for both tuples and arrays. But they are sightly different concepts. I'll be using ()
(instead of standard TypeScript's []
) for tuples for brevity in the following paragraphs. Also, remember that T[] === Array<T>
in TypeScript.
Tuples are - by convention - fixed-length and heterogeneous (holding values of possibly different types). (1, "Adam", 42)
is a tuple of type (number, string, number)
.
Arrays are usually homogeneous (holding value of the same type) and of arbitrary length.
In TypeScript, the difference between the two is slightly blurred because of union types support, ie. [1, "Adam", 42]
is also a perfect Array<T>
when T = number | string
. This is not possible in languages without union types support (and would lead to finding lowest-upper bound of number | string
which is usually something like any
)
Using this knowledge in the provided example
type Inferred<T> = T extends (...args: (infer UnionType)[]) => any ? UnionType : never
we can see it as "get T
out of function with arguments' list of type Array<T>
.
We know that the type of arguments' list of:
function f(first: 'first', second: 'second', bool: boolean)
is ('first', 'second', boolean)
At this point, a following unification (:=:
) needs to be possible:
('first', 'second', boolean) :=: Array<T>
so that values of type Array<T>
can be used wherever values of type ('first', 'second', boolean)
are used. And there is actually no such T, hence the inferred never
.
It may seem like T = string | boolean
or even T = 'first' | 'second' | boolean
is a solution here but:
type Arr = Array<'first' | 'second' | boolean>
type Tuple = ['first', 'second', boolean]
const a: Arr = ['first', 'second', true]
const t: Tuple = ['first', 'second', true]
let a1: Arr = t;
let t1: Tuple = a; // Type error no. 2322
Target requires 3 element(s) but source may have fewer.(2322)
or on a type-level:
type TEA = Tuple extends Arr ? true : false // true
type AET = Arr extends Tuple ? true : false // false
It also explains why type Y = Inferred<(a: number, b: number, c: number) => void>
yields number
To wrap up, (infer UnionType)[]
in your example doesn't seem to support inferring tuple types but just arrays.
Disclaimer - I'm not familiar with TypeScript compiler internals and this is my best attempt to explain what happens here based on general knowledge and intuition.
Upvotes: 1
Reputation: 35512
It's because a function of finite arguments cannot extend a function of (potentially) infinite arguments:
// X is false
type X =
((a: string, b: number) => any) extends ((...args: (string | number)[]) => any)
? true
: false;
Your second two methods all use tuple types (['first', 'second', boolean]
), which has a known finite length, which allows them to work.
Upvotes: 0