inferring a union type from a functions parameters

Viewed 108

In Typescript when I check a variable the then branch infer variable type based on the condition:

type MyType = 'val1' | 'val2' | 'val3'

const variable = 'val1' as MyType

if (variable === 'val2' || variable === 'val3') {
    // typeof variable == 'val2' | 'val3'
}

The above code is chatty especially when I check for more than two values and I want to replace it with

if (hasOneOf(variable, 'val2', 'val3')) {
    // typeof variable == 'val2' | 'val3'
}

Solution with I'm partially satisfied is:

const hasOneOf = <T, U extends T>(value: T | undefined, ...values: U[]): value is U => value !== undefined && values.indexOf(value as U) >= 0

however it works only when I explicitly define types


if (hasOneOf(variable, 'val2', 'val3')) {
    // typeof variable == MyType
}

// vs

if (hasOneOf<MyType, 'val2' | 'val3'>(variable, 'val2', 'val3')) {
    // typeof variable == 'val2' | 'val3'
}

Is it possible to create function hasOneOf which infer generic types and returns union defined by parameters ('val2', 'val3' => 'val2' | 'val3') ?

1 Answers

If you're looking for a solution which accepts a variable number of values for the union, below is a variation on what's in your question. You can pass a readonly array as the second argument and the compiler will create a union from the types of its members. (I think the addition of the array brackets and the as const assertion is still terser than providing type arguments):

TS Playground link

function isInUnion <T, U extends T>(value: T, values: readonly U[]): value is U {
  return values.indexOf(value as U) >= 0;
}

let str = 'val1'; // string

if (isInUnion(str, ['val2', 'val3'] as const)) {
  str // "val2" | "val3"
}

let num = 1; // number

if (isInUnion(num, [2, 3] as const)) {
  num // 2 | 3
}

Original answer:

You can use a type predicate for this:

TS Playground link

function isInUnion <S1 extends string, S2 extends string>(
  value: string,
  str1: S1,
  str2: S2,
): value is S1 | S2 {
  return value === str1 || value === str2;
}

let str = 'val1'; // string

if (isInUnion(str, 'val2', 'val3')) {
  // in this block, str is "val2" | "val3"
}
Related