Why is the infer keyword needed in Typescript?

TypescriptKeywordType InferenceConditional Types

Typescript Problem Overview


Why did the Typescript folks create the infer keyword? According to the [documents][1], this is an example of how you would use it:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

I don't understand why this is needed. Why can't it just be:

type ReturnType<T> = T extends (...args: any[]) => R ? R : any;

Why doesn't this work? Why is the infer keyword necessary ? [1]: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html

Typescript Solutions


Solution 1 - Typescript

With infer, the compiler ensures that you have declared all type variables explicitly:

type MyType<T> = T extends infer R ? R : never;
type T1 = MyType<{b: string}> // T1 is { b: string; }

Here we declare a new type variable R in MyType, which gets inferred from T.
(Note, that infer is always used within the extends clause of a conditional type.)

Usage of undeclared type parameters now can result in a compile error:

type MyType2<T> = T extends R2 ? R2 : never; // error, R2 undeclared

Without infer, the compiler wouldn't know, if you wanted to introduce an additional type variable R2 that is to be inferred (see first case), or if R2 is just an accidental typing error/typo. infer exists to remove this ambiguity.

More precisely the compiler checks, if T is assignable to R , when infer is omitted:

type R = { a: number }
type MyType3<T> = T extends R ? R : never; // compare T with type R
type T2 = MyType2<{b: string}> // T2 is never

Note, that infer R shadows type references of an equally-named type declaration R:

type R = { a: number }
type MyType<T> = T extends infer R ? R : never;
type T1 = MyType<{b: string}> // { b: string; }

Playground

Solution 2 - Typescript

Consider the following code:

interface Example {
    foo: string
}

type GenericExample<T> = T extends Examlep ? 'foo' : 'bar';

This code should result in a compilation error, because Examlep is spelled incorrectly; there is no type named Examlep, and obviously the programmer meant to write Example here.

Now imagine the infer keyword is not needed in an extends clause of a conditional type. Then the above code would not give a compilation error; it would see that there is no type named Examlep, infer what type it is, and then (since Examlep has no constraints) observe that T does indeed extend Examlep for the inferred type.

In that case, GenericExample<T> would always be 'foo' regardless of what T is, and there would be no compilation error to inform the programmer about the mistake. This would be the wrong thing for the compiler to do, almost all of the time.

Solution 3 - Typescript

The infer keyword allows you to deduce a type from another type within a conditional type. Here’s an example:

type UnpackArrayType<T> = T extends (infer R)[] ? R: T;
type t1 = UnpackArrayType<number[]>; // t1 is number

UnpackArrayType is a conditional type. It is read as “If T is a sub-type of (infer R)[] , return R. Otherwise, return T”.

For type alias t1, the condition in UnpackArrayType is true because number[] matches with (infer R)[]. As the result of the infer process, the type variable R is inferred to be number type, and returned from the true branch. Infer is there to tell compiler that a new type variable R is declared within the scope of UnpackArrayType.

type t2 = UnpackArrayType<string>; //t2 is string

For t2 , the condition in UnpackArrayType is false as the string type does not match with(infer R)[] , so it is returned as string. For more information, look at this article. https://javascript.plainenglish.io/typescript-infer-keyword-explained-76f4a7208cb0?sk=082cf733b7fc66228c1373ba63d83187

Solution 4 - Typescript

I'm thinking of it like this:

  1. infer X replaces any.

To use the above example,

type UnpackArrayType<T> = T extends any[] ? T[number]: T;

->

type UnpackArrayType<T> = T extends (infer R)[] ? R: T;

2. X is declared as a new type and captured at the same time. 3. X can now be used in the true/false part of the conditional.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionCodyBugsteinView Question on Stackoverflow
Solution 1 - Typescriptford04View Answer on Stackoverflow
Solution 2 - Typescriptkaya3View Answer on Stackoverflow
Solution 3 - TypescriptpeanutView Answer on Stackoverflow
Solution 4 - Typescriptuser15717674View Answer on Stackoverflow