Search code examples
typescriptmapped-types

Can the type of a type parameter be inferred from the entries of an Array?


In the following example, is it possible for the type of P to be inferred from the entries in the props array, rather than having to define it twice?

type A = {
  one: boolean
  two: number
  three: string[]
  four: string
}


type Definition<T, P extends (keyof T)[]> = {
  props: P
}


const def: Definition<A, ['one', 'three', 'four']> = {
  props: ['one', 'three', 'four']
}

playground


Note - the above example is massively simplified for the purpose of this question. The type of P is also needed to calculate additional parts of a mapped type


Solution

  • I'm assuming you need to manually specify the type parameter A. One thing that I do in those cases is have a kind of “factory function pair” like in the following. The idea is that the outer function lets you specify the type parameter you want, and the type parameter from the inner function is inferred:

    type A = {
      one: boolean
      two: number
      three: string[]
      four: string
    }
    
    type Definition<T, P extends (keyof T)[]> = {
      props: P
    }
    
    function makeDef<T>() {
      return function<const P extends (keyof T)[]>(def: Definition<T, P>) {
        return def
      }
    }
    
    const def1 = makeDef<A>()({
      props: ['one', 'three', 'four']
    })
    

    Here, def1 has type Definition<A, ["one", "three", "four"]>. Sure, the syntax gets a bit loaded, but it works.

    Also note that specifying const before the definition of the type parameter P on the inner function allows the inference to give as type exactly ["one", "three", "four"], whereas without the const, it would be ("one" | "three" | "four")[].