@@ -387,6 +387,7 @@ namespace ts {
387
387
const intersectionTypes = createMap<IntersectionType>();
388
388
const literalTypes = createMap<LiteralType>();
389
389
const indexedAccessTypes = createMap<IndexedAccessType>();
390
+ const conditionalTypes = createMap<Type>();
390
391
const evolvingArrayTypes: EvolvingArrayType[] = [];
391
392
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
392
393
@@ -7461,15 +7462,25 @@ namespace ts {
7461
7462
return baseConstraint && baseConstraint !== type ? baseConstraint : undefined;
7462
7463
}
7463
7464
7465
+ function getDefaultConstraintOfTrueBranchOfConditionalType(root: ConditionalRoot, combinedMapper: TypeMapper | undefined, mapper: TypeMapper | undefined) {
7466
+ const rootTrueType = root.trueType;
7467
+ const rootTrueConstraint = !(rootTrueType.flags & TypeFlags.Substitution)
7468
+ ? rootTrueType
7469
+ : instantiateType(((<SubstitutionType>rootTrueType).substitute), combinedMapper || mapper).flags & TypeFlags.AnyOrUnknown
7470
+ ? (<SubstitutionType>rootTrueType).typeVariable
7471
+ : getIntersectionType([(<SubstitutionType>rootTrueType).substitute, (<SubstitutionType>rootTrueType).typeVariable]);
7472
+ return instantiateType(rootTrueConstraint, combinedMapper || mapper);
7473
+ }
7474
+
7464
7475
function getDefaultConstraintOfConditionalType(type: ConditionalType) {
7465
7476
if (!type.resolvedDefaultConstraint) {
7466
- const rootTrueType = type.root.trueType;
7467
- const rootTrueConstraint = !(rootTrueType.flags & TypeFlags.Substitution)
7468
- ? rootTrueType
7469
- : ((<SubstitutionType>rootTrueType).substitute).flags & TypeFlags.AnyOrUnknown
7470
- ? (<SubstitutionType>rootTrueType).typeVariable
7471
- : getIntersectionType([(<SubstitutionType>rootTrueType).substitute, (<SubstitutionType>rootTrueType).typeVariable] );
7472
- type.resolvedDefaultConstraint = getUnionType([instantiateType(rootTrueConstraint, type.combinedMapper || type.mapper), getFalseTypeFromConditionalType(type) ]);
7477
+ // An `any` branch of a conditional type would normally be viral - specifically, without special handling here,
7478
+ // a conditional type with a single branch of type `any` would be assignable to anything, since it's constraint would simplify to
7479
+ // just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type,
7480
+ // in effect treating `any` like `never` rather than `unknown` in this location.
7481
+ const trueConstraint = getDefaultConstraintOfTrueBranchOfConditionalType(type.root, type.combinedMapper, type.mapper);
7482
+ const falseConstraint = getFalseTypeFromConditionalType(type );
7483
+ type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint ]);
7473
7484
}
7474
7485
return type.resolvedDefaultConstraint;
7475
7486
}
@@ -7480,7 +7491,13 @@ namespace ts {
7480
7491
// with its constraint. We do this because if the constraint is a union type it will be distributed
7481
7492
// over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T'
7482
7493
// removes 'undefined' from T.
7483
- if (type.root.isDistributive) {
7494
+ // We skip returning a distributive constraint for a restrictive instantiation of a conditional type
7495
+ // as the constraint for all type params (check type included) have been replace with `unknown`, which
7496
+ // is going to produce even more false positive/negative results than the distribute constraint already does.
7497
+ // Please note: the distributive constraint is a kludge for emulating what a negated type could to do filter
7498
+ // a union - once negated types exist and are applied to the conditional false branch, this "constraint"
7499
+ // likely doesn't need to exist.
7500
+ if (type.root.isDistributive && type.restrictiveInstantiation !== type) {
7484
7501
const simplified = getSimplifiedType(type.checkType);
7485
7502
const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified;
7486
7503
if (constraint && constraint !== type.checkType) {
@@ -10089,12 +10106,50 @@ namespace ts {
10089
10106
return type.flags & TypeFlags.Substitution ? (<SubstitutionType>type).typeVariable : type;
10090
10107
}
10091
10108
10109
+ /**
10110
+ * Invokes union simplification logic to determine if an intersection is considered empty as a union constituent
10111
+ */
10112
+ function isIntersectionEmpty(type1: Type, type2: Type) {
10113
+ return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never);
10114
+ }
10115
+
10092
10116
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined): Type {
10093
10117
const checkType = instantiateType(root.checkType, mapper);
10094
10118
const extendsType = instantiateType(root.extendsType, mapper);
10095
10119
if (checkType === wildcardType || extendsType === wildcardType) {
10096
10120
return wildcardType;
10097
10121
}
10122
+ const trueType = instantiateType(root.trueType, mapper);
10123
+ const falseType = instantiateType(root.falseType, mapper);
10124
+ const instantiationId = `${root.isDistributive ? "d" : ""}${getTypeId(checkType)}>${getTypeId(extendsType)}?${getTypeId(trueType)}:${getTypeId(falseType)}`;
10125
+ const result = conditionalTypes.get(instantiationId);
10126
+ if (result) {
10127
+ return result;
10128
+ }
10129
+ const newResult = getConditionalTypeWorker(root, mapper, checkType, extendsType, trueType, falseType);
10130
+ conditionalTypes.set(instantiationId, newResult);
10131
+ return newResult;
10132
+ }
10133
+
10134
+ function getConditionalTypeWorker(root: ConditionalRoot, mapper: TypeMapper | undefined, checkType: Type, extendsType: Type, trueType: Type, falseType: Type) {
10135
+ // Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`.
10136
+ if (falseType.flags & TypeFlags.Never && isTypeIdenticalTo(getActualTypeVariable(trueType), getActualTypeVariable(checkType))) {
10137
+ if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
10138
+ return getDefaultConstraintOfTrueBranchOfConditionalType(root, /*combinedMapper*/ undefined, mapper);
10139
+ }
10140
+ else if (isIntersectionEmpty(checkType, extendsType)) { // Always false
10141
+ return neverType;
10142
+ }
10143
+ }
10144
+ else if (trueType.flags & TypeFlags.Never && isTypeIdenticalTo(getActualTypeVariable(falseType), getActualTypeVariable(checkType))) {
10145
+ if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
10146
+ return neverType;
10147
+ }
10148
+ else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false
10149
+ return falseType; // TODO: Intersect negated `extends` type here
10150
+ }
10151
+ }
10152
+
10098
10153
const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable | TypeFlags.GenericMappedType);
10099
10154
let combinedMapper: TypeMapper | undefined;
10100
10155
if (root.inferTypeParameters) {
@@ -10112,18 +10167,18 @@ namespace ts {
10112
10167
// We attempt to resolve the conditional type only when the check and extends types are non-generic
10113
10168
if (!checkTypeInstantiable && !maybeTypeOfKind(inferredExtendsType, TypeFlags.Instantiable | TypeFlags.GenericMappedType)) {
10114
10169
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) {
10115
- return instantiateType(root. trueType, mapper) ;
10170
+ return trueType;
10116
10171
}
10117
10172
// Return union of trueType and falseType for 'any' since it matches anything
10118
10173
if (checkType.flags & TypeFlags.Any) {
10119
- return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root. falseType, mapper) ]);
10174
+ return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), falseType]);
10120
10175
}
10121
10176
// Return falseType for a definitely false extends check. We check an instantiations of the two
10122
10177
// types with type parameters mapped to the wildcard type, the most permissive instantiations
10123
10178
// possible (the wildcard type is assignable to and from all types). If those are not related,
10124
10179
// then no instantiations will be and we can just return the false branch type.
10125
10180
if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) {
10126
- return instantiateType(root. falseType, mapper) ;
10181
+ return falseType;
10127
10182
}
10128
10183
// Return trueType for a definitely true extends check. We check instantiations of the two
10129
10184
// types with type parameters mapped to their restrictive form, i.e. a form of the type parameter
@@ -10142,6 +10197,10 @@ namespace ts {
10142
10197
result.extendsType = extendsType;
10143
10198
result.mapper = mapper;
10144
10199
result.combinedMapper = combinedMapper;
10200
+ if (!combinedMapper) {
10201
+ result.resolvedTrueType = trueType;
10202
+ result.resolvedFalseType = falseType;
10203
+ }
10145
10204
result.aliasSymbol = root.aliasSymbol;
10146
10205
result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217
10147
10206
return result;
@@ -11132,8 +11191,20 @@ namespace ts {
11132
11191
}
11133
11192
11134
11193
function getRestrictiveInstantiation(type: Type) {
11135
- return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type :
11136
- type.restrictiveInstantiation || (type.restrictiveInstantiation = instantiateType(type, restrictiveMapper));
11194
+ if (type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never)) {
11195
+ return type;
11196
+ }
11197
+ if (type.restrictiveInstantiation) {
11198
+ return type.restrictiveInstantiation;
11199
+ }
11200
+ type.restrictiveInstantiation = instantiateType(type, restrictiveMapper);
11201
+ // We set the following so we don't attempt to set the restrictive instance of a restrictive instance
11202
+ // which is redundant - we'll produce new type identities, but all type params have already been mapped.
11203
+ // This also gives us a way to detect restrictive instances upon comparisons and _disable_ the "distributeive constraint"
11204
+ // assignability check for them, which is distinctly unsafe, as once you have a restrctive instance, all the type parameters
11205
+ // are constrained to `unknown` and produce tons of false positives/negatives!
11206
+ type.restrictiveInstantiation.restrictiveInstantiation = type.restrictiveInstantiation;
11207
+ return type.restrictiveInstantiation;
11137
11208
}
11138
11209
11139
11210
function instantiateIndexInfo(info: IndexInfo | undefined, mapper: TypeMapper): IndexInfo | undefined {
0 commit comments