Skip to content

Commit 59e4e38

Browse files
authored
Don't widen when type parameter occurs at top level in type predicate (#52031)
1 parent 5b4a8d4 commit 59e4e38

File tree

5 files changed

+144
-1
lines changed

5 files changed

+144
-1
lines changed

src/compiler/checker.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23465,6 +23465,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2346523465
type.flags & TypeFlags.Conditional && (getTrueTypeFromConditionalType(type as ConditionalType) === typeParameter || getFalseTypeFromConditionalType(type as ConditionalType) === typeParameter));
2346623466
}
2346723467

23468+
function isTypeParameterAtTopLevelInReturnType(signature: Signature, typeParameter: TypeParameter) {
23469+
const typePredicate = getTypePredicateOfSignature(signature);
23470+
return typePredicate ? !!typePredicate.type && isTypeParameterAtTopLevel(typePredicate.type, typeParameter) :
23471+
isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), typeParameter);
23472+
}
23473+
2346823474
/** Create an object with properties named in the string literal type. Every property has type `any` */
2346923475
function createEmptyObjectTypeFromStringLiteral(type: Type) {
2347023476
const members = createSymbolTable();
@@ -24573,7 +24579,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2457324579
// the type parameter was fixed during inference or does not occur at top-level in the return type.
2457424580
const primitiveConstraint = hasPrimitiveConstraint(inference.typeParameter) || isConstTypeVariable(inference.typeParameter);
2457524581
const widenLiteralTypes = !primitiveConstraint && inference.topLevel &&
24576-
(inference.isFixed || !isTypeParameterAtTopLevel(getReturnTypeOfSignature(signature), inference.typeParameter));
24582+
(inference.isFixed || !isTypeParameterAtTopLevelInReturnType(signature, inference.typeParameter));
2457724583
const baseCandidates = primitiveConstraint ? sameMap(candidates, getRegularTypeOfLiteralType) :
2457824584
widenLiteralTypes ? sameMap(candidates, getWidenedLiteralType) :
2457924585
candidates;
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//// [typePredicateTopLevelTypeParameter.ts]
2+
// Repro from #51980
3+
4+
function getPermissions(user: string) {
5+
if (user === 'Jack') return 'admin';
6+
return undefined;
7+
}
8+
9+
const admins = ['Mike', 'Joe'].map(e => getPermissions(e));
10+
11+
function isDefined<T>(a: T | undefined): a is T {
12+
return a !== undefined;
13+
}
14+
15+
const foundAdmins = admins.filter(isDefined); // "admin"[]
16+
17+
18+
//// [typePredicateTopLevelTypeParameter.js]
19+
"use strict";
20+
// Repro from #51980
21+
function getPermissions(user) {
22+
if (user === 'Jack')
23+
return 'admin';
24+
return undefined;
25+
}
26+
var admins = ['Mike', 'Joe'].map(function (e) { return getPermissions(e); });
27+
function isDefined(a) {
28+
return a !== undefined;
29+
}
30+
var foundAdmins = admins.filter(isDefined); // "admin"[]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/typePredicateTopLevelTypeParameter.ts ===
2+
// Repro from #51980
3+
4+
function getPermissions(user: string) {
5+
>getPermissions : Symbol(getPermissions, Decl(typePredicateTopLevelTypeParameter.ts, 0, 0))
6+
>user : Symbol(user, Decl(typePredicateTopLevelTypeParameter.ts, 2, 24))
7+
8+
if (user === 'Jack') return 'admin';
9+
>user : Symbol(user, Decl(typePredicateTopLevelTypeParameter.ts, 2, 24))
10+
11+
return undefined;
12+
>undefined : Symbol(undefined)
13+
}
14+
15+
const admins = ['Mike', 'Joe'].map(e => getPermissions(e));
16+
>admins : Symbol(admins, Decl(typePredicateTopLevelTypeParameter.ts, 7, 5))
17+
>['Mike', 'Joe'].map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
18+
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
19+
>e : Symbol(e, Decl(typePredicateTopLevelTypeParameter.ts, 7, 35))
20+
>getPermissions : Symbol(getPermissions, Decl(typePredicateTopLevelTypeParameter.ts, 0, 0))
21+
>e : Symbol(e, Decl(typePredicateTopLevelTypeParameter.ts, 7, 35))
22+
23+
function isDefined<T>(a: T | undefined): a is T {
24+
>isDefined : Symbol(isDefined, Decl(typePredicateTopLevelTypeParameter.ts, 7, 59))
25+
>T : Symbol(T, Decl(typePredicateTopLevelTypeParameter.ts, 9, 19))
26+
>a : Symbol(a, Decl(typePredicateTopLevelTypeParameter.ts, 9, 22))
27+
>T : Symbol(T, Decl(typePredicateTopLevelTypeParameter.ts, 9, 19))
28+
>a : Symbol(a, Decl(typePredicateTopLevelTypeParameter.ts, 9, 22))
29+
>T : Symbol(T, Decl(typePredicateTopLevelTypeParameter.ts, 9, 19))
30+
31+
return a !== undefined;
32+
>a : Symbol(a, Decl(typePredicateTopLevelTypeParameter.ts, 9, 22))
33+
>undefined : Symbol(undefined)
34+
}
35+
36+
const foundAdmins = admins.filter(isDefined); // "admin"[]
37+
>foundAdmins : Symbol(foundAdmins, Decl(typePredicateTopLevelTypeParameter.ts, 13, 5))
38+
>admins.filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
39+
>admins : Symbol(admins, Decl(typePredicateTopLevelTypeParameter.ts, 7, 5))
40+
>filter : Symbol(Array.filter, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
41+
>isDefined : Symbol(isDefined, Decl(typePredicateTopLevelTypeParameter.ts, 7, 59))
42+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
=== tests/cases/compiler/typePredicateTopLevelTypeParameter.ts ===
2+
// Repro from #51980
3+
4+
function getPermissions(user: string) {
5+
>getPermissions : (user: string) => "admin" | undefined
6+
>user : string
7+
8+
if (user === 'Jack') return 'admin';
9+
>user === 'Jack' : boolean
10+
>user : string
11+
>'Jack' : "Jack"
12+
>'admin' : "admin"
13+
14+
return undefined;
15+
>undefined : undefined
16+
}
17+
18+
const admins = ['Mike', 'Joe'].map(e => getPermissions(e));
19+
>admins : ("admin" | undefined)[]
20+
>['Mike', 'Joe'].map(e => getPermissions(e)) : ("admin" | undefined)[]
21+
>['Mike', 'Joe'].map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
22+
>['Mike', 'Joe'] : string[]
23+
>'Mike' : "Mike"
24+
>'Joe' : "Joe"
25+
>map : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
26+
>e => getPermissions(e) : (e: string) => "admin" | undefined
27+
>e : string
28+
>getPermissions(e) : "admin" | undefined
29+
>getPermissions : (user: string) => "admin" | undefined
30+
>e : string
31+
32+
function isDefined<T>(a: T | undefined): a is T {
33+
>isDefined : <T>(a: T | undefined) => a is T
34+
>a : T | undefined
35+
36+
return a !== undefined;
37+
>a !== undefined : boolean
38+
>a : T | undefined
39+
>undefined : undefined
40+
}
41+
42+
const foundAdmins = admins.filter(isDefined); // "admin"[]
43+
>foundAdmins : "admin"[]
44+
>admins.filter(isDefined) : "admin"[]
45+
>admins.filter : { <S extends "admin" | undefined>(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)[]; }
46+
>admins : ("admin" | undefined)[]
47+
>filter : { <S extends "admin" | undefined>(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)[]; }
48+
>isDefined : <T>(a: T | undefined) => a is T
49+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @strict: true
2+
3+
// Repro from #51980
4+
5+
function getPermissions(user: string) {
6+
if (user === 'Jack') return 'admin';
7+
return undefined;
8+
}
9+
10+
const admins = ['Mike', 'Joe'].map(e => getPermissions(e));
11+
12+
function isDefined<T>(a: T | undefined): a is T {
13+
return a !== undefined;
14+
}
15+
16+
const foundAdmins = admins.filter(isDefined); // "admin"[]

0 commit comments

Comments
 (0)