Skip to content

Commit 70148a4

Browse files
committed
Improve logic that determines when to resolve conditional types
1 parent 4a0bc59 commit 70148a4

File tree

2 files changed

+71
-67
lines changed

2 files changed

+71
-67
lines changed

src/compiler/checker.ts

Lines changed: 68 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -444,10 +444,10 @@ namespace ts {
444444
const circularConstraintType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
445445
const resolvingDefaultType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined);
446446

447-
const markerSuperType = <TypeParameter>createType(TypeFlags.TypeParameter);
448-
const markerSubType = <TypeParameter>createType(TypeFlags.TypeParameter);
447+
const markerSuperType = createTypeParameter();
448+
const markerSubType = createTypeParameter();
449449
markerSubType.constraint = markerSuperType;
450-
const markerOtherType = <TypeParameter>createType(TypeFlags.TypeParameter);
450+
const markerOtherType = createTypeParameter();
451451

452452
const noTypePredicate = createIdentifierTypePredicate("<<unresolved>>", 0, anyType);
453453

@@ -663,7 +663,6 @@ namespace ts {
663663

664664
const subtypeRelation = createMap<RelationComparisonResult>();
665665
const assignableRelation = createMap<RelationComparisonResult>();
666-
const definitelyAssignableRelation = createMap<RelationComparisonResult>();
667666
const comparableRelation = createMap<RelationComparisonResult>();
668667
const identityRelation = createMap<RelationComparisonResult>();
669668
const enumRelation = createMap<RelationComparisonResult>();
@@ -2745,6 +2744,12 @@ namespace ts {
27452744
return getUnionType(arrayFrom(typeofEQFacts.keys(), getLiteralType));
27462745
}
27472746

2747+
function createTypeParameter(symbol?: Symbol) {
2748+
const type = <TypeParameter>createType(TypeFlags.TypeParameter);
2749+
if (symbol) type.symbol = symbol;
2750+
return type;
2751+
}
2752+
27482753
// A reserved member name starts with two underscores, but the third character cannot be an underscore
27492754
// or the @ symbol. A third underscore indicates an escaped form of an identifer that started
27502755
// with at least two underscores. The @ character indicates that the name is denoted by a well known ES
@@ -6085,9 +6090,8 @@ namespace ts {
60856090
(<GenericType>type).instantiations.set(getTypeListId(type.typeParameters), <GenericType>type);
60866091
(<GenericType>type).target = <GenericType>type;
60876092
(<GenericType>type).typeArguments = type.typeParameters;
6088-
type.thisType = <TypeParameter>createType(TypeFlags.TypeParameter);
6093+
type.thisType = createTypeParameter(symbol);
60896094
type.thisType.isThisType = true;
6090-
type.thisType.symbol = symbol;
60916095
type.thisType.constraint = type;
60926096
}
60936097
}
@@ -6228,20 +6232,12 @@ namespace ts {
62286232

62296233
function getDeclaredTypeOfTypeParameter(symbol: Symbol): TypeParameter {
62306234
const links = getSymbolLinks(symbol);
6231-
if (!links.declaredType) {
6232-
const type = <TypeParameter>createType(TypeFlags.TypeParameter);
6233-
type.symbol = symbol;
6234-
links.declaredType = type;
6235-
}
6236-
return <TypeParameter>links.declaredType;
6235+
return links.declaredType || (links.declaredType = createTypeParameter(symbol));
62376236
}
62386237

62396238
function getDeclaredTypeOfAlias(symbol: Symbol): Type {
62406239
const links = getSymbolLinks(symbol);
6241-
if (!links.declaredType) {
6242-
links.declaredType = getDeclaredTypeOfSymbol(resolveAlias(symbol));
6243-
}
6244-
return links.declaredType;
6240+
return links.declaredType || (links.declaredType = getDeclaredTypeOfSymbol(resolveAlias(symbol)));
62456241
}
62466242

62476243
function getDeclaredTypeOfSymbol(symbol: Symbol): Type {
@@ -7413,7 +7409,7 @@ namespace ts {
74137409
if (type.root.isDistributive) {
74147410
const simplified = getSimplifiedType(type.checkType);
74157411
const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified;
7416-
if (constraint) {
7412+
if (constraint && constraint !== type.checkType) {
74177413
const mapper = makeUnaryTypeMapper(type.root.checkType, constraint);
74187414
const instantiated = getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper));
74197415
if (!(instantiated.flags & TypeFlags.Never)) {
@@ -9049,7 +9045,7 @@ namespace ts {
90499045
if (arity) {
90509046
typeParameters = new Array(arity);
90519047
for (let i = 0; i < arity; i++) {
9052-
const typeParameter = typeParameters[i] = <TypeParameter>createType(TypeFlags.TypeParameter);
9048+
const typeParameter = typeParameters[i] = createTypeParameter();
90539049
if (i < maxLength) {
90549050
const property = createSymbol(SymbolFlags.Property | (i >= minLength ? SymbolFlags.Optional : 0), "" + i as __String);
90559051
property.type = typeParameter;
@@ -9070,7 +9066,7 @@ namespace ts {
90709066
type.instantiations.set(getTypeListId(type.typeParameters), <GenericType>type);
90719067
type.target = <GenericType>type;
90729068
type.typeArguments = type.typeParameters;
9073-
type.thisType = <TypeParameter>createType(TypeFlags.TypeParameter);
9069+
type.thisType = createTypeParameter();
90749070
type.thisType.isThisType = true;
90759071
type.thisType.constraint = type;
90769072
type.declaredProperties = properties;
@@ -9971,7 +9967,7 @@ namespace ts {
99719967
if (checkType === wildcardType || extendsType === wildcardType) {
99729968
return wildcardType;
99739969
}
9974-
const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable);
9970+
const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable | TypeFlags.GenericMappedType);
99759971
let combinedMapper: TypeMapper | undefined;
99769972
if (root.inferTypeParameters) {
99779973
const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None);
@@ -9986,7 +9982,7 @@ namespace ts {
99869982
// Instantiate the extends type including inferences for 'infer T' type parameters
99879983
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;
99889984
// We attempt to resolve the conditional type only when the check and extends types are non-generic
9989-
if (!checkTypeInstantiable && !maybeTypeOfKind(inferredExtendsType, TypeFlags.Instantiable)) {
9985+
if (!checkTypeInstantiable && !maybeTypeOfKind(inferredExtendsType, TypeFlags.Instantiable | TypeFlags.GenericMappedType)) {
99909986
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) {
99919987
return instantiateType(root.trueType, mapper);
99929988
}
@@ -9998,14 +9994,15 @@ namespace ts {
99989994
// types with type parameters mapped to the wildcard type, the most permissive instantiations
99999995
// possible (the wildcard type is assignable to and from all types). If those are not related,
100009996
// then no instatiations will be and we can just return the false branch type.
10001-
if (!isTypeAssignableTo(getWildcardInstantiation(checkType), getWildcardInstantiation(inferredExtendsType))) {
9997+
if (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) {
100029998
return instantiateType(root.falseType, mapper);
100039999
}
10004-
// Return trueType for a definitely true extends check. The definitely assignable relation excludes
10005-
// type variable constraints from consideration. Without the definitely assignable relation, the type
10000+
// Return trueType for a definitely true extends check. We check instantiations of the two
10001+
// types with type parameters mapped to their restrictive form, i.e. a form of the type parameter
10002+
// that has no constraint. This ensures that, for example, the type
1000610003
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
10007-
// would immediately resolve to 'string' instead of being deferred.
10008-
if (checkTypeRelatedTo(checkType, inferredExtendsType, definitelyAssignableRelation, /*errorNode*/ undefined)) {
10004+
// doesn't immediately resolve to 'string' instead of being deferred.
10005+
if (isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
1000910006
return instantiateType(root.trueType, combinedMapper || mapper);
1001010007
}
1001110008
}
@@ -10611,13 +10608,20 @@ namespace ts {
1061110608
return t => t === source ? target : baseMapper(t);
1061210609
}
1061310610

10614-
function wildcardMapper(type: Type) {
10611+
function permissiveMapper(type: Type) {
1061510612
return type.flags & TypeFlags.TypeParameter ? wildcardType : type;
1061610613
}
1061710614

10615+
function getRestrictiveTypeParameter(tp: TypeParameter) {
10616+
return !tp.constraint ? tp : tp.restrictiveInstantiation || (tp.restrictiveInstantiation = createTypeParameter(tp.symbol));
10617+
}
10618+
10619+
function restrictiveMapper(type: Type) {
10620+
return type.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(<TypeParameter>type) : type;
10621+
}
10622+
1061810623
function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter {
10619-
const result = <TypeParameter>createType(TypeFlags.TypeParameter);
10620-
result.symbol = typeParameter.symbol;
10624+
const result = createTypeParameter(typeParameter.symbol);
1062110625
result.target = typeParameter;
1062210626
return result;
1062310627
}
@@ -10969,9 +10973,14 @@ namespace ts {
1096910973
return type;
1097010974
}
1097110975

10972-
function getWildcardInstantiation(type: Type) {
10976+
function getPermissiveInstantiation(type: Type) {
1097310977
return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type :
10974-
type.wildcardInstantiation || (type.wildcardInstantiation = instantiateType(type, wildcardMapper));
10978+
type.permissiveInstantiation || (type.permissiveInstantiation = instantiateType(type, permissiveMapper));
10979+
}
10980+
10981+
function getRestrictiveInstantiation(type: Type) {
10982+
return type.flags & (TypeFlags.Primitive | TypeFlags.AnyOrUnknown | TypeFlags.Never) ? type :
10983+
type.restrictiveInstantiation || (type.restrictiveInstantiation = instantiateType(type, restrictiveMapper));
1097510984
}
1097610985

1097710986
function instantiateIndexInfo(info: IndexInfo | undefined, mapper: TypeMapper): IndexInfo | undefined {
@@ -11644,7 +11653,7 @@ namespace ts {
1164411653
if (s & TypeFlags.Null && (!strictNullChecks || t & TypeFlags.Null)) return true;
1164511654
if (s & TypeFlags.Object && t & TypeFlags.NonPrimitive) return true;
1164611655
if (s & TypeFlags.UniqueESSymbol || t & TypeFlags.UniqueESSymbol) return false;
11647-
if (relation === assignableRelation || relation === definitelyAssignableRelation || relation === comparableRelation) {
11656+
if (relation === assignableRelation || relation === comparableRelation) {
1164811657
if (s & TypeFlags.Any) return true;
1164911658
// Type number or any numeric literal type is assignable to any numeric enum type or any
1165011659
// numeric enum literal type. This rule exists for backwards compatibility reasons because
@@ -11836,7 +11845,7 @@ namespace ts {
1183611845
target = (<FreshableType>target).regularType;
1183711846
}
1183811847
if (source.flags & TypeFlags.Substitution) {
11839-
source = relation === definitelyAssignableRelation ? (<SubstitutionType>source).typeVariable : (<SubstitutionType>source).substitute;
11848+
source = (<SubstitutionType>source).substitute;
1184011849
}
1184111850
if (target.flags & TypeFlags.Substitution) {
1184211851
target = (<SubstitutionType>target).typeVariable;
@@ -12022,7 +12031,7 @@ namespace ts {
1202212031
}
1202312032
if (isExcessPropertyCheckTarget(target)) {
1202412033
const isComparingJsxAttributes = !!(getObjectFlags(source) & ObjectFlags.JsxAttributes);
12025-
if ((relation === assignableRelation || relation === definitelyAssignableRelation || relation === comparableRelation) &&
12034+
if ((relation === assignableRelation || relation === comparableRelation) &&
1202612035
(isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) {
1202712036
return false;
1202812037
}
@@ -12425,24 +12434,22 @@ namespace ts {
1242512434
}
1242612435
// A type S is assignable to keyof T if S is assignable to keyof C, where C is the
1242712436
// simplified form of T or, if T doesn't simplify, the constraint of T.
12428-
if (relation !== definitelyAssignableRelation) {
12429-
const simplified = getSimplifiedType((<IndexType>target).type);
12430-
const constraint = simplified !== (<IndexType>target).type ? simplified : getConstraintOfType((<IndexType>target).type);
12431-
if (constraint) {
12432-
// We require Ternary.True here such that circular constraints don't cause
12433-
// false positives. For example, given 'T extends { [K in keyof T]: string }',
12434-
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
12435-
// related to other types.
12436-
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) {
12437-
return Ternary.True;
12438-
}
12437+
const simplified = getSimplifiedType((<IndexType>target).type);
12438+
const constraint = simplified !== (<IndexType>target).type ? simplified : getConstraintOfType((<IndexType>target).type);
12439+
if (constraint) {
12440+
// We require Ternary.True here such that circular constraints don't cause
12441+
// false positives. For example, given 'T extends { [K in keyof T]: string }',
12442+
// 'keyof T' has itself as its constraint and produces a Ternary.Maybe when
12443+
// related to other types.
12444+
if (isRelatedTo(source, getIndexType(constraint, (target as IndexType).stringsOnly), reportErrors) === Ternary.True) {
12445+
return Ternary.True;
1243912446
}
1244012447
}
1244112448
}
1244212449
else if (target.flags & TypeFlags.IndexedAccess) {
1244312450
// A type S is related to a type T[K], where T and K aren't both type variables, if S is related to C,
1244412451
// where C is the base constraint of T[K]
12445-
if (relation !== identityRelation && relation !== definitelyAssignableRelation &&
12452+
if (relation !== identityRelation &&
1244612453
!(isGenericObjectType((<IndexedAccessType>target).objectType) && isGenericIndexType((<IndexedAccessType>target).indexType))) {
1244712454
const constraint = getBaseConstraintOfType(target);
1244812455
if (constraint && constraint !== target) {
@@ -12485,26 +12492,24 @@ namespace ts {
1248512492
return result;
1248612493
}
1248712494
}
12488-
if (relation !== definitelyAssignableRelation) {
12489-
const constraint = getConstraintOfType(<TypeParameter>source);
12490-
if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
12491-
// A type variable with no constraint is not related to the non-primitive object type.
12492-
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) {
12493-
errorInfo = saveErrorInfo;
12494-
return result;
12495-
}
12496-
}
12497-
// hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed
12498-
else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) {
12499-
errorInfo = saveErrorInfo;
12500-
return result;
12501-
}
12502-
// slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example
12503-
else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent)) {
12495+
const constraint = getConstraintOfType(<TypeParameter>source);
12496+
if (!constraint || (source.flags & TypeFlags.TypeParameter && constraint.flags & TypeFlags.Any)) {
12497+
// A type variable with no constraint is not related to the non-primitive object type.
12498+
if (result = isRelatedTo(emptyObjectType, extractTypesOfKind(target, ~TypeFlags.NonPrimitive))) {
1250412499
errorInfo = saveErrorInfo;
1250512500
return result;
1250612501
}
1250712502
}
12503+
// hi-speed no-this-instantiation check (less accurate, but avoids costly `this`-instantiation when the constraint will suffice), see #28231 for report on why this is needed
12504+
else if (result = isRelatedTo(constraint, target, /*reportErrors*/ false, /*headMessage*/ undefined, isIntersectionConstituent)) {
12505+
errorInfo = saveErrorInfo;
12506+
return result;
12507+
}
12508+
// slower, fuller, this-instantiated check (necessary when comparing raw `this` types from base classes), see `subclassWithPolymorphicThisIsAssignable.ts` test for example
12509+
else if (result = isRelatedTo(getTypeWithThisArgument(constraint, source), target, reportErrors, /*headMessage*/ undefined, isIntersectionConstituent)) {
12510+
errorInfo = saveErrorInfo;
12511+
return result;
12512+
}
1250812513
}
1250912514
else if (source.flags & TypeFlags.Index) {
1251012515
if (result = isRelatedTo(keyofConstraintType, target, reportErrors)) {
@@ -12528,7 +12533,7 @@ namespace ts {
1252812533
}
1252912534
}
1253012535
}
12531-
else if (relation !== definitelyAssignableRelation) {
12536+
else {
1253212537
const distributiveConstraint = getConstraintOfDistributiveConditionalType(<ConditionalType>source);
1253312538
if (distributiveConstraint) {
1253412539
if (result = isRelatedTo(distributiveConstraint, target, reportErrors)) {
@@ -12559,9 +12564,6 @@ namespace ts {
1255912564
}
1256012565
return Ternary.False;
1256112566
}
12562-
if (relation === definitelyAssignableRelation && isGenericMappedType(source)) {
12563-
return Ternary.False;
12564-
}
1256512567
const sourceIsPrimitive = !!(source.flags & TypeFlags.Primitive);
1256612568
if (relation !== identityRelation) {
1256712569
source = getApparentType(source);

0 commit comments

Comments
 (0)