You need to create a relation between the two parameters, after all you could potentially be passing in multiple keys, and multiple values, why would the compiler asume that value
should have the type of the key specified in the property
parameter.
To create the relation you will need an extra type parameter to represent the property as a literal type
function filter<T, K extends keyof T>(property: K, value: T[K]) {
return `${property} ${value}`
}
filter<Person, "name">("name", 123); // error
filter<Person, "age">("age", 123); // ok
The problem with the above implementation is that you have to specify the additional type parameter since typescript does not support partial type inference. (Hopefuly it will soon be possible as this issue proposes, it's slated for January 2018 but is has been pushed back several times)
To fix this inconvenience we can create a function that returns a function and fix the T
parameter in the first call and let K
be inferred in the second call
function filter<T>() {
return function <K extends keyof T>(property: K, value: T[K]) {
return `${property} ${value}`
}
}
filter<Person>()("name", 123); // error
filter<Person>()("age", 123); // ok
Or depending on what you plan to do with filter
you can leave the function with two type parameters and use the return type as the source for the inference of T
:
function filter<T, K extends keyof T>(property: K, value: T[K]) {
return `${property} ${value}` as IFiler<T>
}
type IFiler<T> = string & { _isFilterFor: T }
class Query<T> {
addFilter(f: IFiler<T>) {
}
}
var q = new Query<Person>();
// T will be inferred to Person in filter because we assign in to a parameter expecting IFilter<Person>
q.addFilter(filter('age', "")) //error
q.addFilter(filter('age', 12)) //ok
// Same as above but assigning to a variable:
var f: IFiler<Person> = filter('age', 12);
var f2: IFiler<Person> = filter('age', "12");