Don't widen when type parameter occurs at top level in type predicate (#52031)

This commit is contained in:
Anders Hejlsberg
2023-01-12 13:18:36 -08:00
committed by GitHub
parent 5b4a8d4134
commit 59e4e38d08
5 changed files with 144 additions and 1 deletions
+7 -1
View File
@@ -23465,6 +23465,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();
@@ -24573,7 +24579,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;
@@ -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<T>(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"[]
@@ -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<T>(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))
@@ -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 : <U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[]
>['Mike', 'Joe'] : string[]
>'Mike' : "Mike"
>'Joe' : "Joe"
>map : <U>(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<T>(a: T | undefined): a is T {
>isDefined : <T>(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 : { <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)[]; }
>admins : ("admin" | undefined)[]
>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)[]; }
>isDefined : <T>(a: T | undefined) => a is T
@@ -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<T>(a: T | undefined): a is T {
return a !== undefined;
}
const foundAdmins = admins.filter(isDefined); // "admin"[]