@@ -16794,6 +16794,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16794
16794
if (!(flags & TypeFlags.Never)) {
16795
16795
includes |= flags & TypeFlags.IncludesMask;
16796
16796
if (flags & TypeFlags.Instantiable) includes |= TypeFlags.IncludesInstantiable;
16797
+ if (flags & TypeFlags.Intersection && getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) includes |= TypeFlags.IncludesConstrainedTypeVariable;
16797
16798
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
16798
16799
if (!strictNullChecks && flags & TypeFlags.Nullable) {
16799
16800
if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType;
@@ -16938,6 +16939,49 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
16938
16939
}
16939
16940
}
16940
16941
16942
+ function removeConstrainedTypeVariables(types: Type[]) {
16943
+ const typeVariables: TypeVariable[] = [];
16944
+ // First collect a list of the type variables occurring in constraining intersections.
16945
+ for (const type of types) {
16946
+ if (getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) {
16947
+ const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1;
16948
+ pushIfUnique(typeVariables, (type as IntersectionType).types[index]);
16949
+ }
16950
+ }
16951
+ // For each type variable, check if the constraining intersections for that type variable fully
16952
+ // cover the constraint of the type variable; if so, remove the constraining intersections and
16953
+ // substitute the type variable.
16954
+ for (const typeVariable of typeVariables) {
16955
+ const primitives: Type[] = [];
16956
+ // First collect the primitive types from the constraining intersections.
16957
+ for (const type of types) {
16958
+ if (getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) {
16959
+ const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1;
16960
+ if ((type as IntersectionType).types[index] === typeVariable) {
16961
+ insertType(primitives, (type as IntersectionType).types[1 - index]);
16962
+ }
16963
+ }
16964
+ }
16965
+ // If every constituent in the type variable's constraint is covered by an intersection of the type
16966
+ // variable and that constituent, remove those intersections and substitute the type variable.
16967
+ const constraint = getBaseConstraintOfType(typeVariable)!;
16968
+ if (everyType(constraint, t => containsType(primitives, t))) {
16969
+ let i = types.length;
16970
+ while (i > 0) {
16971
+ i--;
16972
+ const type = types[i];
16973
+ if (getObjectFlags(type) & ObjectFlags.IsConstrainedTypeVariable) {
16974
+ const index = (type as IntersectionType).types[0].flags & TypeFlags.TypeVariable ? 0 : 1;
16975
+ if ((type as IntersectionType).types[index] === typeVariable && containsType(primitives, (type as IntersectionType).types[1 - index])) {
16976
+ orderedRemoveItemAt(types, i);
16977
+ }
16978
+ }
16979
+ }
16980
+ insertType(types, typeVariable);
16981
+ }
16982
+ }
16983
+ }
16984
+
16941
16985
function isNamedUnionType(type: Type) {
16942
16986
return !!(type.flags & TypeFlags.Union && (type.aliasSymbol || (type as UnionType).origin));
16943
16987
}
@@ -17012,6 +17056,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
17012
17056
if (includes & TypeFlags.StringLiteral && includes & TypeFlags.TemplateLiteral) {
17013
17057
removeStringLiteralsMatchedByTemplateLiterals(typeSet);
17014
17058
}
17059
+ if (includes & TypeFlags.IncludesConstrainedTypeVariable) {
17060
+ removeConstrainedTypeVariables(typeSet);
17061
+ }
17015
17062
if (unionReduction === UnionReduction.Subtype) {
17016
17063
typeSet = removeSubtypes(typeSet, !!(includes & TypeFlags.Object));
17017
17064
if (!typeSet) {
@@ -17276,9 +17323,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
17276
17323
return true;
17277
17324
}
17278
17325
17279
- function createIntersectionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) {
17326
+ function createIntersectionType(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]) {
17280
17327
const result = createType(TypeFlags.Intersection) as IntersectionType;
17281
- result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
17328
+ result.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
17282
17329
result.types = types;
17283
17330
result.aliasSymbol = aliasSymbol;
17284
17331
result.aliasTypeArguments = aliasTypeArguments;
@@ -17299,6 +17346,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
17299
17346
const typeMembershipMap = new Map<string, Type>();
17300
17347
const includes = addTypesToIntersection(typeMembershipMap, 0 as TypeFlags, types);
17301
17348
const typeSet: Type[] = arrayFrom(typeMembershipMap.values());
17349
+ let objectFlags = ObjectFlags.None;
17302
17350
// An intersection type is considered empty if it contains
17303
17351
// the type never, or
17304
17352
// more than one unit type or,
@@ -17350,6 +17398,36 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
17350
17398
if (typeSet.length === 1) {
17351
17399
return typeSet[0];
17352
17400
}
17401
+ if (typeSet.length === 2) {
17402
+ const typeVarIndex = typeSet[0].flags & TypeFlags.TypeVariable ? 0 : 1;
17403
+ const typeVariable = typeSet[typeVarIndex];
17404
+ const primitiveType = typeSet[1 - typeVarIndex];
17405
+ if (typeVariable.flags & TypeFlags.TypeVariable && (primitiveType.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive) || includes & TypeFlags.IncludesEmptyObject)) {
17406
+ // We have an intersection T & P or P & T, where T is a type variable and P is a primitive type, the object type, or {}.
17407
+ const constraint = getBaseConstraintOfType(typeVariable);
17408
+ // Check that T's constraint is similarly composed of primitive types, the object type, or {}.
17409
+ if (constraint && everyType(constraint, t => !!(t.flags & (TypeFlags.Primitive | TypeFlags.NonPrimitive)) || isEmptyAnonymousObjectType(t))) {
17410
+ // If T's constraint is a subtype of P, simply return T. For example, given `T extends "a" | "b"`,
17411
+ // the intersection `T & string` reduces to just T.
17412
+ if (isTypeStrictSubtypeOf(constraint, primitiveType)) {
17413
+ return typeVariable;
17414
+ }
17415
+ if (!(constraint.flags & TypeFlags.Union && someType(constraint, c => isTypeStrictSubtypeOf(c, primitiveType)))) {
17416
+ // No constituent of T's constraint is a subtype of P. If P is also not a subtype of T's constraint,
17417
+ // then the constraint and P are unrelated, and the intersection reduces to never. For example, given
17418
+ // `T extends "a" | "b"`, the intersection `T & number` reduces to never.
17419
+ if (!isTypeStrictSubtypeOf(primitiveType, constraint)) {
17420
+ return neverType;
17421
+ }
17422
+ }
17423
+ // Some constituent of T's constraint is a subtype of P, or P is a subtype of T's constraint. Thus,
17424
+ // the intersection further constrains the type variable. For example, given `T extends string | number`,
17425
+ // the intersection `T & "a"` is marked as a constrained type variable. Likewise, given `T extends "a" | 1`,
17426
+ // the intersection `T & number` is marked as a constrained type variable.
17427
+ objectFlags = ObjectFlags.IsConstrainedTypeVariable;
17428
+ }
17429
+ }
17430
+ }
17353
17431
const id = getTypeListId(typeSet) + getAliasId(aliasSymbol, aliasTypeArguments);
17354
17432
let result = intersectionTypes.get(id);
17355
17433
if (!result) {
@@ -17385,7 +17463,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
17385
17463
}
17386
17464
}
17387
17465
else {
17388
- result = createIntersectionType(typeSet, aliasSymbol, aliasTypeArguments);
17466
+ result = createIntersectionType(typeSet, objectFlags, aliasSymbol, aliasTypeArguments);
17389
17467
}
17390
17468
intersectionTypes.set(id, result);
17391
17469
}
0 commit comments