typescriptgenericstypescript-genericsrecordkeyof

Is there a way in Typescript to instantiate a Record<K, V> by assignment in a generic function<T> where: T extends an object-type and K = keyof T?


What I'm trying to do is this:

interface A {
    a: number
    b: number
}

function f<T extends A>() {
    const x: Partial<Record<keyof T, string>> = {a: 'generz'}
    console.log(x)
}

But, when compiling (using tsc v4.9.3) I get this error message:

error TS2322: Type '{ a: "generz"; }' is not assignable to type 'Partial<Record<keyof T, string>>'.

I don't understand why and I would like to have an explanation on this error. If T extends A then keyof T is a superset of keyof A (containing at least 'a'|'b'), so the {a: 'generz'} would be legal independently of T? Am i missing something?


I've found out, that creating a custom PartialRecord type (described here) like this:

type PartialRecord<K extends keyof any, T> = {
    [P in K]?: T
}

And then changing the type of variable x to PartialRecord<keyof T, string> , like this:

const x: PartialRecord<keyof T, string> = {a: 'generz'}

Compiles the code without complaining.


Update 2022-11-25

This would be equivalent code and it compiles too:

const x: Partial<Record<keyof T, string>> = {}
x.a = 'generz'

Although is not what i'll like to do.


Update 2022-11-29

What I'm asking is NOT "how to make it work in alternative ways", but is "why it doesn't work that way", since I expect it to work. Otherwise I'd just do {a: 'generz'} as Partial<Record<keyof T, string>> (or worse as any).

And if it wasn't clear: the code is nothing more than the minimum necessary to reproduce the error. So its purpose is not to make sense or do anything useful.


Solution

  • Nesting a Record<keyof T, string> inside a Partial leads to a level of abstraction which can not be properly understood by the compiler. Both Record and Partial are implemented as mapped types which can not be fully resolved when a yet unspecified generic type is supplied.

    While the logic behind your assignment might be sound, it requires higher level reasoning by the compiler about the implications of nesting Record<keyof T, string> inside a Partial. Yet, it seems to be inable reason about this type at all leaving it essentially opaque. No expression would identify this type as a valid assignment target leaving only a type assertion as a workaround.

    There have been multiple open issues about these higher order type problems like #43846 where Partial being wrapped around Omit lead to the same error or #28884 where an intersection of Pick<T, Exclude<keyof T, K>> & Pick<T, Extract<keyof T, K>> was not assignable to T. The compiler would fail to properly evalaute the generic types leaving them opaque, even though these operations a reasonable and mostly sound to us.