Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

Why can mapped types {[P in keyof T]} return primitives?

Here’s a simple mapped type that uses keyof:

type Identity<T> = {
  [P in keyof T]: T[P];
};

Why does Identity<number> return the primitive number type, rather than an object type? Is this special case for primitive types documented somewhere?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

This is the intended behavior of homomorphic mapped types, as implemented in microsoft/TypeScript#12447 (this feature was originally called "isomorphic", but has since changed. Also see What does "homomorphic mapped type" mean?. And note that this term isn’t really present in the new version of the TS Handbook, although it is mentioned in the mapped types section in an older version).

According to that pull request:

when a primitive type is substituted for T in [a homomorphic] mapped type, we simply produce that primitive type. For example, when { [P in keyof T]: X } is instantiated with A | undefined for T, we produce { [P in keyof A]: X } | undefined.

This behavior seems to primarily have been meant to deal with null and undefined in a reasonable way, as well as a plausible base case for recursive mapped types (and before condititional types were introduced, it was the only base case). See this comment on microsoft/TypeScript#13351:

It generally isn’t meaningful to apply mapped types to primitives, but in recursive types such as your example it is going to happen since types are rarely "objects all the way down".

and also this comment on microsoft/TypeScript#21840:

We have discussed this issue before, and the conclusion here is that mapped types [have] affordances to handling primitive types by design; we believe the vast majority of uses of mapped types are on object types, or are OK with skipping primitives (e.g. [Readonly]).


The logic is: if TypeScript decides a mapped type is homomorphic, it will behave like an identity function for primitives. The exact situations where mapped types are seen to be homomorphic are hard to describe, but they’re essentially what you’re probing in your examples.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading