type ReplaceProperty<T, K extends keyof T, V> describes a function where the generic type parameters T, K, and V are specified in advance. The function can only be called with args of these predetermined types (i: T, k: K, v: V).
It is a generic type which describes a function that is not generic.
What you want is a generic function. A generic function can be called with arguments of differing types. The type parameters T, K, and V are different for each function call and are determined based on the arguments when the function is called.
You need to move the generics to the other side of the equals sign:
type ReplaceProperty = <T, K extends keyof T, V>(
This describes the type accurately, but you get errors on your implementation replaceProperty because Typescript sees the return type as a mismatch.
Type '<T, K extends keyof T, V>(i: T, k: K, v: V) => T & { [x: string]: V; }' is not assignable to type 'ReplaceProperty'.
Type 'T & { [x: string]: V; }' is not assignable to type '{ [P in keyof T]: K extends P ? V : T[P]; }'
Setting the dynamic property {[k]: v} creates a string index signature {[x: string]: V;}
You probably need to assert correctness with as. I am using parenthesis because I am asserting the type for the entire function, not for the return value.
const replaceProperty = (<T, K extends keyof T, V>(
i: T,
k: K,
v: V
) => ({
...i,
[k]: v,
})) as ReplaceProperty;
But I'm not sure that there's really a point to creating a type for the function rather than for it's arguments and returned value. Perhaps this inline declaration makes more sense.
const replaceProperty1 = <T, K extends keyof T, V>(
i: T,
k: K,
v: V
) => ({
...i,
[k]: v,
}) as { [P in keyof T]: K extends P ? V : T[P] }
Typescript Playground Link