Skip to content

Commit 771a055

Browse files
committed
Fix narrowing of intersections of type variables and primitive types
1 parent f918bd9 commit 771a055

File tree

1 file changed

+31
-15
lines changed

1 file changed

+31
-15
lines changed

src/compiler/checker.ts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17947,8 +17947,21 @@ namespace ts {
1794717947
if (target.flags & TypeFlags.Intersection) {
1794817948
return typeRelatedToEachType(getRegularTypeOfObjectLiteral(source), target as IntersectionType, reportErrors, IntersectionState.Target);
1794917949
}
17950-
// Source is an intersection. Check to see if any constituents of the intersection are immediately related
17951-
// to the target.
17950+
// Source is an intersection. For the comparable relation, if the target is a primitive type we hoist the
17951+
// constraints of all non-primitive types in the source into a new intersection. We do this because the
17952+
// intersection may further constrain the constraints of the non-primitive types. For example, given a type
17953+
// parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't
17954+
// appear to be comparable to '2'.
17955+
if (relation === comparableRelation && target.flags & TypeFlags.Primitive) {
17956+
const constraints = sameMap((<IntersectionType>source).types, t => t.flags & TypeFlags.Primitive ? t : getBaseConstraintOfType(t) || unknownType);
17957+
if (constraints !== (<IntersectionType>source).types) {
17958+
source = getIntersectionType(constraints);
17959+
if (!(source.flags & TypeFlags.Intersection)) {
17960+
return isRelatedTo(source, target, /*reportErrors*/ false);
17961+
}
17962+
}
17963+
}
17964+
// Check to see if any constituents of the intersection are immediately related to the target.
1795217965
//
1795317966
// Don't report errors though. Checking whether a constituent is related to the source is not actually
1795417967
// useful and leads to some confusing error messages. Instead it is better to let the below checks
@@ -19680,6 +19693,15 @@ namespace ts {
1968019693
return !!(type.flags & TypeFlags.Unit);
1968119694
}
1968219695

19696+
function isUnitLikeType(type: Type): boolean {
19697+
return type.flags & TypeFlags.Intersection ? some((<IntersectionType>type).types, isUnitType) :
19698+
!!(type.flags & TypeFlags.Unit);
19699+
}
19700+
19701+
function extractUnitType(type: Type) {
19702+
return type.flags & TypeFlags.Intersection ? find((<IntersectionType>type).types, isUnitType) || type : type;
19703+
}
19704+
1968319705
function isLiteralType(type: Type): boolean {
1968419706
return type.flags & TypeFlags.Boolean ? true :
1968519707
type.flags & TypeFlags.Union ? type.flags & TypeFlags.EnumLiteral ? true : every((<UnionType>type).types, isUnitType) :
@@ -21663,14 +21685,6 @@ namespace ts {
2166321685
return declaredType;
2166421686
}
2166521687

21666-
function getTypeFactsOfTypes(types: Type[]): TypeFacts {
21667-
let result: TypeFacts = TypeFacts.None;
21668-
for (const t of types) {
21669-
result |= getTypeFacts(t);
21670-
}
21671-
return result;
21672-
}
21673-
2167421688
function isFunctionObjectType(type: ObjectType): boolean {
2167521689
// We do a quick check for a "bind" property before performing the more expensive subtype
2167621690
// check. This gives us a quicker out in the common case where an object type is not a function.
@@ -21742,8 +21756,11 @@ namespace ts {
2174221756
return !isPatternLiteralType(type) ? getTypeFacts(getBaseConstraintOfType(type) || unknownType) :
2174321757
strictNullChecks ? TypeFacts.NonEmptyStringStrictFacts : TypeFacts.NonEmptyStringFacts;
2174421758
}
21745-
if (flags & TypeFlags.UnionOrIntersection) {
21746-
return getTypeFactsOfTypes((<UnionOrIntersectionType>type).types);
21759+
if (flags & TypeFlags.Union) {
21760+
return reduceLeft((<UnionType>type).types, (facts, t) => facts | getTypeFacts(t), TypeFacts.None);
21761+
}
21762+
if (flags & TypeFlags.Intersection) {
21763+
return reduceLeft((<UnionType>type).types, (facts, t) => facts & getTypeFacts(t), TypeFacts.All);
2174721764
}
2174821765
return TypeFacts.All;
2174921766
}
@@ -23075,8 +23092,7 @@ namespace ts {
2307523092
return replacePrimitivesWithLiterals(filterType(type, filterFn), valueType);
2307623093
}
2307723094
if (isUnitType(valueType)) {
23078-
const regularType = getRegularTypeOfLiteralType(valueType);
23079-
return filterType(type, t => isUnitType(t) ? !areTypesComparable(t, valueType) : getRegularTypeOfLiteralType(t) !== regularType);
23095+
return filterType(type, t => !(isUnitLikeType(t) && areTypesComparable(t, valueType)));
2308023096
}
2308123097
return type;
2308223098
}
@@ -23158,7 +23174,7 @@ namespace ts {
2315823174
if (!hasDefaultClause) {
2315923175
return caseType;
2316023176
}
23161-
const defaultType = filterType(type, t => !(isUnitType(t) && contains(switchTypes, getRegularTypeOfLiteralType(t))));
23177+
const defaultType = filterType(type, t => !(isUnitLikeType(t) && contains(switchTypes, getRegularTypeOfLiteralType(extractUnitType(t)))));
2316223178
return caseType.flags & TypeFlags.Never ? defaultType : getUnionType([caseType, defaultType]);
2316323179
}
2316423180

0 commit comments

Comments
 (0)