mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-14 19:16:17 -06:00
Homomorphic mapped type support for arrays and tuples
This commit is contained in:
parent
3bfe91cdd8
commit
8eb24db0c0
@ -8432,6 +8432,10 @@ namespace ts {
|
||||
return createTypeFromGenericGlobalType(globalArrayType, [elementType]);
|
||||
}
|
||||
|
||||
function createReadonlyArrayType(elementType: Type): ObjectType {
|
||||
return createTypeFromGenericGlobalType(globalReadonlyArrayType, [elementType]);
|
||||
}
|
||||
|
||||
function getTypeFromArrayTypeNode(node: ArrayTypeNode): Type {
|
||||
const links = getNodeLinks(node);
|
||||
if (!links.resolvedType) {
|
||||
@ -10087,11 +10091,16 @@ namespace ts {
|
||||
}
|
||||
|
||||
function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type {
|
||||
// Check if we have a homomorphic mapped type, i.e. a type of the form { [P in keyof T]: X } for some
|
||||
// type variable T. If so, the mapped type is distributive over a union type and when T is instantiated
|
||||
// to a union type A | B, we produce { [P in keyof A]: X } | { [P in keyof B]: X }. Furthermore, for
|
||||
// homomorphic mapped types we leave primitive types alone. For example, when T is instantiated to a
|
||||
// union type A | undefined, we produce { [P in keyof A]: X } | undefined.
|
||||
// For a momomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping
|
||||
// operation depends on T as follows:
|
||||
// * If T is a primitive type no mapping is performed and the result is simply T.
|
||||
// * If T is a union type we distribute the mapped type over the union.
|
||||
// * If T is an array we map to an array where the element type has been transformed.
|
||||
// * If T is a tuple we map to a tuple where the element types have been transformed.
|
||||
// * Otherwise we map to an object type where the type of each property has been transformed.
|
||||
// For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } |
|
||||
// { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce
|
||||
// { [P in keyof A]: X } | undefined.
|
||||
const constraintType = getConstraintTypeFromMappedType(type);
|
||||
if (constraintType.flags & TypeFlags.Index) {
|
||||
const typeVariable = (<IndexType>constraintType).type;
|
||||
@ -10100,7 +10109,11 @@ namespace ts {
|
||||
if (typeVariable !== mappedTypeVariable) {
|
||||
return mapType(mappedTypeVariable, t => {
|
||||
if (isMappableType(t)) {
|
||||
return instantiateAnonymousType(type, createReplacementMapper(typeVariable, t, mapper));
|
||||
const replacementMapper = createReplacementMapper(typeVariable, t, mapper);
|
||||
return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
|
||||
isReadonlyArrayType(t) ? createReadonlyArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
|
||||
isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) :
|
||||
instantiateAnonymousType(type, replacementMapper);
|
||||
}
|
||||
return t;
|
||||
});
|
||||
@ -10114,6 +10127,26 @@ namespace ts {
|
||||
return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection);
|
||||
}
|
||||
|
||||
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
|
||||
const minLength = tupleType.target.minLength;
|
||||
const elementTypes = map(tupleType.typeArguments || emptyArray, (_, i) =>
|
||||
instantiateMappedTypeTemplate(mappedType, getLiteralType("" + i), i >= minLength, mapper));
|
||||
const modifiers = getMappedTypeModifiers(mappedType);
|
||||
const newMinLength = modifiers & MappedTypeModifiers.IncludeOptional ? 0 :
|
||||
modifiers & MappedTypeModifiers.ExcludeOptional ? getTypeReferenceArity(tupleType) - (tupleType.target.hasRestElement ? 1 : 0) :
|
||||
minLength;
|
||||
return createTupleType(elementTypes, newMinLength, tupleType.target.hasRestElement, tupleType.target.associatedNames);
|
||||
}
|
||||
|
||||
function instantiateMappedTypeTemplate(type: MappedType, key: Type, isOptional: boolean, mapper: TypeMapper) {
|
||||
const templateMapper = combineTypeMappers(mapper, createTypeMapper([getTypeParameterFromMappedType(type)], [key]));
|
||||
const propType = instantiateType(getTemplateTypeFromMappedType(<MappedType>type.target || type), templateMapper);
|
||||
const modifiers = getMappedTypeModifiers(type);
|
||||
return strictNullChecks && modifiers & MappedTypeModifiers.IncludeOptional && !isTypeAssignableTo(undefinedType, propType) ? getOptionalType(propType) :
|
||||
strictNullChecks && modifiers & MappedTypeModifiers.ExcludeOptional && isOptional ? getTypeWithFacts(propType, TypeFacts.NEUndefined) :
|
||||
propType;
|
||||
}
|
||||
|
||||
function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType {
|
||||
const result = <AnonymousType>createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol);
|
||||
if (type.objectFlags & ObjectFlags.Mapped) {
|
||||
@ -12441,6 +12474,10 @@ namespace ts {
|
||||
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalArrayType;
|
||||
}
|
||||
|
||||
function isReadonlyArrayType(type: Type): boolean {
|
||||
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalReadonlyArrayType;
|
||||
}
|
||||
|
||||
function isArrayLikeType(type: Type): boolean {
|
||||
// A type is array-like if it is a reference to the global Array or global ReadonlyArray type,
|
||||
// or if it is not the undefined or null type and if it is assignable to ReadonlyArray<any>
|
||||
@ -12996,6 +13033,22 @@ namespace ts {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
|
||||
// applied to the element type(s).
|
||||
if (isArrayType(source)) {
|
||||
return createArrayType(inferReverseMappedType((<TypeReference>source).typeArguments![0], target));
|
||||
}
|
||||
if (isReadonlyArrayType(source)) {
|
||||
return createReadonlyArrayType(inferReverseMappedType((<TypeReference>source).typeArguments![0], target));
|
||||
}
|
||||
if (isTupleType(source)) {
|
||||
const elementTypes = map(source.typeArguments || emptyArray, t => inferReverseMappedType(t, target));
|
||||
const minLength = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
|
||||
getTypeReferenceArity(source) - (source.target.hasRestElement ? 1 : 0) : source.target.minLength;
|
||||
return createTupleType(elementTypes, minLength, source.target.hasRestElement, source.target.associatedNames);
|
||||
}
|
||||
// For all other object types we infer a new object type where the reverse mapping has been
|
||||
// applied to the type of each property.
|
||||
const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType;
|
||||
reversed.source = source;
|
||||
reversed.mappedType = target;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user