diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b4a53faade07c..3468bd176e0bc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23382,6 +23382,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { type.flags & TypeFlags.Conditional && (getTrueTypeFromConditionalType(type as ConditionalType) === typeParameter || getFalseTypeFromConditionalType(type as ConditionalType) === typeParameter)); } + function isTypeParameterAtTopLevelInReturnType(signature: Signature, typeParameter: TypeParameter) { + const typePredicate = getTypePredicateOfSignature(signature); + return typePredicate ? !!typePredicate.type && isTypeParameterAtTopLevel(typePredicate.type, typeParameter) : + isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), typeParameter); + } + /** Create an object with properties named in the string literal type. Every property has type `any` */ function createEmptyObjectTypeFromStringLiteral(type: Type) { const members = createSymbolTable(); @@ -24458,7 +24464,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // the type parameter was fixed during inference or does not occur at top-level in the return type. const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter) || isConstTypeVariable(inference.typeParameter); const widenLiteralTypes = !primitiveConstraint && inference.topLevel && - (inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter)); + (inference.isFixed || !isTypeParameterAtTopLevelInReturnType(signature, inference.typeParameter)); const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) : widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) : candidates; diff --git a/tests/baselines/reference/typePredicateTopLevelTypeParameter.js b/tests/baselines/reference/typePredicateTopLevelTypeParameter.js new file mode 100644 index 0000000000000..3f9c667e830ef --- /dev/null +++ b/tests/baselines/reference/typePredicateTopLevelTypeParameter.js @@ -0,0 +1,30 @@ +//// [typePredicateTopLevelTypeParameter.ts] +// Repro from #51980 + +function getPermissions(user: string) { + if (user === 'Jack') return 'admin'; + return undefined; +} + +const admins = ['Mike', 'Joe'].map(e => getPermissions(e)); + +function isDefined(a: T | undefined): a is T { + return a !== undefined; +} + +const foundAdmins = admins.filter(isDefined); // "admin"[] + + +//// [typePredicateTopLevelTypeParameter.js] +"use strict"; +// Repro from #51980 +function getPermissions(user) { + if (user === 'Jack') + return 'admin'; + return undefined; +} +var admins = ['Mike', 'Joe'].map(function (e) { return getPermissions(e); }); +function isDefined(a) { + return a !== undefined; +} +var foundAdmins = admins.filter(isDefined); // "admin"[] diff --git a/tests/baselines/reference/typePredicateTopLevelTypeParameter.symbols b/tests/baselines/reference/typePredicateTopLevelTypeParameter.symbols new file mode 100644 index 0000000000000..4ac39d49e36b1 --- /dev/null +++ b/tests/baselines/reference/typePredicateTopLevelTypeParameter.symbols @@ -0,0 +1,42 @@ +=== tests/cases/compiler/typePredicateTopLevelTypeParameter.ts === +// Repro from #51980 + +function getPermissions(user: string) { +>getPermissions : Symbol(getPermissions, Decl(typePredicateTopLevelTypeParameter.ts, 0, 0)) +>user : Symbol(user, Decl(typePredicateTopLevelTypeParameter.ts, 2, 24)) + + if (user === 'Jack') return 'admin'; +>user : Symbol(user, Decl(typePredicateTopLevelTypeParameter.ts, 2, 24)) + + return undefined; +>undefined : Symbol(undefined) +} + +const admins = ['Mike', 'Joe'].map(e => getPermissions(e)); +>admins : Symbol(admins, Decl(typePredicateTopLevelTypeParameter.ts, 7, 5)) +>['Mike', 'Joe'].map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --)) +>e : Symbol(e, Decl(typePredicateTopLevelTypeParameter.ts, 7, 35)) +>getPermissions : Symbol(getPermissions, Decl(typePredicateTopLevelTypeParameter.ts, 0, 0)) +>e : Symbol(e, Decl(typePredicateTopLevelTypeParameter.ts, 7, 35)) + +function isDefined(a: T | undefined): a is T { +>isDefined : Symbol(isDefined, Decl(typePredicateTopLevelTypeParameter.ts, 7, 59)) +>T : Symbol(T, Decl(typePredicateTopLevelTypeParameter.ts, 9, 19)) +>a : Symbol(a, Decl(typePredicateTopLevelTypeParameter.ts, 9, 22)) +>T : Symbol(T, Decl(typePredicateTopLevelTypeParameter.ts, 9, 19)) +>a : Symbol(a, Decl(typePredicateTopLevelTypeParameter.ts, 9, 22)) +>T : Symbol(T, Decl(typePredicateTopLevelTypeParameter.ts, 9, 19)) + + return a !== undefined; +>a : Symbol(a, Decl(typePredicateTopLevelTypeParameter.ts, 9, 22)) +>undefined : Symbol(undefined) +} + +const foundAdmins = admins.filter(isDefined); // "admin"[] +>foundAdmins : Symbol(foundAdmins, Decl(typePredicateTopLevelTypeParameter.ts, 13, 5)) +>admins.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>admins : Symbol(admins, Decl(typePredicateTopLevelTypeParameter.ts, 7, 5)) +>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>isDefined : Symbol(isDefined, Decl(typePredicateTopLevelTypeParameter.ts, 7, 59)) + diff --git a/tests/baselines/reference/typePredicateTopLevelTypeParameter.types b/tests/baselines/reference/typePredicateTopLevelTypeParameter.types new file mode 100644 index 0000000000000..990495939be1b --- /dev/null +++ b/tests/baselines/reference/typePredicateTopLevelTypeParameter.types @@ -0,0 +1,49 @@ +=== tests/cases/compiler/typePredicateTopLevelTypeParameter.ts === +// Repro from #51980 + +function getPermissions(user: string) { +>getPermissions : (user: string) => "admin" | undefined +>user : string + + if (user === 'Jack') return 'admin'; +>user === 'Jack' : boolean +>user : string +>'Jack' : "Jack" +>'admin' : "admin" + + return undefined; +>undefined : undefined +} + +const admins = ['Mike', 'Joe'].map(e => getPermissions(e)); +>admins : ("admin" | undefined)[] +>['Mike', 'Joe'].map(e => getPermissions(e)) : ("admin" | undefined)[] +>['Mike', 'Joe'].map : (callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[] +>['Mike', 'Joe'] : string[] +>'Mike' : "Mike" +>'Joe' : "Joe" +>map : (callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[] +>e => getPermissions(e) : (e: string) => "admin" | undefined +>e : string +>getPermissions(e) : "admin" | undefined +>getPermissions : (user: string) => "admin" | undefined +>e : string + +function isDefined(a: T | undefined): a is T { +>isDefined : (a: T | undefined) => a is T +>a : T | undefined + + return a !== undefined; +>a !== undefined : boolean +>a : T | undefined +>undefined : undefined +} + +const foundAdmins = admins.filter(isDefined); // "admin"[] +>foundAdmins : "admin"[] +>admins.filter(isDefined) : "admin"[] +>admins.filter : { (predicate: (value: "admin" | undefined, index: number, array: ("admin" | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: "admin" | undefined, index: number, array: ("admin" | undefined)[]) => unknown, thisArg?: any): ("admin" | undefined)[]; } +>admins : ("admin" | undefined)[] +>filter : { (predicate: (value: "admin" | undefined, index: number, array: ("admin" | undefined)[]) => value is S, thisArg?: any): S[]; (predicate: (value: "admin" | undefined, index: number, array: ("admin" | undefined)[]) => unknown, thisArg?: any): ("admin" | undefined)[]; } +>isDefined : (a: T | undefined) => a is T + diff --git a/tests/cases/compiler/typePredicateTopLevelTypeParameter.ts b/tests/cases/compiler/typePredicateTopLevelTypeParameter.ts new file mode 100644 index 0000000000000..bb886d6ca9145 --- /dev/null +++ b/tests/cases/compiler/typePredicateTopLevelTypeParameter.ts @@ -0,0 +1,16 @@ +// @strict: true + +// Repro from #51980 + +function getPermissions(user: string) { + if (user === 'Jack') return 'admin'; + return undefined; +} + +const admins = ['Mike', 'Joe'].map(e => getPermissions(e)); + +function isDefined(a: T | undefined): a is T { + return a !== undefined; +} + +const foundAdmins = admins.filter(isDefined); // "admin"[]