danvk / effective-typescript

Effective TypeScript 2nd Edition: 83 Specific Ways to Improve Your TypeScript
https://effectivetypescript.com
Other
1.56k stars 226 forks source link

The display of types #19

Open utterances-bot opened 2 years ago

utterances-bot commented 2 years ago

The display of types

Effective TypeScript: The display of types

https://effectivetypescript.com/2022/02/25/gentips-4-display/

jfet97 commented 2 years ago

type Resolve<T> = T extends Function ? T : {[K in keyof T]: T[K]} seems to be able to preserve types like number or "42":

type n = Resolve<number> // number
type s42 = Resolve<"42"> // "42"

How is that even possible? Ahah I mean {[K in keyof "42"]: "42"[K]} is definitely not the same as "42".

danvk commented 2 years ago

It is surprising, isn't it? One clue is that keyof gives the methods defined on the wrapper types for primitives:

type K = keyof number;
//   ^? type K = "toString" | "toFixed" | "toExponential" | ...

But still, I think this must be special-cased in the TypeScript compiler. Especially in the case of a literal type, the result is much more specific than you'd expect. Resolve is magical indeed!

jfet97 commented 2 years ago

I think I found something: https://github.com/microsoft/TypeScript/pull/12447 {[K in keyof T]: T[K]}is what we now call a homomorphic (previously isomorphic) mapped type, and ahejlsberg said that "when a primitive type is substituted for T in an isomorphic mapped type, we simply produce that primitive type".

danvk commented 2 years ago

So there is a rationale behind this. Nice find @jfet97! It's news to me that "distributing over unions" is a concept that predates conditional types. This behavior was never mentioned in the release notes at the time (2.1 and 2.2).