TypeScript: For `T extends M` where `M` is a mapped type, `T[keyof T]` doesn't consider `M`

TypeScript Version: 3.4.0-dev.201xxxxx and 3.3.3333 (used to work in TS 3.2)

Search Terms: Mapped type, T[keyof T]

Code

// Takes type -> {a: string}
// returns type -> {a: string | null} 
type Nullable<T extends object> = {
  [P in keyof T]: T[P] | null;
};

// This function should only accept objects, such that all props can be assigned null.
function f<T extends Nullable<T>>(t: T, key: keyof T) {
  let x = t[key];
  x = null;
}

Expected behavior: x = null to be accepted as T’s lower type bound is Nullable<_> thus all properties have | null.

Actual behavior: This was accepted up to TS 3.2, but started erring with TS 3.3 with the error:

ERROR(11,3): : Type 'null' is not assignable to type 'T[keyof T]'.

** Some notes ** There is nothing special about Nullable, same error happens with other mapped types.

This is not my code and there is a valid question why even bother writing f as written, since TS accepts:

let x: {a: string} = {a: ''}; 
f(x, 'a');

I.e. the Nullable lower bound doesn’t seem to be checked at the call-site.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 1
  • Comments: 15 (11 by maintainers)

Most upvoted comments

Probably, yeah. @rikov isn’t this the much simpler way to write that?:

export function setValue<T, K extends keyof T>(
    obj: T, key: K, value: T[K]) {
  obj[key] = value;
}

as written, that setValue can be invoked with type params which would make that assignment unsound, eg:

export function setValue<V, T extends {[KeyT in keyof T]: V}>(
    obj: T, key: keyof T, value: V) {
  obj[key] = value;
}

setValue<number, { "ok": 6 }>({ ok: 6 }, "ok", 0); // allows setting `0` because `V` can be less specialized than T[keyof T]