4
votes

TypeScript seems to have problems to infer union types of type guards. As an example, consider a function to combine an array of type guards with the following signature

function combine<T>(guards: ((x: any) => x is T)[]): (x: any) => x is T

and consider the following type guards with A and B having different properties

function isA(x: any): x is A
function isB(x: any): x is B

Now I would expect combine([isA, isB]) to work and have the inferred type (x: any) => x is A | B but instead I get an error saying that an argument of type((x: any) => x is A | (x: any) => x is B)[] is not assignable to parameter of type (x: any) => x is A, meaning that T is inferred as A rather than A | B.

When specifying T explicitely, i.e. combine<A|B>([isA, isB]), it works as expected. Is there a way to change the signature of combine such that this could be inferred?

1
You can always use function combine2<T, U>(guards: [((x: any) => x is T), ((x: any) => x is U)]): (x: any) => x is T | U, but I guess you want to accept an array with various amount of items?Caramiriel
Exactly, I am looking for a solution which works with any (positive) number of array elements.ipsec

1 Answers

5
votes

You can use a type parameter to denote the whole function instead of just the guarded type. This allows the compiler to infer a union of guard functions. We can then use a conditional type to extract the union of guarded types :

type GuardType<T> = T extends (o: any) => o is infer U ? U : never

class A { q: any }
class B { p: any }
declare function isA(x: any): x is A
declare function isB(x: any): x is B

declare function combine<T extends ((x: any) => x is any)>(guards: T[]): (x: any) => x is GuardType<T>

let isAB = combine([isA, isB]); // (x:any) => x is A|B