Search code examples
typescripttypescript-genericsmapped-types

Why can't I write to object property with index type inference


I have this function

function foo<T extends { [P in K]: number }, K extends keyof T>(obj: T, key: K): void {
  const x: number = obj[key]
  obj[key] = 123 //error
}

obj[key] has type number but I cannot write number to it

Type '123' is not assignable to type 'T[K]'.

How to fix it?


Solution

  • This is working as intended. Imagine you invoke foo like this:

    const obj = {a: 42} as const
    foo(obj,"a") 
    // foo<{readonly a: 42; }, "a">(obj: { readonly a: 42; }, key: "a"): void
    

    Then T[K] won't be number, but 42 (a subtype) - an assignment of 123 to a wouldn't be valid anymore. Inside foo body, we only know for sure, that T[K] is some kind of number (generic constraint of T), but not what the exact type is.

    Hence TS will emit the error Type '123' is not assignable to type 'T[K]' - the compiler cannot ensure, that 123 is the right type, as generic types are set by the caller of foo. The only type that can be checked statically is T[K].

    To write number to certain properties K, you can change the signature as follows:

    function foo<K extends PropertyKey>(obj: Record<K, number>, key: K): void {
      const x: number = obj[key]
      obj[key] = 123 
    }
    
    const obj = { foo: 1, bar: true }
    foo(obj, 'foo') // works
    

    TS Playground sample