Repository: TypeScript
Issue:  Union of user-defined type guards spawns incorrect type
Labels: Help Wanted,Possible Improvement

### 🔎 Search Terms

- Type predicate
- Type guard
- Type guard union
- TS 2677
- A type predicate's type must be assignable to its parameter's type

### 🕗 Version & Regression Information

- This is the behavior in every version I tried, and I reviewed the FAQ for entries about 
- Tested on (next, 5.3.2, 4.9.5, 3.9.7)

### ⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.3.2#code/PTAEFVQWwVwZwC6gKYA8HIHYBNQBUAaFAGzmVAHdyBzZJPAZQCYB2ANjZQCcuB7L0ACIAgqAQBPAA7lJXZNgCWAYwCGGAORwxU8rESgARuRVw4C6phUHi5BL1AKEWySq4qodZF03bpAOkEAWAAoCWkISy5xPB0AcRhXbAAeQggUdCxsLTxQAF58AD480AAKV2oALnwASjyi8octcABuEJCQUAAZOh9sZFViOVA4Xg9QagSubABaMws1GDlcADMYTCUEBV5MX2Q4EL6lYldbHVB4xIBGYpLUKuuAH1AmUCeAZlrcotRG0EvW4KHY5DMLkC5TF75W73V7PWEfOqgH4KLRMAFAk67c6TbBvG53OHvWEAFk+31+xIB7TAMWkuBMoAA2oJwbgKI4ABa7OAVQRER6E0BvfkAXVADLQ0g28hCoPwewQrOu+SV6QwOCakWicRxSQUmGWXnwRH1hoE4CKAH4mSycZROdzeURUuAxVVBAA5XhIVmCAEdWnycVaZms+0ILmgnl8v6wl7vflxsUS1BSjDYWVnPAK8CYLaYS6Q7FXWGsl5odVZCKubXSVl6g1G1Kmo0W0DW0N29kRx0xl1uoRen04v3U-A6ekh22JcORnTRogqTDiIjx0DEkVEEagAAGZdLOLeO+gaiUHL2WImM7mlgQi2QmfC2cQufzTDewmXxX3T1ZeIrmSajWgb1kuK4OI25pWqANpht2c7SAu4rLkQrqgO6Q7FlMo7BB0ABCMBIBGKKgNs5AkaCk6Dt6WHYDGFAcsoXIeEuWh7jia5-se2C8HsmDqEgUCnghNB2jeCxyH4UmPuQz4IK+2zvt+HEHok-4ZBq1ZRCBuotgIzaQRA0GdjO8G9s6qEDp6NG+lSuFgN0CA+PqCB8NgMBKMYoCrOsmzbLOwzmLe96kcsoBrPmQZXlM+yAv0wLkEo2z6GRADyyxVD+tFvOi8WYklmD6CoVSCjlbT2aAADqjE2KAHK8AAbl4+rUGI57eWsGz5qAAzEKA4i8DAPVLsMyAPhVO4FSlmDIOlVTQqVZJIr8a7EjuY4MUxo3IFAWgqFiDVbMcmyYK1jCsBwBBjgYhGUOQiAKMQfWqDskR8BQ3LBukaYysEChhSUaXLGU1S1AA3qAIQwTBHQ7iox4kX0yz6o4yDEOIoVYjuTDHuecghAAviQZCgGDUPQ7D8O-EjKMYOjmNyjuR51V440E+VHSVYlI1yK5CjIE1bUkYg-BBoza0QcMHlcqoZAHHlQxTUgBhVGBAL-aUQMlAYoOk+TMNgDuBi-NjJLrcE7PBEAA

### 💻 Code

```ts
// U must extend T, else we get TS2766 error "A type predicate's type must be assignable to its parameter's type."
type UnaryTypeGuard<T, U extends T = T> = (arg: T) => arg is U;

// Let's decalre some guard-signatured function types
declare type Guard1 = (x: 1 | 2 | 3) => x is 1;
declare type Guard2 = (x: 1 | 2 | 3) => x is 2;
declare type Guard3 = (x: 2 | 3 | 4) => x is 4;

// Typed as ["Guard with types:", 1 | 2 | 3, 1] as expected
type TestGuard1 = Guard1 extends UnaryTypeGuard<infer T, infer U> ? ["Guard with types:", T, U] : "Not Guard";
// Typed as ["Guard with types:", 1 | 2 | 3, 1 | 2] as expected
type TestUnion12 = Guard1 | Guard2 extends UnaryTypeGuard<infer T, infer U> ? ["Guard with types:", T, U] : "Not Guard";
// Typed as ["Guard with types:", any, 2 | 4], so `Guard2 | Guard3` matches type guard signature
type TestUnion23Any = Guard2 | Guard3 extends UnaryTypeGuard<any, infer U> ?  ["Guard with types:", any, U] : "Not Guard";
// But this one is typed as "Not Guard", which means `Guard2 | Guard3` doesn't match type guard signature...
type TestUnion23 = Guard2 | Guard3 extends UnaryTypeGuard<infer T, infer U> ? ["Guard with types:", T, U] : "Not Guard";

// Let's introduce a function with signature of unioned guards
declare const oneOf: Guard2 | Guard3;
declare const a:  2 | 3;

// While hovering the function call you can see
// `const oneOf: (x: 2 | 3) => x is 2 | 4`
// which seems a type violating TS2766,
// but we still can narrow types as expected
if (oneOf(a)) { 
    // `a` is definitely of type `2` here
} else {
    // `a` is definitely of type `3` here
}

// We can retrieve this stored type `4` in such case
declare const b: any;
if (oneOf(b)) {
    // `b is `2 | 4`
}
```


### 🙁 Actual behavior

A union type of user-defined guards' types produces an incorrect guard signature which leads to inconsistent behaviour. The resulting type doesn't extends the type guard sinature in general, but a value of such type acts as a type guard.

```ts
// Typed as "Not Guard"
type TestUnion23 = Guard2 | Guard3 extends UnaryTypeGuard<infer T, infer U> ? ["Guard with types:", T, U] : "Not Guard";

// typed as `const oneOf: (x: 2 | 3) => x is 2 | 4` which violates TS2677
decalre const oneOf: Guard2 | Guard3;
```


### 🙂 Expected behavior

A union type of user-defined guards' types should produce a correct type guard signature.

A possible way to achieve this is to forcibly intersect unioned predicates' types with intersected arguments' types to compute a resulting predicate's type. Then:

```ts
// ["Guard with types:", 2 | 3, 2]
type TestUnion23 = Guard2 | Guard3 extends UnaryTypeGuard<infer T, infer U> ? ["Guard with types:", T, U] : "Not Guard";

// typed as `const oneOf: (x: 2 | 3) => x is 2` which doesn't violates TS2677
decalre const oneOf: Guard2 | Guard3;
```


### Additional information about the issue

This report comes from my original task of typing a generic function which combines several user-defined type guards with `OR` strategy.

The simplest solution (see below) spawns the reported behaviour.

```ts
function someGuard<TGuards extends UnaryTypeGuard<any>[]>(...guards: TGuards): TGuards[number] {
    return (x => guards.some(g => g(x))) as TGuards[number];
}
```