infer
In this chapter, we explore how we can extract parts of compound types via the infer
keyword.
It helps if you are loosely familiar with conditional types.
infer
: extracting types inside the extends
clause of a conditional typeinfer
is used inside the constraint part of a conditional type:
type _ = Type extends Constraint ? <then> : <else>;
A constraint can be a type pattern – e.g.:
T[]
Promise<T>
(arg: T) => R
At any location where we can mention an existing type variable (such as T
or R
in the previous examples), we can also introduce a new type variable X
via infer X
. Let’s look at an example: The following generic type ElemType<Arr>
extracts the element type of an Array type:
type ElemType<Arr> = Arr extends Array<infer Elem> ? Elem : never;
type _ = Assert<Equal<
ElemType<Array<string>>, string
>>;
infer
has a lot in common with destructuring in JavaScript.
Record
The utility type Record
lets us implement the keyof
operator ourselves:
const Color = {
red: 0,
green: 1,
blue: 2,
} as const;
type KeyOf<T> = T extends Record<infer K, any> ? K : never;
type _1 = Assert<Equal<
KeyOf<typeof Color>,
"red" | "green" | "blue"
>>;
We can also implement a utility type PropValues
that extracts property values:
type PropValues<T> = T extends Record<any, infer V> ? V : never;
type _2 = Assert<Equal<
PropValues<typeof Color>,
0 | 1 | 2
>>;
infer
via extends
In some cases, we need to help infer
by using extends
to constrain what can be inferred – e.g. (based on an idea by Heribert Schütz):
type EnumFromTuple<T extends ReadonlyArray<string>> =
T extends [
infer First extends string, // (A)
...infer Rest extends ReadonlyArray<string> // (B)
]
? Record<First, First> & EnumFromTuple<Rest>
: {}
;
type _ = [
Assert<Equal< // (C)
EnumFromTuple<['a', 'b']>,
Record<'a', 'a'> & Record<'b', 'b'>
>>,
Assert<Equal< // (D)
EnumFromTuple<['a', 'b']>,
{ a: 'a', b: 'b' }
>>,
];
Notes:
extends
in line A and line B.
EnumFromTuple
.
infer
TypeScript has several built-in utility types. In this section, we look at those that use infer
.
infer
/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
Both of these utility types are straightforward: We put infer
where we want to extract a type. Let’s use both types:
function add(x: number, y: number): number {
return x + y;
}
type _1 = Assert<Equal<
typeof add,
(x: number, y: number) => number
>>;
type _2 = Assert<Equal<
Parameters<typeof add>,
[x: number, y: number]
>>;
type _3 = Assert<Equal<
ReturnType<typeof add>,
number
>>;
infer
The following non-built-in utility type for classes demonstrate how construct signatures work:
type Class<T> = abstract new (...args: Array<any>) => T;
This type includes all classes whose instances have the type T
. The keyword abstract
means that it includes both abstract and concrete classes. Without that keyword, it would only include classes that are instantiable.
Construct signatures enable us to extract parts of classes:
/**
* Obtain the parameters of a constructor function type in a tuple
*/
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
/**
* Obtain the return type of a constructor function type
*/
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;
To demonstrate these utility types, let’s define a class we can apply them to:
class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
Note that the class Point
defines two things:
Point
for instances of the class.
Point
. That value has the type Class<Point>
.
const pointAsValue: Class<Point> = Point;
type _ = [
Assert<Equal<
ConstructorParameters<typeof Point>,
[x: number, y: number]
>>,
Assert<Equal<
InstanceType<typeof Point>,
Point
>>,
];
The following example is a little more complex: It converts all asynchronous methods in an object type to synchronous methods:
type Syncify<Intf> = {
[K in keyof Intf]:
Intf[K] extends (...args: infer A) => Promise<infer R> // (A)
? (...args: A) => R // (B)
: Intf[K] // (C)
};
Intf[K]
a function? (Line A)
A
but an unwrapped return type R
. (Line B)
Let’s apply Syncify
to an interface AsyncService
with Promise-based methods:
interface AsyncService {
factorize(num: number): Promise<Array<number>>;
createDigest(text: string): Promise<string>;
}
type SyncService = Syncify<AsyncService>;
type _ = Assert<Equal<
SyncService,
{
factorize: (num: number) => Array<number>,
createDigest: (text: string) => string,
}
>>;
infer
to define local type variablesWe can use infer
to define local type variables such as W
below:
type WrapTriple<T> = Promise<T> extends infer W
? [W, W, W]
: never
;
type _ = Assert<Equal<
WrapTriple<number>,
[Promise<number>, Promise<number>, Promise<number>]
>>;
For more information, see “Defining local type variables” (§33.8).