Skip to content

Commit d7dd028

Browse files
committed
Distribute isomorphic mapped types over union types
1 parent 2d16b19 commit d7dd028

File tree

1 file changed

+48
-14
lines changed

1 file changed

+48
-14
lines changed

src/compiler/checker.ts

Lines changed: 48 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6472,7 +6472,36 @@ namespace ts {
64726472
return result;
64736473
}
64746474

6475-
function instantiateMappedType(type: MappedType, mapper: TypeMapper): MappedType {
6475+
function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type {
6476+
// Check if we have an isomorphic mapped type, i.e. a type of the form { [P in keyof T]: X } for some
6477+
// type parameter T. If so, the mapped type is distributive over a union type and when T is instantiated
6478+
// to a union type A | B, we produce { [P in keyof A]: X } | { [P in keyof B]: X }. Furthermore, for
6479+
// isomorphic mapped types we leave primitive types alone. For example, when T is instantiated to a
6480+
// union type A | undefined, we produce { [P in keyof A]: X } | undefined.
6481+
const constraintType = getConstraintTypeFromMappedType(type);
6482+
if (constraintType.flags & TypeFlags.Index) {
6483+
const typeParameter = (<IndexType>constraintType).type;
6484+
const mappedTypeParameter = mapper(typeParameter);
6485+
if (typeParameter !== mappedTypeParameter) {
6486+
return mapType(mappedTypeParameter, t => {
6487+
if (isMappableType(t)) {
6488+
const replacementMapper = createUnaryTypeMapper(typeParameter, t);
6489+
const combinedMapper = mapper.mappedTypes && mapper.mappedTypes.length === 1 ? replacementMapper : combineTypeMappers(replacementMapper, mapper);
6490+
combinedMapper.mappedTypes = mapper.mappedTypes;
6491+
return instantiateMappedObjectType(type, combinedMapper);
6492+
}
6493+
return t;
6494+
});
6495+
}
6496+
}
6497+
return instantiateMappedObjectType(type, mapper);
6498+
}
6499+
6500+
function isMappableType(type: Type) {
6501+
return type.flags & (TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.Intersection | TypeFlags.IndexedAccess);
6502+
}
6503+
6504+
function instantiateMappedObjectType(type: MappedType, mapper: TypeMapper): Type {
64766505
const result = <MappedType>createObjectType(ObjectFlags.Mapped | ObjectFlags.Instantiated, type.symbol);
64776506
result.declaration = type.declaration;
64786507
result.mapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper;
@@ -7514,25 +7543,30 @@ namespace ts {
75147543

75157544
// 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.
75167545
function mappedTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary {
7517-
if (isGenericMappedType(source) && isGenericMappedType(target)) {
7518-
let result: Ternary;
7519-
if (relation === identityRelation) {
7520-
const readonlyMatches = !(<MappedType>source).declaration.readonlyToken === !(<MappedType>target).declaration.readonlyToken;
7521-
const optionalMatches = !(<MappedType>source).declaration.questionToken === !(<MappedType>target).declaration.questionToken;
7522-
if (readonlyMatches && optionalMatches) {
7523-
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
7524-
return result & isRelatedTo(getErasedTemplateTypeFromMappedType(<MappedType>source), getErasedTemplateTypeFromMappedType(<MappedType>target), reportErrors);
7546+
if (isGenericMappedType(target)) {
7547+
if (isGenericMappedType(source)) {
7548+
let result: Ternary;
7549+
if (relation === identityRelation) {
7550+
const readonlyMatches = !(<MappedType>source).declaration.readonlyToken === !(<MappedType>target).declaration.readonlyToken;
7551+
const optionalMatches = !(<MappedType>source).declaration.questionToken === !(<MappedType>target).declaration.questionToken;
7552+
if (readonlyMatches && optionalMatches) {
7553+
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
7554+
return result & isRelatedTo(getErasedTemplateTypeFromMappedType(<MappedType>source), getErasedTemplateTypeFromMappedType(<MappedType>target), reportErrors);
7555+
}
75257556
}
75267557
}
7527-
}
7528-
else {
7529-
if (relation === comparableRelation || !(<MappedType>source).declaration.questionToken || (<MappedType>target).declaration.questionToken) {
7530-
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
7531-
return result & isRelatedTo(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target), reportErrors);
7558+
else {
7559+
if (relation === comparableRelation || !(<MappedType>source).declaration.questionToken || (<MappedType>target).declaration.questionToken) {
7560+
if (result = isRelatedTo(getConstraintTypeFromMappedType(<MappedType>target), getConstraintTypeFromMappedType(<MappedType>source), reportErrors)) {
7561+
return result & isRelatedTo(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target), reportErrors);
7562+
}
75327563
}
75337564
}
75347565
}
75357566
}
7567+
else if (relation !== identityRelation && isEmptyObjectType(resolveStructuredTypeMembers(<ObjectType>target))) {
7568+
return Ternary.True;
7569+
}
75367570
return Ternary.False;
75377571
}
75387572

0 commit comments

Comments
 (0)