Skip to content

Explore adding the Boolean to filter to narrow out null/undefined in array#filter #50377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/lib/es5.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,12 @@ interface ReadonlyArray<T> {
* @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.
*/
filter<S extends T>(predicate: (value: T, index: number, array: readonly T[]) => value is S, thisArg?: any): S[];
/**
* Filters out nullish values from the array. Used when `Boolean` is passed as the argument to filter.
* @param predicate the `Boolean` constructor, which validates the truthiness of the value being mapped over.
* @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.
*/
filter<S extends T>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a hack solution. Is it to improve how ts infer the return type of a function so it can infer a type predicate?

arr.filter(x => typeof x === 'number')
// T[].filter<(x: T) => x is number>

arr.filter(x => x instanceof Q)
// T[].filter<(x: T) => x is Q>

arr.filter(x => x.kind === 1)
// ({ kind: 1, data: T } | { kind: 2, data: Q })[].filter<(x: ...) => x is { kind: 1, data: T }>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this has nothing to do with inferring type predicates from function expressions, it’s just so you can use .filter(Boolean). See #50387.

/**
* Returns the elements of an array that meet the condition specified in a callback function.
* @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.
Expand Down Expand Up @@ -1426,6 +1432,12 @@ interface Array<T> {
* @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.
*/
filter(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): T[];
/**
* Filters out nullish values from the array. Used when `Boolean` is passed as the argument to filter.
* @param predicate the `Boolean` constructor, which validates the truthiness of the value being mapped over.
* @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.
*/
filter<S extends T>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[];
/**
* Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
* @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/arrayFilter.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ var foo = [
]

foo.filter(x => x.name); //should accepted all possible types not only boolean!
>foo.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>foo.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>foo : Symbol(foo, Decl(arrayFilter.ts, 0, 3))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(arrayFilter.ts, 6, 11))
>x.name : Symbol(name, Decl(arrayFilter.ts, 1, 5))
>x : Symbol(x, Decl(arrayFilter.ts, 6, 11))
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/arrayFilter.types
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ var foo = [

foo.filter(x => x.name); //should accepted all possible types not only boolean!
>foo.filter(x => x.name) : { name: string; }[]
>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; }[]; }
>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; }[]; <S extends { name: string; }>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; }
>foo : { name: string; }[]
>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; }[]; }
>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; }[]; <S extends { name: string; }>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; }
>x => x.name : (x: { name: string; }) => string
>x : { name: string; }
>x.name : string
Expand Down
14 changes: 14 additions & 0 deletions tests/baselines/reference/arrayFilterBoolean.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//// [arrayFilterBoolean.ts]
const mixed = [undefined, "string", null]
const mixedReadonly: Readonly<typeof mixed> = [undefined, "string", null]

const shouldBeJustStringForMutableArray = mixed.filter(Boolean)

const shouldBeJustStringForReadonlyArray = mixedReadonly.filter(Boolean)

//// [arrayFilterBoolean.js]
"use strict";
var mixed = [undefined, "string", null];
var mixedReadonly = [undefined, "string", null];
var shouldBeJustStringForMutableArray = mixed.filter(Boolean);
var shouldBeJustStringForReadonlyArray = mixedReadonly.filter(Boolean);
25 changes: 25 additions & 0 deletions tests/baselines/reference/arrayFilterBoolean.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
=== tests/cases/compiler/arrayFilterBoolean.ts ===
const mixed = [undefined, "string", null]
>mixed : Symbol(mixed, Decl(arrayFilterBoolean.ts, 0, 5))
>undefined : Symbol(undefined)

const mixedReadonly: Readonly<typeof mixed> = [undefined, "string", null]
>mixedReadonly : Symbol(mixedReadonly, Decl(arrayFilterBoolean.ts, 1, 5))
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
>mixed : Symbol(mixed, Decl(arrayFilterBoolean.ts, 0, 5))
>undefined : Symbol(undefined)

const shouldBeJustStringForMutableArray = mixed.filter(Boolean)
>shouldBeJustStringForMutableArray : Symbol(shouldBeJustStringForMutableArray, Decl(arrayFilterBoolean.ts, 3, 5))
>mixed.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>mixed : Symbol(mixed, Decl(arrayFilterBoolean.ts, 0, 5))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Boolean : Symbol(Boolean, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

const shouldBeJustStringForReadonlyArray = mixedReadonly.filter(Boolean)
>shouldBeJustStringForReadonlyArray : Symbol(shouldBeJustStringForReadonlyArray, Decl(arrayFilterBoolean.ts, 5, 5))
>mixedReadonly.filter : Symbol(ReadonlyArray.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>mixedReadonly : Symbol(mixedReadonly, Decl(arrayFilterBoolean.ts, 1, 5))
>filter : Symbol(ReadonlyArray.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Boolean : Symbol(Boolean, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

32 changes: 32 additions & 0 deletions tests/baselines/reference/arrayFilterBoolean.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
=== tests/cases/compiler/arrayFilterBoolean.ts ===
const mixed = [undefined, "string", null]
>mixed : (string | null | undefined)[]
>[undefined, "string", null] : (string | null | undefined)[]
>undefined : undefined
>"string" : "string"
>null : null

const mixedReadonly: Readonly<typeof mixed> = [undefined, "string", null]
>mixedReadonly : readonly (string | null | undefined)[]
>mixed : (string | null | undefined)[]
>[undefined, "string", null] : (string | null | undefined)[]
>undefined : undefined
>"string" : "string"
>null : null

const shouldBeJustStringForMutableArray = mixed.filter(Boolean)
>shouldBeJustStringForMutableArray : (string | null | undefined)[]
>mixed.filter(Boolean) : (string | null | undefined)[]
>mixed.filter : { <S extends string | null | undefined>(predicate: (value: string | null | undefined, index: number, array: (string | null | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | null | undefined, index: number, array: (string | null | undefined)[]) => unknown, thisArg?: any): (string | null | undefined)[]; <S extends string | null | undefined>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; }
>mixed : (string | null | undefined)[]
>filter : { <S extends string | null | undefined>(predicate: (value: string | null | undefined, index: number, array: (string | null | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | null | undefined, index: number, array: (string | null | undefined)[]) => unknown, thisArg?: any): (string | null | undefined)[]; <S extends string | null | undefined>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; }
>Boolean : BooleanConstructor

const shouldBeJustStringForReadonlyArray = mixedReadonly.filter(Boolean)
>shouldBeJustStringForReadonlyArray : string[]
>mixedReadonly.filter(Boolean) : string[]
>mixedReadonly.filter : { <S extends string | null | undefined>(predicate: (value: string | null | undefined, index: number, array: readonly (string | null | undefined)[]) => value is S, thisArg?: any): S[]; <S extends string | null | undefined>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; (predicate: (value: string | null | undefined, index: number, array: readonly (string | null | undefined)[]) => unknown, thisArg?: any): (string | null | undefined)[]; }
>mixedReadonly : readonly (string | null | undefined)[]
>filter : { <S extends string | null | undefined>(predicate: (value: string | null | undefined, index: number, array: readonly (string | null | undefined)[]) => value is S, thisArg?: any): S[]; <S extends string | null | undefined>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; (predicate: (value: string | null | undefined, index: number, array: readonly (string | null | undefined)[]) => unknown, thisArg?: any): (string | null | undefined)[]; }
>Boolean : BooleanConstructor

12 changes: 6 additions & 6 deletions tests/baselines/reference/booleanFilterAnyArray.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ var ys: any[];

var ys = realanys.filter(Boolean)
>ys : Symbol(ys, Decl(booleanFilterAnyArray.ts, 16, 3), Decl(booleanFilterAnyArray.ts, 17, 3))
>realanys.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>realanys.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>realanys : Symbol(realanys, Decl(booleanFilterAnyArray.ts, 15, 11))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Boolean : Symbol(Boolean, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))

var foo = [{ name: 'x' }]
Expand All @@ -86,9 +86,9 @@ var foor: Array<{name: string}>

var foor = foo.filter(x => x.name)
>foor : Symbol(foor, Decl(booleanFilterAnyArray.ts, 20, 3), Decl(booleanFilterAnyArray.ts, 21, 3))
>foo.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>foo.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>foo : Symbol(foo, Decl(booleanFilterAnyArray.ts, 19, 3))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>x : Symbol(x, Decl(booleanFilterAnyArray.ts, 21, 22))
>x.name : Symbol(name, Decl(booleanFilterAnyArray.ts, 19, 12))
>x : Symbol(x, Decl(booleanFilterAnyArray.ts, 21, 22))
Expand All @@ -100,8 +100,8 @@ var foos: Array<boolean>

var foos = [true, true, false, null].filter((thing): thing is boolean => thing !== null)
>foos : Symbol(foos, Decl(booleanFilterAnyArray.ts, 22, 3), Decl(booleanFilterAnyArray.ts, 23, 3))
>[true, true, false, null].filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>[true, true, false, null].filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>thing : Symbol(thing, Decl(booleanFilterAnyArray.ts, 23, 45))
>thing : Symbol(thing, Decl(booleanFilterAnyArray.ts, 23, 45))
>thing : Symbol(thing, Decl(booleanFilterAnyArray.ts, 23, 45))
Expand Down
12 changes: 6 additions & 6 deletions tests/baselines/reference/booleanFilterAnyArray.types
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ var ys: any[];
var ys = realanys.filter(Boolean)
>ys : any[]
>realanys.filter(Boolean) : any[]
>realanys.filter : { <S extends any>(predicate: (value: any, index: number, array: any[]) => value is S, thisArg?: any): S[]; (predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): any[]; }
>realanys.filter : { <S extends any>(predicate: (value: any, index: number, array: any[]) => value is S, thisArg?: any): S[]; (predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): any[]; <S extends any>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; }
>realanys : any[]
>filter : { <S extends any>(predicate: (value: any, index: number, array: any[]) => value is S, thisArg?: any): S[]; (predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): any[]; }
>filter : { <S extends any>(predicate: (value: any, index: number, array: any[]) => value is S, thisArg?: any): S[]; (predicate: (value: any, index: number, array: any[]) => unknown, thisArg?: any): any[]; <S extends any>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; }
>Boolean : BooleanConstructor

var foo = [{ name: 'x' }]
Expand All @@ -64,9 +64,9 @@ var foor: Array<{name: string}>
var foor = foo.filter(x => x.name)
>foor : { name: string; }[]
>foo.filter(x => x.name) : { name: string; }[]
>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; }[]; }
>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; }[]; <S extends { name: string; }>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; }
>foo : { name: string; }[]
>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; }[]; }
>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; }[]; <S extends { name: string; }>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; }
>x => x.name : (x: { name: string; }) => string
>x : { name: string; }
>x.name : string
Expand All @@ -79,13 +79,13 @@ var foos: Array<boolean>
var foos = [true, true, false, null].filter((thing): thing is boolean => thing !== null)
>foos : boolean[]
>[true, true, false, null].filter((thing): thing is boolean => thing !== null) : boolean[]
>[true, true, false, null].filter : { <S extends boolean>(predicate: (value: boolean, index: number, array: boolean[]) => value is S, thisArg?: any): S[]; (predicate: (value: boolean, index: number, array: boolean[]) => unknown, thisArg?: any): boolean[]; }
>[true, true, false, null].filter : { <S extends boolean>(predicate: (value: boolean, index: number, array: boolean[]) => value is S, thisArg?: any): S[]; (predicate: (value: boolean, index: number, array: boolean[]) => unknown, thisArg?: any): boolean[]; <S extends boolean>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; }
>[true, true, false, null] : boolean[]
>true : true
>true : true
>false : false
>null : null
>filter : { <S extends boolean>(predicate: (value: boolean, index: number, array: boolean[]) => value is S, thisArg?: any): S[]; (predicate: (value: boolean, index: number, array: boolean[]) => unknown, thisArg?: any): boolean[]; }
>filter : { <S extends boolean>(predicate: (value: boolean, index: number, array: boolean[]) => value is S, thisArg?: any): S[]; (predicate: (value: boolean, index: number, array: boolean[]) => unknown, thisArg?: any): boolean[]; <S extends boolean>(predicate: BooleanConstructor, thisArg?: any): NonNullable<S>[]; }
>(thing): thing is boolean => thing !== null : (thing: boolean) => thing is boolean
>thing : boolean
>thing !== null : boolean
Expand Down
Loading