Search code examples
typescriptmapped-types

Inline evaluation different from non-inline


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 }

Playground


Solution

  • 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.