Skip to content

Commit 28da703

Browse files
committed
PR-50377-redo-filterBooleanOverload
1 parent 3e12250 commit 28da703

File tree

61 files changed

+1211
-539
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1211
-539
lines changed

src/compiler/checker.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14749,19 +14749,38 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1474914749
* maps primitive types and type parameters are to their apparent types.
1475014750
*/
1475114751
function getSignaturesOfType(type: Type, kind: SignatureKind): readonly Signature[] {
14752-
const result = getSignaturesOfStructuredType(getReducedApparentType(type), kind);
14753-
if (kind === SignatureKind.Call && !length(result) && type.flags & TypeFlags.Union) {
14752+
function carveoutResult(type: Type, kind: SignatureKind): readonly Signature[] | undefined {
14753+
if (kind === SignatureKind.Call && type.flags & TypeFlags.Union) {
14754+
// If the union is all different instantiations of a member of the global array type...
14755+
let memberName: __String;
14756+
if (everyType(type, t => !!t.symbol?.parent && isArrayOrTupleSymbol(t.symbol.parent) && (!memberName ? (memberName = t.symbol.escapedName, true) : memberName === t.symbol.escapedName))) {
14757+
if ((type as UnionType).arrayFallbackSignatures) {
14758+
return (type as UnionType).arrayFallbackSignatures!;
14759+
}
14760+
const arrayArg = mapType(type, t => getMappedType((isReadonlyArraySymbol(t.symbol.parent) ? globalReadonlyArrayType : globalArrayType).typeParameters![0], (t as AnonymousType).mapper!));
14761+
const arrayType = createArrayType(arrayArg, someType(type, t => isReadonlyArraySymbol(t.symbol.parent)));
14762+
return (type as UnionType).arrayFallbackSignatures = getSignaturesOfType(getTypeOfPropertyOfType(arrayType, memberName!)!, kind);
14763+
}
14764+
}
14765+
return undefined;
14766+
}
14767+
let result = carveoutResult(type, kind);
14768+
if (result) return result;
14769+
// The original logical for the non-carveout case is recorded in the block comment below, for the record.
14770+
// result = getSignaturesOfStructuredType(getReducedApparentType(type), kind);
14771+
// if (kind === SignatureKind.Call && !length(result) && type.flags & TypeFlags.Union) {
14772+
// if ((type as UnionType).arrayFallbackSignatures) {
14773+
// return (type as UnionType).arrayFallbackSignatures!;
14774+
// }
14775+
// return (type as UnionType).arrayFallbackSignatures = result;
14776+
// }
14777+
if (kind === SignatureKind.Call && type.flags & TypeFlags.Union) {
1475414778
if ((type as UnionType).arrayFallbackSignatures) {
1475514779
return (type as UnionType).arrayFallbackSignatures!;
1475614780
}
14757-
// If the union is all different instantiations of a member of the global array type...
14758-
let memberName: __String;
14759-
if (everyType(type, t => !!t.symbol?.parent && isArrayOrTupleSymbol(t.symbol.parent) && (!memberName ? (memberName = t.symbol.escapedName, true) : memberName === t.symbol.escapedName))) {
14760-
// Transform the type from `(A[] | B[])["member"]` to `(A | B)[]["member"]` (since we pretend array is covariant anyway)
14761-
const arrayArg = mapType(type, t => getMappedType((isReadonlyArraySymbol(t.symbol.parent) ? globalReadonlyArrayType : globalArrayType).typeParameters![0], (t as AnonymousType).mapper!));
14762-
const arrayType = createArrayType(arrayArg, someType(type, t => isReadonlyArraySymbol(t.symbol.parent)));
14763-
return (type as UnionType).arrayFallbackSignatures = getSignaturesOfType(getTypeOfPropertyOfType(arrayType, memberName!)!, kind);
14764-
}
14781+
}
14782+
result = getSignaturesOfStructuredType(getReducedApparentType(type), kind);
14783+
if (kind === SignatureKind.Call && type.flags & TypeFlags.Union) {
1476514784
(type as UnionType).arrayFallbackSignatures = result;
1476614785
}
1476714786
return result;

src/lib/es5.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1257,6 +1257,12 @@ interface ReadonlyArray<T> {
12571257
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
12581258
*/
12591259
filter<S extends T>(predicate: (value: T, index: number, array: readonly T[]) => value is S, thisArg?: any): S[];
1260+
/**
1261+
* Returns the non-Falsy elements of an array
1262+
* @param predicate Must be exactly "Boolean"
1263+
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
1264+
*/
1265+
filter(predicate: BooleanConstructor, thisArg?: any): (T extends false | 0 | "" | null | undefined | 0n ? never : T)[];
12601266
/**
12611267
* Returns the elements of an array that meet the condition specified in a callback function.
12621268
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
@@ -1448,6 +1454,12 @@ interface Array<T> {
14481454
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
14491455
*/
14501456
filter<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[];
1457+
/**
1458+
* Returns the non-Falsy elements of an array
1459+
* @param predicate Must be exactly "Boolean"
1460+
* @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
1461+
*/
1462+
filter(predicate: BooleanConstructor, thisArg?: any): (T extends false | 0 | "" | null | undefined | 0n ? never : T)[];
14511463
/**
14521464
* Returns the elements of an array that meet the condition specified in a callback function.
14531465
* @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.

tests/baselines/reference/arrayFilter.symbols

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ var foo = [
1616
]
1717

1818
foo.filter(x => x.name); //should accepted all possible types not only boolean!
19-
>foo.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
19+
>foo.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
2020
>foo : Symbol(foo, Decl(arrayFilter.ts, 0, 3))
21-
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
21+
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
2222
>x : Symbol(x, Decl(arrayFilter.ts, 6, 11))
2323
>x.name : Symbol(name, Decl(arrayFilter.ts, 1, 5))
2424
>x : Symbol(x, Decl(arrayFilter.ts, 6, 11))

tests/baselines/reference/arrayFilter.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ var foo = [
2323

2424
foo.filter(x => x.name); //should accepted all possible types not only boolean!
2525
>foo.filter(x => x.name) : { name: string; }[]
26-
>foo.filter : { <S extends { name: string; }>(predicate: (value: { name: string; }, index: number, array: { name: string; }[]) => value is S, thisArg?: any): S[]; (predicate: (value: { name: string; }, index: number, array: { name: string; }[]) => unknown, thisArg?: any): { name: string; }[]; }
26+
>foo.filter : { <S extends { name: string; }>(predicate: (value: { name: string; }, index: number, array: { name: string; }[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): { name: string; }[]; (predicate: (value: { name: string; }, index: number, array: { name: string; }[]) => unknown, thisArg?: any): { name: string; }[]; }
2727
>foo : { name: string; }[]
28-
>filter : { <S extends { name: string; }>(predicate: (value: { name: string; }, index: number, array: { name: string; }[]) => value is S, thisArg?: any): S[]; (predicate: (value: { name: string; }, index: number, array: { name: string; }[]) => unknown, thisArg?: any): { name: string; }[]; }
28+
>filter : { <S extends { name: string; }>(predicate: (value: { name: string; }, index: number, array: { name: string; }[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): { name: string; }[]; (predicate: (value: { name: string; }, index: number, array: { name: string; }[]) => unknown, thisArg?: any): { name: string; }[]; }
2929
>x => x.name : (x: { name: string; }) => string
3030
>x : { name: string; }
3131
>x.name : string
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [tests/cases/compiler/arrayFilterBooleanOverload#56013WithoutExternalOverload.ts] ////
2+
3+
//// [arrayFilterBooleanOverload#56013WithoutExternalOverload.ts]
4+
type NonFalsy<T> = T extends false | 0 | "" | null | undefined | 0n
5+
? never
6+
: T;
7+
8+
const id = <T,>() => (t: T) => !!t;
9+
10+
['foo', 'bar'].filter(id()); // // expect id() = (t: string) => boolean
11+
12+
['foo', 'bar', 1].filter(id()); // // expect id() = (t: string | number) => boolean
13+
14+
declare const maybe: boolean;
15+
(maybe ? ['foo', 'bar'] : [1] ).filter(id()); // expect id() = (t: string | number) => boolean
16+
17+
['foo', 'bar', undefined].filter(id()); // expect id() = (t: string | undefined) => boolean
18+
19+
20+
//// [arrayFilterBooleanOverload#56013WithoutExternalOverload.js]
21+
"use strict";
22+
const id = () => (t) => !!t;
23+
['foo', 'bar'].filter(id()); // // expect id() = (t: string) => boolean
24+
['foo', 'bar', 1].filter(id()); // // expect id() = (t: string | number) => boolean
25+
(maybe ? ['foo', 'bar'] : [1]).filter(id()); // expect id() = (t: string | number) => boolean
26+
['foo', 'bar', undefined].filter(id()); // expect id() = (t: string | undefined) => boolean
27+
28+
29+
//// [arrayFilterBooleanOverload#56013WithoutExternalOverload.d.ts]
30+
type NonFalsy<T> = T extends false | 0 | "" | null | undefined | 0n ? never : T;
31+
declare const id: <T>() => (t: T) => boolean;
32+
declare const maybe: boolean;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//// [tests/cases/compiler/arrayFilterBooleanOverload#56013WithoutExternalOverload.ts] ////
2+
3+
=== arrayFilterBooleanOverload#56013WithoutExternalOverload.ts ===
4+
type NonFalsy<T> = T extends false | 0 | "" | null | undefined | 0n
5+
>NonFalsy : Symbol(NonFalsy, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 0, 0))
6+
>T : Symbol(T, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 0, 14))
7+
>T : Symbol(T, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 0, 14))
8+
9+
? never
10+
: T;
11+
>T : Symbol(T, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 0, 14))
12+
13+
const id = <T,>() => (t: T) => !!t;
14+
>id : Symbol(id, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 4, 5))
15+
>T : Symbol(T, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 4, 12))
16+
>t : Symbol(t, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 4, 22))
17+
>T : Symbol(T, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 4, 12))
18+
>t : Symbol(t, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 4, 22))
19+
20+
['foo', 'bar'].filter(id()); // // expect id() = (t: string) => boolean
21+
>['foo', 'bar'].filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
22+
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
23+
>id : Symbol(id, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 4, 5))
24+
25+
['foo', 'bar', 1].filter(id()); // // expect id() = (t: string | number) => boolean
26+
>['foo', 'bar', 1].filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
27+
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
28+
>id : Symbol(id, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 4, 5))
29+
30+
declare const maybe: boolean;
31+
>maybe : Symbol(maybe, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 10, 13))
32+
33+
(maybe ? ['foo', 'bar'] : [1] ).filter(id()); // expect id() = (t: string | number) => boolean
34+
>(maybe ? ['foo', 'bar'] : [1] ).filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --) ... and 1 more)
35+
>maybe : Symbol(maybe, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 10, 13))
36+
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --) ... and 1 more)
37+
>id : Symbol(id, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 4, 5))
38+
39+
['foo', 'bar', undefined].filter(id()); // expect id() = (t: string | undefined) => boolean
40+
>['foo', 'bar', undefined].filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
41+
>undefined : Symbol(undefined)
42+
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
43+
>id : Symbol(id, Decl(arrayFilterBooleanOverload#56013WithoutExternalOverload.ts, 4, 5))
44+
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//// [tests/cases/compiler/arrayFilterBooleanOverload#56013WithoutExternalOverload.ts] ////
2+
3+
=== arrayFilterBooleanOverload#56013WithoutExternalOverload.ts ===
4+
type NonFalsy<T> = T extends false | 0 | "" | null | undefined | 0n
5+
>NonFalsy : NonFalsy<T>
6+
>false : false
7+
8+
? never
9+
: T;
10+
11+
const id = <T,>() => (t: T) => !!t;
12+
>id : <T>() => (t: T) => boolean
13+
><T,>() => (t: T) => !!t : <T>() => (t: T) => boolean
14+
>(t: T) => !!t : (t: T) => boolean
15+
>t : T
16+
>!!t : boolean
17+
>!t : boolean
18+
>t : T
19+
20+
['foo', 'bar'].filter(id()); // // expect id() = (t: string) => boolean
21+
>['foo', 'bar'].filter(id()) : string[]
22+
>['foo', 'bar'].filter : { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): string[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[]; }
23+
>['foo', 'bar'] : string[]
24+
>'foo' : "foo"
25+
>'bar' : "bar"
26+
>filter : { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): string[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[]; }
27+
>id() : (t: string) => boolean
28+
>id : <T>() => (t: T) => boolean
29+
30+
['foo', 'bar', 1].filter(id()); // // expect id() = (t: string | number) => boolean
31+
>['foo', 'bar', 1].filter(id()) : (string | number)[]
32+
>['foo', 'bar', 1].filter : { <S extends string | number>(predicate: (value: string | number, index: number, array: (string | number)[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): (string | number)[]; (predicate: (value: string | number, index: number, array: (string | number)[]) => unknown, thisArg?: any): (string | number)[]; }
33+
>['foo', 'bar', 1] : (string | number)[]
34+
>'foo' : "foo"
35+
>'bar' : "bar"
36+
>1 : 1
37+
>filter : { <S extends string | number>(predicate: (value: string | number, index: number, array: (string | number)[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): (string | number)[]; (predicate: (value: string | number, index: number, array: (string | number)[]) => unknown, thisArg?: any): (string | number)[]; }
38+
>id() : (t: string | number) => boolean
39+
>id : <T>() => (t: T) => boolean
40+
41+
declare const maybe: boolean;
42+
>maybe : boolean
43+
44+
(maybe ? ['foo', 'bar'] : [1] ).filter(id()); // expect id() = (t: string | number) => boolean
45+
>(maybe ? ['foo', 'bar'] : [1] ).filter(id()) : (string | number)[]
46+
>(maybe ? ['foo', 'bar'] : [1] ).filter : { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): string[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[]; } | { <S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): number[]; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): number[]; }
47+
>(maybe ? ['foo', 'bar'] : [1] ) : string[] | number[]
48+
>maybe ? ['foo', 'bar'] : [1] : string[] | number[]
49+
>maybe : boolean
50+
>['foo', 'bar'] : string[]
51+
>'foo' : "foo"
52+
>'bar' : "bar"
53+
>[1] : number[]
54+
>1 : 1
55+
>filter : { <S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): string[]; (predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[]; } | { <S extends number>(predicate: (value: number, index: number, array: number[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): number[]; (predicate: (value: number, index: number, array: number[]) => unknown, thisArg?: any): number[]; }
56+
>id() : (t: string | number) => boolean
57+
>id : <T>() => (t: T) => boolean
58+
59+
['foo', 'bar', undefined].filter(id()); // expect id() = (t: string | undefined) => boolean
60+
>['foo', 'bar', undefined].filter(id()) : (string | undefined)[]
61+
>['foo', 'bar', undefined].filter : { <S extends string | undefined>(predicate: (value: string | undefined, index: number, array: (string | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): string[]; (predicate: (value: string | undefined, index: number, array: (string | undefined)[]) => unknown, thisArg?: any): (string | undefined)[]; }
62+
>['foo', 'bar', undefined] : (string | undefined)[]
63+
>'foo' : "foo"
64+
>'bar' : "bar"
65+
>undefined : undefined
66+
>filter : { <S extends string | undefined>(predicate: (value: string | undefined, index: number, array: (string | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: BooleanConstructor, thisArg?: any): string[]; (predicate: (value: string | undefined, index: number, array: (string | undefined)[]) => unknown, thisArg?: any): (string | undefined)[]; }
67+
>id() : (t: string | undefined) => boolean
68+
>id : <T>() => (t: T) => boolean
69+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [tests/cases/compiler/arrayFilterBooleanOverload.ts] ////
2+
3+
//// [arrayFilterBooleanOverload.ts]
4+
const nullableValues = ['a', 'b', null]; // expect (string | null)[]
5+
6+
const values1 = nullableValues.filter(Boolean); // expect string[]
7+
8+
// @ts-expect-error
9+
const values2 = nullableValues.filter(new Boolean);
10+
11+
const arr = [0, 1, "", "foo", null] as const;
12+
13+
const arr2 = arr.filter(Boolean); // expect ("foo" | 1)[]
14+
15+
16+
17+
//// [arrayFilterBooleanOverload.js]
18+
"use strict";
19+
const nullableValues = ['a', 'b', null]; // expect (string | null)[]
20+
const values1 = nullableValues.filter(Boolean); // expect string[]
21+
// @ts-expect-error
22+
const values2 = nullableValues.filter(new Boolean);
23+
const arr = [0, 1, "", "foo", null];
24+
const arr2 = arr.filter(Boolean); // expect ("foo" | 1)[]
25+
26+
27+
//// [arrayFilterBooleanOverload.d.ts]
28+
declare const nullableValues: (string | null)[];
29+
declare const values1: string[];
30+
declare const values2: (string | null)[];
31+
declare const arr: readonly [0, 1, "", "foo", null];
32+
declare const arr2: (1 | "foo")[];

0 commit comments

Comments
 (0)