@@ -4550,6 +4550,10 @@ namespace ts {
45504550 unknownType);
45514551 }
45524552
4553+ function getErasedTemplateTypeFromMappedType(type: MappedType) {
4554+ return instantiateType(getTemplateTypeFromMappedType(type), createUnaryTypeMapper(getTypeParameterFromMappedType(type), anyType));
4555+ }
4556+
45534557 function isGenericMappedType(type: Type) {
45544558 if (getObjectFlags(type) & ObjectFlags.Mapped) {
45554559 const constraintType = getConstraintTypeFromMappedType(<MappedType>type);
@@ -7190,29 +7194,18 @@ namespace ts {
71907194 return result;
71917195 }
71927196 }
7193- if (isGenericMappedType(target)) {
7194- // A type [P in S]: X is related to a type [P in T]: Y if T is related to S and X is related to Y.
7195- if (isGenericMappedType(source)) {
7196- if ((result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) &&
7197- (result = isRelatedTo(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target), reportErrors))) {
7198- return result;
7199- }
7200- }
7201- }
7202- else {
7203- // Even if relationship doesn't hold for unions, intersections, or generic type references,
7204- // it may hold in a structural comparison.
7205- const apparentSource = getApparentType(source);
7206- // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
7207- // to X. Failing both of those we want to check if the aggregation of A and B's members structurally
7208- // relates to X. Thus, we include intersection types on the source side here.
7209- if (apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
7210- // Report structural errors only if we haven't reported any errors yet
7211- const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !(source.flags & TypeFlags.Primitive);
7212- if (result = objectTypeRelatedTo(apparentSource, source, target, reportStructuralErrors)) {
7213- errorInfo = saveErrorInfo;
7214- return result;
7215- }
7197+ // Even if relationship doesn't hold for unions, intersections, or generic type references,
7198+ // it may hold in a structural comparison.
7199+ const apparentSource = getApparentType(source);
7200+ // In a check of the form X = A & B, we will have previously checked if A relates to X or B relates
7201+ // to X. Failing both of those we want to check if the aggregation of A and B's members structurally
7202+ // relates to X. Thus, we include intersection types on the source side here.
7203+ if (apparentSource.flags & (TypeFlags.Object | TypeFlags.Intersection) && target.flags & TypeFlags.Object) {
7204+ // Report structural errors only if we haven't reported any errors yet
7205+ const reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo && !(source.flags & TypeFlags.Primitive);
7206+ if (result = objectTypeRelatedTo(apparentSource, source, target, reportStructuralErrors)) {
7207+ errorInfo = saveErrorInfo;
7208+ return result;
72167209 }
72177210 }
72187211 }
@@ -7441,6 +7434,9 @@ namespace ts {
74417434 if (expandingFlags === 3) {
74427435 result = Ternary.Maybe;
74437436 }
7437+ else if (isGenericMappedType(source) || isGenericMappedType(target)) {
7438+ result = mappedTypeRelatedTo(source, target, reportErrors);
7439+ }
74447440 else {
74457441 result = propertiesRelatedTo(source, target, reportErrors);
74467442 if (result) {
@@ -7472,6 +7468,30 @@ namespace ts {
74727468 return result;
74737469 }
74747470
7471+ // A type [P in S]: X is related to a type [P in T]: Y if T is related to S and X is related to Y.
7472+ function mappedTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
7473+ if (isGenericMappedType(source) && isGenericMappedType(target)) {
7474+ let result: Ternary;
7475+ if (relation === identityRelation) {
7476+ const readonlyMatches = !(<MappedType>source).declaration.readonlyToken === !(<MappedType>target).declaration.readonlyToken;
7477+ const optionalMatches = !(<MappedType>source).declaration.questionToken === !(<MappedType>target).declaration.questionToken;
7478+ if (readonlyMatches && optionalMatches) {
7479+ if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
7480+ return result & isRelatedTo(getErasedTemplateTypeFromMappedType(<MappedType>source), getErasedTemplateTypeFromMappedType(<MappedType>target), reportErrors);
7481+ }
7482+ }
7483+ }
7484+ else {
7485+ if (relation === comparableRelation || !(<MappedType>source).declaration.questionToken || (<MappedType>target).declaration.questionToken) {
7486+ if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
7487+ return result & isRelatedTo(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target), reportErrors);
7488+ }
7489+ }
7490+ }
7491+ }
7492+ return Ternary.False;
7493+ }
7494+
74757495 function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
74767496 if (relation === identityRelation) {
74777497 return propertiesIdenticalTo(source, target);
@@ -9721,20 +9741,20 @@ namespace ts {
97219741 }
97229742
97239743 if (targetType) {
9724- return getNarrowedType(type, targetType, assumeTrue);
9744+ return getNarrowedType(type, targetType, assumeTrue, isTypeInstanceOf );
97259745 }
97269746
97279747 return type;
97289748 }
97299749
9730- function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean) {
9750+ function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean ) {
97319751 if (!assumeTrue) {
9732- return filterType(type, t => !isTypeInstanceOf (t, candidate));
9752+ return filterType(type, t => !isRelated (t, candidate));
97339753 }
97349754 // If the current type is a union type, remove all constituents that couldn't be instances of
97359755 // the candidate type. If one or more constituents remain, return a union of those.
97369756 if (type.flags & TypeFlags.Union) {
9737- const assignableType = filterType(type, t => isTypeInstanceOf (t, candidate));
9757+ const assignableType = filterType(type, t => isRelated (t, candidate));
97389758 if (!(assignableType.flags & TypeFlags.Never)) {
97399759 return assignableType;
97409760 }
@@ -9770,7 +9790,7 @@ namespace ts {
97709790 const predicateArgument = callExpression.arguments[predicate.parameterIndex];
97719791 if (predicateArgument) {
97729792 if (isMatchingReference(reference, predicateArgument)) {
9773- return getNarrowedType(type, predicate.type, assumeTrue);
9793+ return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf );
97749794 }
97759795 if (containsMatchingReference(reference, predicateArgument)) {
97769796 return declaredType;
@@ -9783,7 +9803,7 @@ namespace ts {
97839803 const accessExpression = invokedExpression as ElementAccessExpression | PropertyAccessExpression;
97849804 const possibleReference = skipParentheses(accessExpression.expression);
97859805 if (isMatchingReference(reference, possibleReference)) {
9786- return getNarrowedType(type, predicate.type, assumeTrue);
9806+ return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf );
97879807 }
97889808 if (containsMatchingReference(reference, possibleReference)) {
97899809 return declaredType;
0 commit comments