Open mattiaz9 opened 1 year ago
Looks like using a generic fixes this error:
filter<P extends (value: T, index: number, array: T[]) => unknown>(
predicate: P,
thisArg?: unknown,
): P extends BooleanConstructor ? NonFalsy<T>[] : T[];
This was fixed upstream in TS I think!
Unfortunately, this still occurs with the latest TypeScript 5.3.3.
Yeah, this is weird.
Looks fixed in this playground:
@mattpocock you forgot to add import "@total-typescript/ts-reset";
. Doing so breaks that code.
Looking at the code for ts-reset
, this fixes it without breaking .filter(Boolean)
/// <reference path="utils.d.ts" />
interface Array<T> {
+ filter(predicate: (item: T, index: number, array: T[]) => boolean, thisArg?: any): TSReset.NonFalsy<T>[];
- filter(predicate: BooleanConstructor, thisArg?: any): TSReset.NonFalsy<T>[];
}
interface ReadonlyArray<T> {
+ filter(predicate: (item: T, index: number, array: T[]) => boolean, thisArg?: any): TSReset.NonFalsy<T>[];
- filter(predicate: BooleanConstructor, thisArg?: any): TSReset.NonFalsy<T>[];
}
Here is an updated example:
Hmm. Looks like the above solution breaks type predicate filters.
// With the interface
const list1 = ["0", 0, false];
const list2 = list1.filter((v): v is number => typeof v === "number");
// ^? (string | number | true)[]
console.log(list2); // 0
// Without the interface
const list1 = ["0", 0, false];
const list2 = list1.filter((v): v is number => typeof v === "number");
// ^? number[]
console.log(list2); // 0
This is actually a pretty fundamental problem with the ts-reset approach that I think will be hard to fix. https://github.com/microsoft/TypeScript/pull/53489 added special logic for calling methods on A[] | B[]
. If there are no overloads that work on A[] | B[]
then it tries (A | B)[]
instead. When you add a .filter(Boolean)
overload, there's always one valid overload and the fallback never triggers.
I think adding an overload to BooleanConstructor
itself might be a more promising approach (https://github.com/microsoft/TypeScript/issues/16655) but it also has some issues, see this thread.
interface Array<T> {
filter<S extends T>(
predicate: (value: T, index: number, array: readonly T[]) => value is S,
thisArg?: unknown,
): S[]; // duplicated from DOM types
filter<P extends (value: T, index: number, array: T[]) => unknown>(
predicate: P,
thisArg?: unknown,
): (P extends BooleanConstructor ? NonFalsy<T> : T)[];
}
I've been using this solution without problems for a while, although I haven't looked much into why it works. Interstingly, the overloads have to be in this order for it to work. The BooleanConstructor
overload must override the predefined DOM overload and duplicating the DOM overload somehow prevents that.
Playground Link
Example:
In this example both
ClientA
andClientB
has a common fieldisAdmin
but.filter()
can't handle this union.Removing
ts-reset
fixed the error. Also changing the type to(ClientA | ClientB)[]
fixed the error, but it can be inconvenient in some cases because of a forced type cast.