Repository: TypeScript
Issue: Valid filter predicates rejected after 5.2.2 upgrade #53489
Labels: Needs Investigation

### 🔎 Search Terms

 Add fallback logic for generating signatures for unions of array members #53489 

### 🕗 Version & Regression Information

- This changed between versions 5.1.6 and 5.2.2


### ⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.2.2#code/CYUwxgNghgTiAEYD2A7AzgF3rGaUC55MYBLFAcwG0BdeAH3hQFcBbAIxBhoG4AoX0JFgIAZkxRgMJVPBYBPAApxgJMFAwgAFADcoEJiELN2nADTwyoAB5HWHGOZxQ5tk12oBKQmyRIIIKBQ+QWg4eDEJKRl5JRAVNQ0dPQNCYjJyc0sQG0Y7M2wYGGdUjFIKGi94Hz8AoP5kdCwYAEZ4AF4C3BQAOhESCA0YTRjlVXUQDz5eAHpp+ABWbubugDZ4ThgkGEJNGT6Bzg8ZuYAVAAsSNHWrAAc4NDRpFAsrlCQsNQhoNn9u4-h4ABRKBgM6yEBueBIETwDBnBDiJ6wuQ3BD-eAAcgA3vAADwAZWuGhQwCuaQoAD5NHc4mMNDtdPpDERSulMiTsq57I5CsUWWUqJ52hT4IyDC94PjzHDLgBBGDkAD8hECckq+J48HRAPg1NGCWZSSZJQF7OsXPyThc-PSFWF8HEAGs3gB3FDSi5oeVKlUoNUm23UbjwAC+9HgOO6UeDIYxWtmOrOUDJJHIKHUTHu5jYTCwbxQCGhsLOSDQCEeaYz9wKCGQLBu6hIPwQLpIcPWILB73hMG6mgATABmAAsAE4jv9Fv3uv31oUtjtoop9eMjgmAHJIKHaTgQJBQYCydSgkBXGVXT4QP4JgEAeR3MD3B-grSL-fMGL1tINDOSzPJ5DhsY3IWByOTAZavLWpoAFAXkMAeHabQimKCCXDaFBwW4HpygqyrYH6lQwaymEMBBCE0Bi5jkFAO7FqIfh7q2mEbFs15zDq8DeqwIAoFgRYYCiCDYrqqEWg4oHmrk2GdHy5EVN4vj+IEwZGikGEZJJnLSSBVoBuUniKTUKmhnG6FvFgyYVumzawluDZFCwICDFCMKCaimJqf+JGAWR8Fmtp5E8kU0GwX5biIUKyGin+EphTpnAYuxnE6icQlXEWDlQE5gxXBiqFxoEh75X+hVhGQdYNlIzbJSlqVCZi8XkWZrzvNgDypjZ-h2ciHkYs1tV1fVfUAS1jBtVZnVQLZGBbu5wkDdq967vuh6zm+H5fvE4y-saGlYSBWTgf5smhT5B2HEhIpOq67rFrhPoEf6upNfBkVUfANF0XCDFfEgzGAaxvbagC3FOXxrm9cJOJeeJAXHTJekJe4lTVMpQSiX++maUdcOndjClVEptQxmNFntdZ03dbN8BZTlnCQ-Nnlift4WHWBeNI8RAoXRRUXXSgzr-SgSUg8Np6Q3TzmcHlBUEcVcvCKBlWNjVYucWlI3nWziUSuTk2VjNc0Nf18GizeQ0AprwmjXrE0dYb1PG31A0DgA7Cs478Ez8ohQAYv0gx++IkhPNbuK+841tErxpLwHAB6oBAcgOoLt00CKHS8KDUHR9kxJx5oZAiAzgInJF8CKtnOpbXShiw0CJzw5zUGEJHcjWx49o3cLOFenhvpqvaZc0FqnFGCAD7wFMAjgKEaF8ZwIgggg7cAOptmca9bI6sBIOIwB+1sAcHDAEcnDHJJXD3boZxG1cAvsgxt1BJ9ByHUQoOHsonBSwa8CGeoqBMDx0HO0XUOA8DtS4lBDecJt4wF3psA+R8YBv04LieaRZIEoApB4XogdODDGXN+VcfAEwgjACAG4GArgjFIRoeW2AIBoC3EmC8Ww4CSHjs5TMzwmYACIAKjx1u4AR-AEyoPgGcVggRaacCYGgPQvAmYXw6O3dBaCP5hyEpgoS2DCh4D-v8E4SkpA3CICWF0aB-iqPAf8TQtcfyYz2gBZuyNgp8mEfzVOQs3R929PhVUXdoreK7v8BgjiaTbXpC49SQUtII10q3ZGV1fG3QCQPJ6ISRTyU8EAA

### 💻 Code

Passing valid predicate types to `Array.filter` is an error in version 5.2.2.
The following code produces errors in both version 5.1.6 and 5.2.2,
but the errors are are different and specify different locations.
In 5.1.6 the error is on `filter`, in 5.2.2 it is on `myPredicates`.

```ts
declare const arrsn: string[] | number[];

declare function myPredicate(value: number, index: number, array: number[]): boolean;
declare function myPredicate(value: string, index: number, array: string[]): boolean;

const r1 = arrsn.filter(myPredicate);

// 5.1.6 error: (on filter)
// This expression is not callable.
//  Each member of the union type 
//  '{ <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S[]; 
//     (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[]; } | { ...; }' 
//   has signatures, but none of those signatures are compatible with each other.(2349)

// 5.2.2 error: (on myPredicate)
// No overload matches this call.
//   Overload 1 of 2, '(predicate: (value: string | number, index: number, array: (string | number)[]) => value is string | number, thisArg?: any): (string | number)[]', gave the following error.
//     Argument of type '{ (value: number, index: number, array: number[]): boolean; (value: string, index: number, array: string[]): boolean; }' is not assignable to parameter of type '(value: string | number, index: number, array: (string | number)[]) => value is string | number'.
//       Types of parameters 'value' and 'value' are incompatible.
//         Type 'string | number' is not assignable to type 'number'.
//           Type 'string' is not assignable to type 'number'.
//   Overload 2 of 2, '(predicate: (value: string | number, index: number, array: (string | number)[]) => unknown, thisArg?: any): (string | number)[]', gave the following error.
//     Argument of type '{ (value: number, index: number, array: number[]): boolean; (value: string, index: number, array: string[]): boolean; }' is not assignable to parameter of type '(value: string | number, index: number, array: (string | number)[]) => unknown'.
//       Types of parameters 'value' and 'value' are incompatible.
//         Type 'string | number' is not assignable to type 'number'.
//           Type 'string' is not assignable to type 'number'.(2769)

```


### 🙁 Actual behavior

Error, as shown above.

Also, the return type `(string|number)[]` is not correct - it should be `string[]|number[]`.  I understand that the overly widened return type was thought to be better than nothing,  However, since the new behavior in 5.2.2. is still buggy, perhaps the final fix could could also return the correct type. 

### 🙂 Expected behavior

Emit no error.


### Additional information about the issue

Note that using a template function does work with current 5.2.2 version:
```
declare function myPredicateTpl<T>(value: T, index: number, array: T[]): boolean;
const r2 = arrsn.filter(myPredicateTpl); // no error in 5.2.2., return type is (string|number)[]
```
However, the behavior described by passing  the overloaded `myPredicate` template function as not the same  is not the same as the behavior described by passing `myPredicateTpl` .  Only the former describes the scenario such that the same type is used for all index values and that the array has single valued element type. 

We can actually generate a working version of this using the following distributed template function:

```
type ArrayFilterFunctionType<ArrayType extends readonly unknown[]> = 
  ArrayType extends (infer ET)[] ?
    (predicate:(value: ET, index: number, array: ArrayType) => unknown, thisArg?: any) => ET[] 
    : never ;

declare interface ArrayWithWorkaroundForFilter<AT extends unknown[]> {
    filter: ArrayFilterFunctionType<AT>; 
}

const r3 = (arrsn as ArrayWithWorkaroundForFilter<typeof arrsn>).filter(myPredicate);
// accepts myPredicate and also has correct return type "string[] | number[]"
```
It also actually returns the correct type `string[] | number[]`.

Here we can see it describes a union of signature types:
```
// For human perusal
type T = ArrayFilterFunctionType<typeof arrsn>;
// Tooltip shows
// type T = 
// ((predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any) => string[]) 
// | ((predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any) => number[])
```

While currently TypeScript has the unfortunate limitation of only being able to return `string[] | number[]`, 
it may at some point be improved to overcome that limitation and return the accurate type.  For that reason 
it is better to encourage habitual use of a predicate description which actually describes the behavior of the predicate. 

I guess putting a distributed template type like `ArrayFilterFunctionType` in a TypeScript lib file would be probably be too computationally expensive.  However, if the same thing could be achieved with **oneof** -like notation and implementation,  then it could become cheap enough. 

Finally, note that adding a catch all function with return type `never` to the overaloads will get rid of the error:

```
declare function myPredicate(value: number, index: number, array: number[]): boolean;
declare function myPredicate(value: string, index: number, array: string[]): boolean;
declare function myPredicate(value: any, index: number, array: any[]): never;
const r1 = arrsn.filter(myPredicate); // no error in 5.2.2, return type (string|number)[]
```
However, it returns `(string|number)[]`, not the correct return type `string[] | number[]`.