Is it possible to have TypeScript constrain the 2nd index using the first argument?
For example:
type Data = {
FOO: number
BAR: string
}
type DataKeys = keyof Data
type SpecialArray<T extends DataKeys = DataKeys> = [T, Data[T]]
// I want both of these array to fail because the 2nd argument's type
// is determined by the first argument
const arr1: SpecialArray = ['FOO', 'wrong'] // 2nd argument should be `number`
const arr2: SpecialArray = ['BAR', 666] // 2nd argument should be `string`
// This works well with functions
const specialFunction = <T extends DataKeys = DataKeys>(key: T, value: Data[T]) => {
return
}
specialFunction('FOO', 'wrong')
specialFunction('BAR', 666)
>Solution :
Yes and no.
In this case:
type SpecialArray<T extends DataKeys = DataKeys> = [T, Data[T]]
const arr1: SpecialArray = ['FOO', 'wrong'] // no error
You omitted the generic parameter, so the default is used. And since the default type DataKeys is all keys then the resulting value type is number | string.
However, all generic parameters must have a type assigned to them, so it doesn’t work if you only remove the default:
type SpecialArray<T extends DataKeys> = [T, Data[T]]
const arr1: SpecialArray = ['FOO', 'wrong'] // error
// Generic type 'SpecialArray' requires 1 type argument(s).(2314)
It does work if you specify the generic parameter:
// errors as expected
const arr1: SpecialArray<'FOO'> = ['FOO', 'wrong'] // 2nd argument should be `number`
const arr2: SpecialArray<'BAR'> = ['BAR', 666] // 2nd argument should be `string`
But that’s not exactly pretty.
Sadly, you just can’t infer a generic parameter from a simple assignment in typescript.
The typical workaround for this is to use a function to make the objects for you. Since, as you note functions handle this better.
function makeSpecialArray<
T extends DataKeys
>(key: T, value: Data[T]): SpecialArray<T> {
return [key, value]
}
// these are type errors, and the objects they create are SpecialArray<SomeKey>
const arr1 = makeSpecialArray('FOO', 'wrong') // 2nd argument should be `number`
const arr2 = makeSpecialArray('BAR', 666) // 2nd argument should be `string`