Home > Net >  TypeScript: What is the context of P in `T extends infer P`? (unclear where `P` come from)
TypeScript: What is the context of P in `T extends infer P`? (unclear where `P` come from)

Time:09-06

I came across this TypeScript snippet yesterday:

type KeysOfUnion<T> = T extends infer P ? keyof P : never
//                    ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ 

It makes me confused: What is being inferred here? I've created a mental model that using infer keyword is similar to destructuring in JS – of course this parallel is far from perfect, but it did helped me a lot.

But I can't see any context here: T extends infer P ? What am I infering? What is the relation between P and T?

After testing the snippet in TS playground, I would guess that it have something to do with separating the types that have a constructor from those that don't have one.

type test1 = KeysOfUnion<{ name: "Bill"}> // name
type test2 = KeysOfUnion<"Orange"> // number | typeof Symbol.iterator | "toString" | "charAt" etc.
type test3 = KeysOfUnion<1> // "toString" | "valueOf" | "toFixed" etc.
type test4 = KeysOfUnion<true> // "valueOf"
type test5 =  KeysOfUnion<Object> // keyof Object

type test6  = KeysOfUnion<object> // never
type test7 = KeysOfUnion<{}> // never

The object and {} behavior is a little confusing, but I guess it is explained here: Difference between 'object' ,{} and Object in TypeScript

type test8 = KeysOfUnion<undefined> // never
type test9 = KeysOfUnion<null> // never
type test10 = KeysOfUnion<never> // never
type test11 = KeysOfUnion<void> // never

But the test results are probably rather related to keyof P – still have no clue why T extends infer P is used here.

Can anyone explain?

CodePudding user response:

The details of the infer keyword are well explained in posts like this one.

But infer does only play a minor role in this generic type. It is only used here to use the effects of distributive conditional types. The creator of this type wanted to distribute each member of a potential union in T over the right side of the conditional.


The type in your question has one important use: It generates a union of all the keys of each union member in an object union. T extends infer P does not really have a special effect here (aside from distribution) as it just copies the type T into P. So let's imagine we would simplify this type to just use keyof T directly.

type KeysOfUnion<T> = T extends infer P ? keyof P : never
type KeysOfUnionWithoutInfer<T> = keyof T

They might look similar but the first one distributes a union while the second one does not.

Let's call them with an object union.

type T0 = KeysOfUnion<{ a: string } | { b: string}>
//   ^? type T0 = "a" | "b"

type T1 = KeysOfUnionWithoutInfer<{ a: string } | { b: string}>
//   ^? type T1 = never

Only the first one gives the correct key union.


As I said, the infer statement is not really needed here. For distribution, a simple T extends T would work too.

type KeysOfUnionSimpler<T> = T extends T ? keyof T : never

type T3 = KeysOfUnionSimpler<{ a: string } | { b: string}>
//   ^? type T3 = "a" | "b"

Side note: The reason you are seeing never as a result when you pass object, {}, undefined, null, never or void is just because using keyof on these types produces never.


Playground

  • Related