Psytronic
Psytronic

Reputation: 6113

Narrowing generic type which is keyof type without specifying generic argument

Apologies if this is duplicate somewhere, my google-fu was failing to find the correct words to narrow down the results.

I'm trying to create a helper function to re-use some functionality, and can't quite figure out if/how I can achieve it.

Given a function (or type) with generics, where one argument is a key of a specified generic type, having the function return type be determined as the return type of that specific property on the generic type, and not a union of every property type on the generic type.

interface MyObject {
  readonly foo: Date;
  readonly bar: number;
  readonly wizz: string;
  readonly bang: boolean;
}

function getHelpers<ItemType>(key: keyof ItemType) {
  return (item: ItemType) => item[key];
}

const res = getHelpers<MyObject>('bang');
// Has type of `(item: MyObject) => string | number | boolean | Date`, when I need it to have `(item: MyObject) => boolean`

I know if I add a secondary generic argument, which narrows down the key type, then it works:

function getHelpers<ItemType, KeyType extends keyof ItemType>(key: KeyType) {
  return (item: ItemType) => item[key];
}

const res = getHelpers<MyObject, 'bang'>('bang'); // Has typed of `(item: MyObject) => boolean`

But I'd rather not have to duplicate the key by specifiying the generic argument every time I call the function, if at all possible.

Upvotes: 3

Views: 649

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250106

interface MyObject {
    readonly foo: Date;
    readonly bar: number;
    readonly wizz: string;
    readonly bang: boolean;
}

Unfortunately this is not possible without function currrying. Your second option is the right approach. Ideally we would like to specify ItemType but infer KeyType. This is unfortunately not possible. We either infer all type parameters, or we have to specify them all. There is a feature proposal to support partial inference, but it has been postponed several times and is not currently on the road map.

The solution is to use two function calls, for the first one we specify ItemType and we let inference work for the second call.

function getHelpers<ItemType>() {
    return function <KeyType extends keyof ItemType>(key: KeyType) {
        return (item: ItemType) => item[key];
    }
}

const res = getHelpers<MyObject>()('bang'); // Has typed of `(item: MyObject) => boolean`

Upvotes: 5

Related Questions