Skip to content

Commit 2c68ded

Browse files
authored
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
1 parent 4f29633 commit 2c68ded

File tree

6 files changed

+1667
-19
lines changed

6 files changed

+1667
-19
lines changed

src/compiler/checker.ts

+26-17
Original file line numberDiff line numberDiff line change
@@ -25405,23 +25405,32 @@ namespace ts {
2540525405
if (!assumeTrue) {
2540625406
return filterType(type, t => !isRelated(t, candidate));
2540725407
}
25408-
// If the current type is a union type, remove all constituents that couldn't be instances of
25409-
// the candidate type. If one or more constituents remain, return a union of those.
25410-
if (type.flags & TypeFlags.Union) {
25411-
const assignableType = filterType(type, t => isRelated(t, candidate));
25412-
if (!(assignableType.flags & TypeFlags.Never)) {
25413-
return assignableType;
25414-
}
25415-
}
25416-
// If the candidate type is a subtype of the target type, narrow to the candidate type.
25417-
// Otherwise, if the target type is assignable to the candidate type, keep the target type.
25418-
// Otherwise, if the candidate type is assignable to the target type, narrow to the candidate
25419-
// type. Otherwise, the types are completely unrelated, so narrow to an intersection of the
25420-
// two types.
25421-
return isTypeSubtypeOf(candidate, type) ? candidate :
25422-
isTypeAssignableTo(type, candidate) ? type :
25423-
isTypeAssignableTo(candidate, type) ? candidate :
25424-
getIntersectionType([type, candidate]);
25408+
if (type.flags & TypeFlags.AnyOrUnknown) {
25409+
return candidate;
25410+
}
25411+
// We first attempt to filter the current type, narrowing constituents as appropriate and removing
25412+
// constituents that are unrelated to the candidate.
25413+
const keyPropertyName = type.flags & TypeFlags.Union ? getKeyPropertyName(type as UnionType) : undefined;
25414+
const narrowedType = mapType(candidate, c => {
25415+
// If a discriminant property is available, use that to reduce the type.
25416+
const discriminant = keyPropertyName && getTypeOfPropertyOfType(c, keyPropertyName);
25417+
const matching = discriminant && getConstituentTypeForKeyType(type as UnionType, discriminant);
25418+
// For each constituent t in the current type, if t and and c are directly related, pick the most
25419+
// specific of the two.
25420+
const directlyRelated = mapType(matching || type, t => isRelated(t, c) ? t : isRelated(c, t) ? c : neverType);
25421+
// If no constituents are directly related, create intersections for any generic constituents that
25422+
// are related by constraint.
25423+
return directlyRelated.flags & TypeFlags.Never ?
25424+
mapType(type, t => maybeTypeOfKind(t, TypeFlags.Instantiable) && isRelated(c, getBaseConstraintOfType(t) || unknownType) ? getIntersectionType([t, c]) : neverType) :
25425+
directlyRelated;
25426+
});
25427+
// If filtering produced a non-empty type, return that. Otherwise, pick the most specific of the two
25428+
// based on assignability, or as a last resort produce an intersection.
25429+
return !(narrowedType.flags & TypeFlags.Never) ? narrowedType :
25430+
isTypeSubtypeOf(candidate, type) ? candidate :
25431+
isTypeAssignableTo(type, candidate) ? type :
25432+
isTypeAssignableTo(candidate, type) ? candidate :
25433+
getIntersectionType([type, candidate]);
2542525434
}
2542625435

2542725436
function narrowTypeByCallExpression(type: Type, callExpression: CallExpression, assumeTrue: boolean): Type {

tests/baselines/reference/controlFlowOptionalChain.types

+2-2
Original file line numberDiff line numberDiff line change
@@ -1768,9 +1768,9 @@ function f30(o: Thing | undefined) {
17681768
>foo : string | number | undefined
17691769

17701770
o.foo;
1771-
>o.foo : string | number
1771+
>o.foo : NonNullable<string | number | undefined>
17721772
>o : Thing
1773-
>foo : string | number
1773+
>foo : NonNullable<string | number | undefined>
17741774
}
17751775
}
17761776

0 commit comments

Comments
 (0)