carlosvini
carlosvini

Reputation: 1829

Is it possible for a generic tuple to match keyof with its value?

The function below is able to understand that the key "banana" cannot have the value "red":

type Fruits = {
  banana: 'yellow' | 'green'
  strawberry: 'red'
}

const fruit = <K extends keyof Fruits>(module: K, action: Fruits[K]) => true

fruit('banana', 'red') // error: not assignable to parameter of type '"yellow" | "green"

Is there any way to make something similar with tuples?

type FruitTuple<K extends keyof Fruits> = [K, Fruits[K]]

const c: FruitTuple<keyof Fruits> = ['banana', 'red'] // no error

Or even with template literals?

type FruitTemplate<K extends keyof Fruits> = `${K}.${Fruits[K]}`

const c: FruitTemplate<keyof Fruits> = 'banana.red' // no error

Or maybe objects?

type Fruit<K extends keyof Fruits> = {
  name: K
  color: Fruits[K]
}

const d: Fruit<keyof Fruits> = { name: 'banana', color: 'red' } // no error

Upvotes: 0

Views: 68

Answers (1)

jered
jered

Reputation: 11581

Yes! But as pointed out in the comment, you can't pass keyof Fruits into the generic, because that will not provide enough detail for TypeScript to infer which fruit you have specified. If you explicitly pass in the literal type into each of your examples, it works as expected.


type FruitTuple<K extends keyof Fruits> = [K, Fruits[K]]

const c: FruitTuple<'banana'> = ['banana', 'red'] // error

type FruitTemplate<K extends keyof Fruits> = `${K}.${Fruits[K]}`

const d: FruitTemplate<'banana'> = 'banana.red' // error

type Fruit<K extends keyof Fruits> = {
  name: K
  color: Fruits[K]
}

const e: Fruit<'banana'> = { name: 'banana', color: 'red' } // error

This highlights the power of using generics with functions: TypeScript can infer the actual type used when the function is invoked. However when instantiating values directly with generic tuples, template literals, objects etc. you don't have the same power to infer the type, hence the "redundancy" of typing something like const c: FruitTuple<'banana'> = ['banana', 'red']

However that doesn't mean you can't still use your custom tuple types or whatever else. But might help you out to make some helper functions to more easily instantiate the values and types you want.

function makeFruitTuple<K extends keyof Fruits>(fruit: K, color: Fruits[K]): FruitTuple<K> {
    return [fruit, color];
}

const myFruitTuple1 = makeFruitTuple('banana', 'yellow'); // ok
const myFruitTuple2 = makeFruitTuple('banana', 'red'); // error

Playground

Upvotes: 1

Related Questions