I would like to create a function that can manipulate values of a nested Record, based off the collection name. In my example code, the value type is not being attained:
type Player = { name:string }
type Point = { x:number, y:number }
const collections = {
players: {} as Record<string, Player>,
positions: {} as Record<string, Point>,
}
type Collections = typeof collections;
function handleEntity<
Key extends keyof Collections,
Col extends Collections[Key],
Val extends Col[keyof Col],
>(name:Key, value:Val){
switch(name){
case 'positions': value.x // <-- Not valid :(
}
}
Why is Typescript not able to infer Val? Is there a workaround?
CodePudding user response:
The TypeScript compiler generally has a hard time properly narrowing down the types of generically typed variables. The relation between name
and value
is understood by the compiler when you are calling the function. But narrowing the type of value
by checking name
in the implementation is currently not possible, at least when name
and value
are generic types.
We can remove the generic types and construct a union type for the parameters.
function handleEntity(...[name, value]: {
[K in keyof Collections]: [K, Collections[K][keyof Collections[K]]]
}[keyof Collections]
){
switch(name){
case 'positions': value.x // properly narrowed :)
}
}
handleEntity("players", { name: "abc" })
handleEntity("positions", { x: 1, y: 0 })
Since the name
, value
tuple acts as a discriminated union, the compiler can properly narrow the type of value
by checking the value of name
.
If you still need handleEntity
to be generic, you can add the generic signature as an overload.
function handleEntity<
Key extends keyof Collections,
Col extends Collections[Key],
Val extends Col[keyof Col],
>(name:Key, value:Val): void
function handleEntity(...[name, value]: {
[K in keyof Collections]: [K, Collections[K][keyof Collections[K]]]
}[keyof Collections]
){
switch(name){
case 'positions': value.x // properly narrowed :)
}
}