What makes compiler return different results for these two mapped types:
type NonNullableObj1<O> = {[Key in keyof O] : O[Key] extends null ? never : O[Key]}
type NotNull<T> = T extends null ? never : T;
type NonNullableObj2<T> = {[P in keyof T]: NotNull<T[P]>}
type Nullable = {prop: string | null};
type ResultOf1 = NonNullableObj1<Nullable>; // { prop: string | null }
type ResultOf2 = NonNullableObj2<Nullable>; // { prop: string }
CodePudding user response:
Both types return different results because of distributive conditional types.
In NotNull
, T
is a naked generic type on the left side of a condtional. If a union is passed for T
, each member of this union will be distributed over the conditional. So string
and null
will be evaluated seperately. Only string
does not extend null
, so the type evaluates to just string
.
In NonNullableObj1
, O[Key]
is not a naked generic type because of the indexed access. Therefore, no distribution takes place and the whole union string | null
is evaluated in the conditional at once. Since string | null
does not extend null
, it resolves in the false-branch to O[Key]
which is string | null
.