diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3ac1038a4cdbe..74e20297b62f6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -34120,6 +34120,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma); } if (result) { + const returnType = calculateSignatureReturnTypeForSpecialCases(result, args); + if (returnType) { + result = cloneSignature(result); + result.resolvedReturnType = returnType; + } return result; } @@ -34235,6 +34240,70 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return result; + function calculateSignatureReturnTypeForSpecialCases(signature: Readonly, args: readonly Expression[]): Type | undefined { + if (args.length >= 1) { + // In some tsx cases "symbol" is undefined, even though it is defined in the typechecker. So add ? + if ( + signature.declaration?.symbol?.escapedName === "filter" && ( + signature.declaration?.symbol?.parent?.escapedName === "Array" + || signature.declaration?.symbol?.parent?.escapedName === "ReadonlyArray" + ) + ) { + const arg0Type = getTypeOfExpression(args[0]); + // This is safe even if a different BooleanConstructor is defined in a namespace, + // because in that case arg0Type.symbol.escapedName will appear as "__type". + if (arg0Type.symbol?.escapedName === "BooleanConstructor") { + // It is a-priori knowledge the filter returns the same type as the array type + // for a signature succeeding when BooleanConstructor is the argument type + let returnType = (signature.mapper as undefined | { targets: readonly Type[]; })?.targets[1]; + // result.declaration?.symbol.parent?.escapedName==="ReadonlyArray" + if (returnType) { + const nonFalsieArrayTypesOut: Type[] = []; + // the return type can only be an array type. + // It cant actually be a union of array types for a single signature. + // So this forEachType could be skipped, but may be used in the future with union of array types + forEachType(returnType, at => { + let elemType: Type; + if (isTupleType(at)) { + // The tuple elements are unionized, *abondoning* the tupleness becuase + // filtering could create result of varying length. + // For variable length tuples, undefined is *not* added to the union within getElementTypes. + elemType = getUnionType(getElementTypes(at)); + } + else if (isTupleLikeType(at)) { + // doesn't handle tupleLikeTypes + // just return the orginal type + nonFalsieArrayTypesOut.push(at); + return; + } + else { + elemType = getElementTypeOfArrayType(at) || anyType; // need test case for anyType + } + const nonFalsieElemTypes: Type[] = []; + nonFalsieElemTypes.push(filterType( + elemType, + t => { + const facts = getTypeFacts(t, TypeFacts.Truthy | TypeFacts.Falsy); + if (facts === TypeFacts.Falsy) { + return false; + } + else { + return true; + } + }, + )); + // output arrays are not not readonly + const atout = createArrayType(getUnionType(nonFalsieElemTypes)); + nonFalsieArrayTypesOut.push(atout); + }); + returnType = getUnionType(nonFalsieArrayTypesOut); + return returnType; + } + } + } + } + } + function addImplementationSuccessElaboration(failed: Signature, diagnostic: Diagnostic) { const oldCandidatesForArgumentError = candidatesForArgumentError; const oldCandidateForArgumentArityError = candidateForArgumentArityError; diff --git a/tests/baselines/reference/arrayFilterBooleanOverload.js b/tests/baselines/reference/arrayFilterBooleanOverload.js new file mode 100644 index 0000000000000..ace5769bd8c9b --- /dev/null +++ b/tests/baselines/reference/arrayFilterBooleanOverload.js @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/arrayFilterBooleanOverload.ts] //// + +//// [arrayFilterBooleanOverload.ts] +const nullableValues = ['a', 'b', null]; // expect (string | null)[] + +const values1 = nullableValues.filter(Boolean); // expect string[] + +// @ts-expect-error +const values2 = nullableValues.filter(new Boolean); + +const arr = [0, 1, "", "foo", null] as const; + +const arr2 = arr.filter(Boolean); // expect ("foo" | 1)[] + + + +//// [arrayFilterBooleanOverload.js] +"use strict"; +const nullableValues = ['a', 'b', null]; // expect (string | null)[] +const values1 = nullableValues.filter(Boolean); // expect string[] +// @ts-expect-error +const values2 = nullableValues.filter(new Boolean); +const arr = [0, 1, "", "foo", null]; +const arr2 = arr.filter(Boolean); // expect ("foo" | 1)[] + + +//// [arrayFilterBooleanOverload.d.ts] +declare const nullableValues: (string | null)[]; +declare const values1: string[]; +declare const values2: (string | null)[]; +declare const arr: readonly [0, 1, "", "foo", null]; +declare const arr2: (1 | "foo")[]; diff --git a/tests/baselines/reference/arrayFilterBooleanOverload.symbols b/tests/baselines/reference/arrayFilterBooleanOverload.symbols new file mode 100644 index 0000000000000..611ed5a8a7b94 --- /dev/null +++ b/tests/baselines/reference/arrayFilterBooleanOverload.symbols @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/arrayFilterBooleanOverload.ts] //// + +=== arrayFilterBooleanOverload.ts === +const nullableValues = ['a', 'b', null]; // expect (string | null)[] +>nullableValues : Symbol(nullableValues, Decl(arrayFilterBooleanOverload.ts, 0, 5)) + +const values1 = nullableValues.filter(Boolean); // expect string[] +>values1 : Symbol(values1, Decl(arrayFilterBooleanOverload.ts, 2, 5)) +>nullableValues.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>nullableValues : Symbol(nullableValues, Decl(arrayFilterBooleanOverload.ts, 0, 5)) +>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Boolean : Symbol(Boolean, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +// @ts-expect-error +const values2 = nullableValues.filter(new Boolean); +>values2 : Symbol(values2, Decl(arrayFilterBooleanOverload.ts, 5, 5)) +>nullableValues.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>nullableValues : Symbol(nullableValues, Decl(arrayFilterBooleanOverload.ts, 0, 5)) +>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Boolean : Symbol(Boolean, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +const arr = [0, 1, "", "foo", null] as const; +>arr : Symbol(arr, Decl(arrayFilterBooleanOverload.ts, 7, 5)) +>const : Symbol(const) + +const arr2 = arr.filter(Boolean); // expect ("foo" | 1)[] +>arr2 : Symbol(arr2, Decl(arrayFilterBooleanOverload.ts, 9, 5)) +>arr.filter : Symbol(ReadonlyArray.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>arr : Symbol(arr, Decl(arrayFilterBooleanOverload.ts, 7, 5)) +>filter : Symbol(ReadonlyArray.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Boolean : Symbol(Boolean, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + + diff --git a/tests/baselines/reference/arrayFilterBooleanOverload.types b/tests/baselines/reference/arrayFilterBooleanOverload.types new file mode 100644 index 0000000000000..03af0c6c60080 --- /dev/null +++ b/tests/baselines/reference/arrayFilterBooleanOverload.types @@ -0,0 +1,45 @@ +//// [tests/cases/compiler/arrayFilterBooleanOverload.ts] //// + +=== arrayFilterBooleanOverload.ts === +const nullableValues = ['a', 'b', null]; // expect (string | null)[] +>nullableValues : (string | null)[] +>['a', 'b', null] : (string | null)[] +>'a' : "a" +>'b' : "b" + +const values1 = nullableValues.filter(Boolean); // expect string[] +>values1 : string[] +>nullableValues.filter(Boolean) : string[] +>nullableValues.filter : { (predicate: (value: string | null, index: number, array: (string | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | null, index: number, array: (string | null)[]) => unknown, thisArg?: any): (string | null)[]; } +>nullableValues : (string | null)[] +>filter : { (predicate: (value: string | null, index: number, array: (string | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | null, index: number, array: (string | null)[]) => unknown, thisArg?: any): (string | null)[]; } +>Boolean : BooleanConstructor + +// @ts-expect-error +const values2 = nullableValues.filter(new Boolean); +>values2 : (string | null)[] +>nullableValues.filter(new Boolean) : (string | null)[] +>nullableValues.filter : { (predicate: (value: string | null, index: number, array: (string | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | null, index: number, array: (string | null)[]) => unknown, thisArg?: any): (string | null)[]; } +>nullableValues : (string | null)[] +>filter : { (predicate: (value: string | null, index: number, array: (string | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: string | null, index: number, array: (string | null)[]) => unknown, thisArg?: any): (string | null)[]; } +>new Boolean : Boolean +>Boolean : BooleanConstructor + +const arr = [0, 1, "", "foo", null] as const; +>arr : readonly [0, 1, "", "foo", null] +>[0, 1, "", "foo", null] as const : readonly [0, 1, "", "foo", null] +>[0, 1, "", "foo", null] : readonly [0, 1, "", "foo", null] +>0 : 0 +>1 : 1 +>"" : "" +>"foo" : "foo" + +const arr2 = arr.filter(Boolean); // expect ("foo" | 1)[] +>arr2 : (1 | "foo")[] +>arr.filter(Boolean) : (1 | "foo")[] +>arr.filter : { (predicate: (value: "" | 0 | 1 | "foo" | null, index: number, array: readonly ("" | 0 | 1 | "foo" | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: "" | 0 | 1 | "foo" | null, index: number, array: readonly ("" | 0 | 1 | "foo" | null)[]) => unknown, thisArg?: any): ("" | 0 | 1 | "foo" | null)[]; } +>arr : readonly [0, 1, "", "foo", null] +>filter : { (predicate: (value: "" | 0 | 1 | "foo" | null, index: number, array: readonly ("" | 0 | 1 | "foo" | null)[]) => value is S, thisArg?: any): S[]; (predicate: (value: "" | 0 | 1 | "foo" | null, index: number, array: readonly ("" | 0 | 1 | "foo" | null)[]) => unknown, thisArg?: any): ("" | 0 | 1 | "foo" | null)[]; } +>Boolean : BooleanConstructor + + diff --git a/tests/baselines/reference/unionOfArraysBooleanFilterCall.js b/tests/baselines/reference/unionOfArraysBooleanFilterCall.js new file mode 100644 index 0000000000000..bad8408e74d5b --- /dev/null +++ b/tests/baselines/reference/unionOfArraysBooleanFilterCall.js @@ -0,0 +1,38 @@ +//// [tests/cases/compiler/unionOfArraysBooleanFilterCall.ts] //// + +//// [unionOfArraysBooleanFilterCall.ts] +interface Fizz { + id: number; + member: number; +} +interface Buzz { + id: number; + member: string; +} +type Falsey = "" | 0 | false | null | undefined; + + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(Boolean); // expect type (Fizz|Buzz)[] + +([] as (Fizz|Falsey)[] | readonly (Buzz|Falsey)[]).filter(Boolean); // expect type (Fizz|Buzz)[] + +([] as [Fizz|Falsey] | readonly [(Buzz|Falsey)?]).filter(Boolean); // expect type (Fizz|Buzz)[] + +// confirm that the other filter signatures are still available and working + +declare function isFizz(x: unknown): x is Fizz; +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(isFizz); // expect type Fizz[] + +declare function isBuzz(x: unknown): x is Buzz; +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(isBuzz); // expect type Buzz[] + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(x => x && typeof x.member === "number"); // expect type (Fizz|Buzz|Falsey)[] + + +//// [unionOfArraysBooleanFilterCall.js] +[].filter(Boolean); // expect type (Fizz|Buzz)[] +[].filter(Boolean); // expect type (Fizz|Buzz)[] +[].filter(Boolean); // expect type (Fizz|Buzz)[] +[].filter(isFizz); // expect type Fizz[] +[].filter(isBuzz); // expect type Buzz[] +[].filter(x => x && typeof x.member === "number"); // expect type (Fizz|Buzz|Falsey)[] diff --git a/tests/baselines/reference/unionOfArraysBooleanFilterCall.symbols b/tests/baselines/reference/unionOfArraysBooleanFilterCall.symbols new file mode 100644 index 0000000000000..ca3964dc74977 --- /dev/null +++ b/tests/baselines/reference/unionOfArraysBooleanFilterCall.symbols @@ -0,0 +1,97 @@ +//// [tests/cases/compiler/unionOfArraysBooleanFilterCall.ts] //// + +=== unionOfArraysBooleanFilterCall.ts === +interface Fizz { +>Fizz : Symbol(Fizz, Decl(unionOfArraysBooleanFilterCall.ts, 0, 0)) + + id: number; +>id : Symbol(Fizz.id, Decl(unionOfArraysBooleanFilterCall.ts, 0, 16)) + + member: number; +>member : Symbol(Fizz.member, Decl(unionOfArraysBooleanFilterCall.ts, 1, 15)) +} +interface Buzz { +>Buzz : Symbol(Buzz, Decl(unionOfArraysBooleanFilterCall.ts, 3, 1)) + + id: number; +>id : Symbol(Buzz.id, Decl(unionOfArraysBooleanFilterCall.ts, 4, 16)) + + member: string; +>member : Symbol(Buzz.member, Decl(unionOfArraysBooleanFilterCall.ts, 5, 15)) +} +type Falsey = "" | 0 | false | null | undefined; +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 1)) + + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(Boolean); // expect type (Fizz|Buzz)[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Fizz : Symbol(Fizz, Decl(unionOfArraysBooleanFilterCall.ts, 0, 0)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 1)) +>Buzz : Symbol(Buzz, Decl(unionOfArraysBooleanFilterCall.ts, 3, 1)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 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, --, --)) +>Boolean : Symbol(Boolean, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + +([] as (Fizz|Falsey)[] | readonly (Buzz|Falsey)[]).filter(Boolean); // expect type (Fizz|Buzz)[] +>([] as (Fizz|Falsey)[] | readonly (Buzz|Falsey)[]).filter : Symbol(filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Fizz : Symbol(Fizz, Decl(unionOfArraysBooleanFilterCall.ts, 0, 0)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 1)) +>Buzz : Symbol(Buzz, Decl(unionOfArraysBooleanFilterCall.ts, 3, 1)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 1)) +>filter : Symbol(filter, Decl(lib.es5.d.ts, --, --), 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, --, --)) + +([] as [Fizz|Falsey] | readonly [(Buzz|Falsey)?]).filter(Boolean); // expect type (Fizz|Buzz)[] +>([] as [Fizz|Falsey] | readonly [(Buzz|Falsey)?]).filter : Symbol(filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Fizz : Symbol(Fizz, Decl(unionOfArraysBooleanFilterCall.ts, 0, 0)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 1)) +>Buzz : Symbol(Buzz, Decl(unionOfArraysBooleanFilterCall.ts, 3, 1)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 1)) +>filter : Symbol(filter, Decl(lib.es5.d.ts, --, --), 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, --, --)) + +// confirm that the other filter signatures are still available and working + +declare function isFizz(x: unknown): x is Fizz; +>isFizz : Symbol(isFizz, Decl(unionOfArraysBooleanFilterCall.ts, 15, 66)) +>x : Symbol(x, Decl(unionOfArraysBooleanFilterCall.ts, 19, 24)) +>x : Symbol(x, Decl(unionOfArraysBooleanFilterCall.ts, 19, 24)) +>Fizz : Symbol(Fizz, Decl(unionOfArraysBooleanFilterCall.ts, 0, 0)) + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(isFizz); // expect type Fizz[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Fizz : Symbol(Fizz, Decl(unionOfArraysBooleanFilterCall.ts, 0, 0)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 1)) +>Buzz : Symbol(Buzz, Decl(unionOfArraysBooleanFilterCall.ts, 3, 1)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 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, --, --)) +>isFizz : Symbol(isFizz, Decl(unionOfArraysBooleanFilterCall.ts, 15, 66)) + +declare function isBuzz(x: unknown): x is Buzz; +>isBuzz : Symbol(isBuzz, Decl(unionOfArraysBooleanFilterCall.ts, 20, 57)) +>x : Symbol(x, Decl(unionOfArraysBooleanFilterCall.ts, 22, 24)) +>x : Symbol(x, Decl(unionOfArraysBooleanFilterCall.ts, 22, 24)) +>Buzz : Symbol(Buzz, Decl(unionOfArraysBooleanFilterCall.ts, 3, 1)) + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(isBuzz); // expect type Buzz[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Fizz : Symbol(Fizz, Decl(unionOfArraysBooleanFilterCall.ts, 0, 0)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 1)) +>Buzz : Symbol(Buzz, Decl(unionOfArraysBooleanFilterCall.ts, 3, 1)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 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, --, --)) +>isBuzz : Symbol(isBuzz, Decl(unionOfArraysBooleanFilterCall.ts, 20, 57)) + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(x => x && typeof x.member === "number"); // expect type (Fizz|Buzz|Falsey)[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Fizz : Symbol(Fizz, Decl(unionOfArraysBooleanFilterCall.ts, 0, 0)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 1)) +>Buzz : Symbol(Buzz, Decl(unionOfArraysBooleanFilterCall.ts, 3, 1)) +>Falsey : Symbol(Falsey, Decl(unionOfArraysBooleanFilterCall.ts, 7, 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, --, --)) +>x : Symbol(x, Decl(unionOfArraysBooleanFilterCall.ts, 25, 49)) +>x : Symbol(x, Decl(unionOfArraysBooleanFilterCall.ts, 25, 49)) +>x.member : Symbol(member, Decl(unionOfArraysBooleanFilterCall.ts, 1, 15), Decl(unionOfArraysBooleanFilterCall.ts, 5, 15)) +>x : Symbol(x, Decl(unionOfArraysBooleanFilterCall.ts, 25, 49)) +>member : Symbol(member, Decl(unionOfArraysBooleanFilterCall.ts, 1, 15), Decl(unionOfArraysBooleanFilterCall.ts, 5, 15)) + diff --git a/tests/baselines/reference/unionOfArraysBooleanFilterCall.types b/tests/baselines/reference/unionOfArraysBooleanFilterCall.types new file mode 100644 index 0000000000000..b2fcf008e6071 --- /dev/null +++ b/tests/baselines/reference/unionOfArraysBooleanFilterCall.types @@ -0,0 +1,95 @@ +//// [tests/cases/compiler/unionOfArraysBooleanFilterCall.ts] //// + +=== unionOfArraysBooleanFilterCall.ts === +interface Fizz { + id: number; +>id : number + + member: number; +>member : number +} +interface Buzz { + id: number; +>id : number + + member: string; +>member : string +} +type Falsey = "" | 0 | false | null | undefined; +>Falsey : false | "" | 0 +>false : false + + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(Boolean); // expect type (Fizz|Buzz)[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(Boolean) : (Fizz | Buzz)[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]) : (Fizz | Falsey)[] | (Buzz | Falsey)[] +>[] as (Fizz|Falsey)[] | (Buzz|Falsey)[] : (Fizz | Falsey)[] | (Buzz | Falsey)[] +>[] : undefined[] +>filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>Boolean : BooleanConstructor + +([] as (Fizz|Falsey)[] | readonly (Buzz|Falsey)[]).filter(Boolean); // expect type (Fizz|Buzz)[] +>([] as (Fizz|Falsey)[] | readonly (Buzz|Falsey)[]).filter(Boolean) : (Fizz | Buzz)[] +>([] as (Fizz|Falsey)[] | readonly (Buzz|Falsey)[]).filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: readonly (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: readonly (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>([] as (Fizz|Falsey)[] | readonly (Buzz|Falsey)[]) : (Fizz | Falsey)[] | readonly (Buzz | Falsey)[] +>[] as (Fizz|Falsey)[] | readonly (Buzz|Falsey)[] : (Fizz | Falsey)[] | readonly (Buzz | Falsey)[] +>[] : undefined[] +>filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: readonly (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: readonly (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>Boolean : BooleanConstructor + +([] as [Fizz|Falsey] | readonly [(Buzz|Falsey)?]).filter(Boolean); // expect type (Fizz|Buzz)[] +>([] as [Fizz|Falsey] | readonly [(Buzz|Falsey)?]).filter(Boolean) : (Fizz | Buzz)[] +>([] as [Fizz|Falsey] | readonly [(Buzz|Falsey)?]).filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: readonly (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: readonly (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>([] as [Fizz|Falsey] | readonly [(Buzz|Falsey)?]) : [Fizz | Falsey] | readonly [(Buzz | Falsey)?] +>[] as [Fizz|Falsey] | readonly [(Buzz|Falsey)?] : [Fizz | Falsey] | readonly [(Buzz | Falsey)?] +>[] : [] +>filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: readonly (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: readonly (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>Boolean : BooleanConstructor + +// confirm that the other filter signatures are still available and working + +declare function isFizz(x: unknown): x is Fizz; +>isFizz : (x: unknown) => x is Fizz +>x : unknown + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(isFizz); // expect type Fizz[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(isFizz) : Fizz[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]) : (Fizz | Falsey)[] | (Buzz | Falsey)[] +>[] as (Fizz|Falsey)[] | (Buzz|Falsey)[] : (Fizz | Falsey)[] | (Buzz | Falsey)[] +>[] : undefined[] +>filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>isFizz : (x: unknown) => x is Fizz + +declare function isBuzz(x: unknown): x is Buzz; +>isBuzz : (x: unknown) => x is Buzz +>x : unknown + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(isBuzz); // expect type Buzz[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(isBuzz) : Buzz[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]) : (Fizz | Falsey)[] | (Buzz | Falsey)[] +>[] as (Fizz|Falsey)[] | (Buzz|Falsey)[] : (Fizz | Falsey)[] | (Buzz | Falsey)[] +>[] : undefined[] +>filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>isBuzz : (x: unknown) => x is Buzz + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(x => x && typeof x.member === "number"); // expect type (Fizz|Buzz|Falsey)[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(x => x && typeof x.member === "number") : (Fizz | Buzz | Falsey)[] +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]) : (Fizz | Falsey)[] | (Buzz | Falsey)[] +>[] as (Fizz|Falsey)[] | (Buzz|Falsey)[] : (Fizz | Falsey)[] | (Buzz | Falsey)[] +>[] : undefined[] +>filter : { (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Fizz | Falsey, index: number, array: (Fizz | Falsey)[]) => unknown, thisArg?: any): (Fizz | Falsey)[]; } | { (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => value is S, thisArg?: any): S[]; (predicate: (value: Buzz | Falsey, index: number, array: (Buzz | Falsey)[]) => unknown, thisArg?: any): (Buzz | Falsey)[]; } +>x => x && typeof x.member === "number" : (x: Fizz | Buzz | Falsey) => boolean +>x : Fizz | Buzz | Falsey +>x && typeof x.member === "number" : boolean +>x : Fizz | Buzz | Falsey +>typeof x.member === "number" : boolean +>typeof x.member : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>x.member : string | number +>x : Fizz | Buzz +>member : string | number +>"number" : "number" + diff --git a/tests/cases/compiler/arrayFilterBooleanOverload.ts b/tests/cases/compiler/arrayFilterBooleanOverload.ts new file mode 100644 index 0000000000000..a9f4faa9c0538 --- /dev/null +++ b/tests/cases/compiler/arrayFilterBooleanOverload.ts @@ -0,0 +1,15 @@ +// @strict: true +// @declaration: true +// @target: es6 + +const nullableValues = ['a', 'b', null]; // expect (string | null)[] + +const values1 = nullableValues.filter(Boolean); // expect string[] + +// @ts-expect-error +const values2 = nullableValues.filter(new Boolean); + +const arr = [0, 1, "", "foo", null] as const; + +const arr2 = arr.filter(Boolean); // expect ("foo" | 1)[] + diff --git a/tests/cases/compiler/unionOfArraysBooleanFilterCall.ts b/tests/cases/compiler/unionOfArraysBooleanFilterCall.ts new file mode 100644 index 0000000000000..2e2de7a299558 --- /dev/null +++ b/tests/cases/compiler/unionOfArraysBooleanFilterCall.ts @@ -0,0 +1,27 @@ +// @target: es6 +interface Fizz { + id: number; + member: number; +} +interface Buzz { + id: number; + member: string; +} +type Falsey = "" | 0 | false | null | undefined; + + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(Boolean); // expect type (Fizz|Buzz)[] + +([] as (Fizz|Falsey)[] | readonly (Buzz|Falsey)[]).filter(Boolean); // expect type (Fizz|Buzz)[] + +([] as [Fizz|Falsey] | readonly [(Buzz|Falsey)?]).filter(Boolean); // expect type (Fizz|Buzz)[] + +// confirm that the other filter signatures are still available and working + +declare function isFizz(x: unknown): x is Fizz; +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(isFizz); // expect type Fizz[] + +declare function isBuzz(x: unknown): x is Buzz; +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(isBuzz); // expect type Buzz[] + +([] as (Fizz|Falsey)[] | (Buzz|Falsey)[]).filter(x => x && typeof x.member === "number"); // expect type (Fizz|Buzz|Falsey)[]