A function with a generic type may infer the generic types from the arguments in most cases. However, if an argument is a function where the generic type is both part of the arguments and the return value, the generic type is sometimes not inferred.
Dumbed down example with a class for storing items, and where some attributes of items are auto-generated (e.g. a database where IDs are auto-generated):
/**
* Stores items of type T
*/
class Store<T> {
/**
* Return a function that creates items from supplied partial items merged with
* attributes U auto-generated by a generator function
*/
itemCreator<U>(
generate: (item: Omit<T, keyof U>) => U
): (item: Omit<T, keyof U>) => Omit<T, keyof U> & U {
return item => ({...item, ...generate(item)});
}
}
type Person = {
id: string;
name: string;
email: string;
age?: number;
};
So, if you create a Store<Person> and supply a generator to auto-generate id, the returned creator function would only require name and email.
However, in some cases, U is not inferred:
Works:
const create = new Store<Person>()
.itemCreator(() => ({id: 'ID', extra: 42}));
// U is {id: string, extra: number}, `create` only needs to provide `name` and `email` :)
const person = create({name: 'John', email: 'john.doe@foo.com'}); // creates person with extra
Does not work:
const create = new Store<Person>()
.itemCreator(item => ({id: 'ID', extra: 42}));
// U is now unknown, meaning the `create` function must provide complete `Person` objects :(
const person = create({name: 'John', email: 'john.doe@foo.com'}); // does not compile
Works with explicit <U>:
const create = new Store<Person>()
.itemCreator<{id: string, extra: number}>((item) => ({id: 'ID', extra: 42}));
const person = create({name: 'John', email: 'john.doe@foo.com'}); // creates person with extra
Now, the reason for passing the partial item to the generator is that some auto-generated attributes might be dependent on other attributes (such as an id being generated as a hash of an email attribute):
const creator = new Store<Person>()
.itemCreator(item => ({id: hash(item.email)}))
So, my question is why inferring U fails if the argument to the generate function is provided? Does TypeScript simply use the first found instance of U or what is the reason? The generate function returns U, so if it sees {id: string} being returned, one could argue that the fact that U is also present in the Omit type of the argument to generate should be irrelevant?
Is there a way to solve this?