In order to establish a connection between the two parameters, it is important to consider that multiple keys and values could potentially be passed in. Therefore, there should be an explicit indication of the type of the key specified in the 'property' parameter rather than assuming that the 'value' should have the same type.
To create this connection, an additional type parameter representing the property as a literal type is required.
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 challenge with the current implementation is the necessity of specifying the extra type parameter due to TypeScript's lack of support for partial type inference. Hopefully, future updates will address this issue as proposed here.
To overcome this limitation, a function returning another function can be utilized to fix the 'T' parameter in the initial call and allow for inference of 'K' in subsequent calls.
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
Another approach involves maintaining two type parameters in the function and using the return type as a basis for inferring '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>();
// The 'T' is inferred as Person in filter when assigned to a parameter expecting IFilter<Person>
q.addFilter(filter('age', "")) //error
q.addFilter(filter('age', 12)) //ok
// Same concept but assigning to a variable:
var f: IFiler<Person> = filter('age', 12);
var f2: IFiler<Person> = filter('age', "12");