pekala
pekala

Reputation: 13

Narrow down a union produced with template literal type based on passed values

I have a union type which is a result of a template literal type containing two other string union types. It contains all possible permutations of the values as expected. The template literal type is constructed based on generic types on a function, which takes a tuple. Is it possible to narrow the union based on the values passed to that function, so that only the combinations determined by the tuple are present? Hard to explain, so here's a simple code snippet:

function get<T extends string, K extends string>(vals: [T, K][]) {
  type FullKey = `${T}${K}`
  const answer: FullKey[] = []

  for(const v of vals) {
    const fullKey: FullKey = `${v[0]}${v[1]}`
    answer.push(fullKey)
  }

  return answer
}

const answer = get([['1', 'a'], ['2', 'b']])
//     ^?
// How to make this ('1a' | '2b')[] instead?

Playground link

I looked at Distributive Conditional Types, that seem like maybe could be the solution here, but I don't know how to apply them here.

Upvotes: 1

Views: 190

Answers (1)

tenshi
tenshi

Reputation: 26344

Instead of trying to mingle our complex types with the implementation, let's move all of that outside into the return type:

// actual type magic happens outside of the implementation
function get<A extends [T, K][], T extends string, K extends string>(vals: [...A]): { [K in keyof A]: `${A[K][0]}${A[K][1]}` }[number][];
function get(vals: string[][]) {
  const answer: string[] = [] // now just string[]

  for(const v of vals) {
    const fullKey = `${v[0]}${v[1]}` // who CARES that this is just a plain string
    answer.push(fullKey)
  }

  return answer // string[]
}

We use another type parameter A, and infer vals as a tuple. Then in the return type we can "map" over the tuple to create the desired result.

Playground

Upvotes: 1

Related Questions