Skip to content

Commit 8eb24db

Browse files
committed
Homomorphic mapped type support for arrays and tuples
1 parent 3bfe91c commit 8eb24db

File tree

1 file changed

+59
-6
lines changed

1 file changed

+59
-6
lines changed

src/compiler/checker.ts

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8432,6 +8432,10 @@ namespace ts {
84328432
return createTypeFromGenericGlobalType(globalArrayType, [elementType]);
84338433
}
84348434

8435+
function createReadonlyArrayType(elementType: Type): ObjectType {
8436+
return createTypeFromGenericGlobalType(globalReadonlyArrayType, [elementType]);
8437+
}
8438+
84358439
function getTypeFromArrayTypeNode(node: ArrayTypeNode): Type {
84368440
const links = getNodeLinks(node);
84378441
if (!links.resolvedType) {
@@ -10087,11 +10091,16 @@ namespace ts {
1008710091
}
1008810092

1008910093
function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type {
10090-
// Check if we have a homomorphic mapped type, i.e. a type of the form { [P in keyof T]: X } for some
10091-
// type variable T. If so, the mapped type is distributive over a union type and when T is instantiated
10092-
// to a union type A | B, we produce { [P in keyof A]: X } | { [P in keyof B]: X }. Furthermore, for
10093-
// homomorphic mapped types we leave primitive types alone. For example, when T is instantiated to a
10094-
// union type A | undefined, we produce { [P in keyof A]: X } | undefined.
10094+
// For a momomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping
10095+
// operation depends on T as follows:
10096+
// * If T is a primitive type no mapping is performed and the result is simply T.
10097+
// * If T is a union type we distribute the mapped type over the union.
10098+
// * If T is an array we map to an array where the element type has been transformed.
10099+
// * If T is a tuple we map to a tuple where the element types have been transformed.
10100+
// * Otherwise we map to an object type where the type of each property has been transformed.
10101+
// For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } |
10102+
// { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce
10103+
// { [P in keyof A]: X } | undefined.
1009510104
const constraintType = getConstraintTypeFromMappedType(type);
1009610105
if (constraintType.flags & TypeFlags.Index) {
1009710106
const typeVariable = (<IndexType>constraintType).type;
@@ -10100,7 +10109,11 @@ namespace ts {
1010010109
if (typeVariable !== mappedTypeVariable) {
1010110110
return mapType(mappedTypeVariable, t => {
1010210111
if (isMappableType(t)) {
10103-
return instantiateAnonymousType(type, createReplacementMapper(typeVariable, t, mapper));
10112+
const replacementMapper = createReplacementMapper(typeVariable, t, mapper);
10113+
return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
10114+
isReadonlyArrayType(t) ? createReadonlyArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
10115+
isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) :
10116+
instantiateAnonymousType(type, replacementMapper);
1010410117
}
1010510118
return t;
1010610119
});
@@ -10114,6 +10127,26 @@ namespace ts {
1011410127
return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection);
1011510128
}
1011610129

10130+
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
10131+
const minLength = tupleType.target.minLength;
10132+
const elementTypes = map(tupleType.typeArguments || emptyArray, (_, i) =>
10133+
instantiateMappedTypeTemplate(mappedType, getLiteralType("" + i), i >= minLength, mapper));
10134+
const modifiers = getMappedTypeModifiers(mappedType);
10135+
const newMinLength = modifiers & MappedTypeModifiers.IncludeOptional ? 0 :
10136+
modifiers & MappedTypeModifiers.ExcludeOptional ? getTypeReferenceArity(tupleType) - (tupleType.target.hasRestElement ? 1 : 0) :
10137+
minLength;
10138+
return createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, tupleType.target.associatedNames);
10139+
}
10140+
10141+
function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) {
10142+
const templateMapper = combineTypeMappers(mapper, createTypeMapper([getTypeParameterFromMappedType(type)], [key]));
10143+
const propType = instantiateType(getTemplateTypeFromMappedType(<MappedType>type.target || type), templateMapper);
10144+
const modifiers = getMappedTypeModifiers(type);
10145+
return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !isTypeAssignableTo(undefinedType, propType) ? getOptionalType(propType) :
10146+
strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) :
10147+
propType;
10148+
}
10149+
1011710150
function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType {
1011810151
const result = <AnonymousType>createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol);
1011910152
if (type.objectFlags & ObjectFlags.Mapped) {
@@ -12441,6 +12474,10 @@ namespace ts {
1244112474
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalArrayType;
1244212475
}
1244312476

12477+
function isReadonlyArrayType(type: Type): boolean {
12478+
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalReadonlyArrayType;
12479+
}
12480+
1244412481
function isArrayLikeType(type: Type): boolean {
1244512482
// A type is array-like if it is a reference to the global Array or global ReadonlyArray type,
1244612483
// or if it is not the undefined or null type and if it is assignable to ReadonlyArray<any>
@@ -12996,6 +13033,22 @@ namespace ts {
1299613033
return undefined;
1299713034
}
1299813035
}
13036+
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
13037+
// applied to the element type(s).
13038+
if (isArrayType(source)) {
13039+
return createArrayType(inferReverseMappedType((<TypeReference>source).typeArguments![0], target));
13040+
}
13041+
if (isReadonlyArrayType(source)) {
13042+
return createReadonlyArrayType(inferReverseMappedType((<TypeReference>source).typeArguments![0], target));
13043+
}
13044+
if (isTupleType(source)) {
13045+
const elementTypes = map(source.typeArguments || emptyArray, t => inferReverseMappedType(t, target));
13046+
const minLength = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
13047+
getTypeReferenceArity(source) - (source.target.hasRestElement ? 1 : 0) : source.target.minLength;
13048+
return createTupleType(elementTypes, minLength, source.target.hasRestElement, source.target.associatedNames);
13049+
}
13050+
// For all other object types we infer a new object type where the reverse mapping has been
13051+
// applied to the type of each property.
1299913052
const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType;
1300013053
reversed.source = source;
1300113054
reversed.mappedType = target;

0 commit comments

Comments
 (0)