The compiler is technically correct to warn you inside the implementation of update(). The generic type parameter T can be "name" or "age", but it can also be the union type "name" | "age".
It is possible (though not likely) for someone to call update() with a value that doesn't match with key, because of this. For example:
update(unknownPerson, Math.random() < 2 ? "name" : "age", 100); // no error
// function update<"name" | "age">(
// orig: Person, key: "name" | "age", value: string | number): Person
So the compiler does not assume that checking key has any implication on the type of value. If key === "name", the compiler can narrow the type of key, but it does not narrow the type parameter T. And hence the error.
This is a known issue in TypeScript, and there are a few open feature requests intended to deal with it. See microsoft/TypeScript#33014 and microsoft/TypeScript#27808 to read more. For now though, this is just how it is.
If you need to write generic functions this way, then you might have to use type assertions to suppress the error.
But in your case, you don't really need generics here. Instead, you can refactor the function signature so that the key and value function parameters are represented as elements of a rest tuple whose type is a discriminated union. Like this:
type KeyValueTuple<T> =
keyof T extends infer K ? K extends keyof T ? [key: K, value: T[K]] : never : never;
function update(orig: Person, ...[key, value]: KeyValueTuple<Person>): Person {
if (key === "name") {
return {
...orig,
name: value
}
} else if (key === "age") {
return {
...orig,
age: value
}
} else {
return orig
}
}
The type function KeyValueTuple<T> takes a type T and produces a union of tuples whose first element is the key type (generally a string literal type) and whose second element is the corresponding value type. You can see how this behaves with Person:
type PersonKeyValueTuple = KeyValueTuple<Person>;
// type PersonKeyValueTuple = [key: "name", value: string] | [key: "age", value: number]
And then after the orig argument, we package key and value into a rest argument of type ...[key, value]: KeyValueTuple<Person>. Note that inside the function implementation key and value are seemingly distinct variables, but the compiler treats them as destructured discriminated unions so checking key does have the required implication for the type of value.
And so the above compiles with no error.
Playground link to code