Improve narrowing logic for instanceof, type predicate functions, and assertion functions (#49625)

* Improve narrowing logic for instanceof, type predicates, and assertions

* Accept new baselines

* Add tests

* Tweak algorithm

* Accept new baselines

* Optimize for discriminated unions
This commit is contained in:
Anders Hejlsberg
2022-07-15 14:01:55 -10:00
committed by GitHub
parent 4f29633934
commit 2c68ded954
6 changed files with 1666 additions and 18 deletions

View File

@@ -25405,23 +25405,32 @@ namespace ts {
if (!assumeTrue) {
return filterType(type, t => !isRelated(t, candidate));
}
// If the current type is a union type, remove all constituents that couldn't be instances of
// the candidate type. If one or more constituents remain, return a union of those.
if (type.flags & TypeFlags.Union) {
const assignableType = filterType(type, t => isRelated(t, candidate));
if (!(assignableType.flags & TypeFlags.Never)) {
return assignableType;
}
if (type.flags & TypeFlags.AnyOrUnknown) {
return candidate;
}
// If the candidate type is a subtype of the target type, narrow to the candidate type.
// Otherwise, if the target type is assignable to the candidate type, keep the target type.
// Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
// type. Otherwise, the types are completely unrelated, so narrow to an intersection of the
// two types.
return isTypeSubtypeOf(candidate, type) ? candidate :
isTypeAssignableTo(type, candidate) ? type :
isTypeAssignableTo(candidate, type) ? candidate :
getIntersectionType([type, candidate]);
// We first attempt to filter the current type, narrowing constituents as appropriate and removing
// constituents that are unrelated to the candidate.
const keyPropertyName = type.flags & TypeFlags.Union ? getKeyPropertyName(type as UnionType) : undefined;
const narrowedType = mapType(candidate, c => {
// If a discriminant property is available, use that to reduce the type.
const discriminant = keyPropertyName && getTypeOfPropertyOfType(c, keyPropertyName);
const matching = discriminant && getConstituentTypeForKeyType(type as UnionType, discriminant);
// For each constituent t in the current type, if t and and c are directly related, pick the most
// specific of the two.
const directlyRelated = mapType(matching || type, t => isRelated(t, c) ? t : isRelated(c, t) ? c : neverType);
// If no constituents are directly related, create intersections for any generic constituents that
// are related by constraint.
return directlyRelated.flags & TypeFlags.Never ?
mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType) :
directlyRelated;
});
// If filtering produced a non-empty type, return that. Otherwise, pick the most specific of the two
// based on assignability, or as a last resort produce an intersection.
return !(narrowedType.flags & TypeFlags.Never) ? narrowedType :
isTypeSubtypeOf(candidate, type) ? candidate :
isTypeAssignableTo(type, candidate) ? type :
isTypeAssignableTo(candidate, type) ? candidate :
getIntersectionType([type, candidate]);
}
function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {