Skip to content

Commit fe3f88c

Browse files
committed
Properly handle identity relation for mapped types
1 parent ddf2ad4 commit fe3f88c

File tree

1 file changed

+43
-23
lines changed

1 file changed

+43
-23
lines changed

src/compiler/checker.ts

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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);

0 commit comments

Comments
 (0)