Skip to content

Commit

Permalink
Merge pull request #26063 from Microsoft/mappedTypesArraysTuples
Browse files Browse the repository at this point in the history
Improved mapped type support for arrays and tuples
  • Loading branch information
ahejlsberg committed Jul 31, 2018
2 parents 03c7a19 + efb6e0b commit 4bc7f15
Show file tree
Hide file tree
Showing 6 changed files with 773 additions and 7 deletions.
65 changes: 59 additions & 6 deletions src/compiler/checker.ts
Expand Up @@ -8436,6 +8436,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) {
Expand Down Expand Up @@ -10091,11 +10095,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;
Expand All @@ -10104,7 +10113,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;
});
Expand All @@ -10118,6 +10131,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) {
Expand Down Expand Up @@ -12445,6 +12478,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>
Expand Down Expand Up @@ -13000,6 +13037,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;
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/mappedTypes4.types
Expand Up @@ -103,7 +103,7 @@ type T01 = Readonly<A | B | C | null | undefined>;
>null : null

type T02 = Boxified<A | B[] | C | string>
>T02 : string | Boxified<A> | Boxified<C> | Boxified<B[]>
>T02 : string | Boxified<A> | Boxified<C> | Box<B>[]
>Boxified : Boxified<T>
>A : A
>B : B
Expand Down
144 changes: 144 additions & 0 deletions tests/baselines/reference/mappedTypesArraysTuples.js
@@ -0,0 +1,144 @@
//// [mappedTypesArraysTuples.ts]
type Box<T> = { value: T };
type Boxified<T> = { [P in keyof T]: Box<T[P]> };

type T00 = Boxified<[number, string?, ...boolean[]]>;
type T01 = Partial<[number, string?, ...boolean[]]>;
type T02 = Required<[number, string?, ...boolean[]]>;

type T10 = Boxified<string[]>;
type T11 = Partial<string[]>;
type T12 = Required<string[]>;
type T13 = Boxified<ReadonlyArray<string>>;
type T14 = Partial<ReadonlyArray<string>>;
type T15 = Required<ReadonlyArray<string>>;

type T20 = Boxified<(string | undefined)[]>;
type T21 = Partial<(string | undefined)[]>;
type T22 = Required<(string | undefined)[]>;
type T23 = Boxified<ReadonlyArray<string | undefined>>;
type T24 = Partial<ReadonlyArray<string | undefined>>;
type T25 = Required<ReadonlyArray<string | undefined>>;

type T30 = Boxified<Partial<string[]>>;
type T31 = Partial<Boxified<string[]>>;

type A = { a: string };
type B = { b: string };

type T40 = Boxified<A | A[] | ReadonlyArray<A> | [A, B] | string | string[]>;

declare function unboxify<T>(x: Boxified<T>): T;

declare let x10: [Box<number>, Box<string>, ...Box<boolean>[]];
let y10 = unboxify(x10);

declare let x11: Box<number>[];
let y11 = unboxify(x11);

declare let x12: { a: Box<number>, b: Box<string[]> };
let y12 = unboxify(x12);

declare function nonpartial<T>(x: Partial<T>): T;

declare let x20: [number | undefined, string?, ...boolean[]];
let y20 = nonpartial(x20);

declare let x21: (number | undefined)[];
let y21 = nonpartial(x21);

declare let x22: { a: number | undefined, b?: string[] };
let y22 = nonpartial(x22);

type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
type Awaitified<T> = { [P in keyof T]: Awaited<T[P]> };

declare function all<T extends any[]>(...values: T): Promise<Awaitified<T>>;

function f1(a: number, b: Promise<number>, c: string[], d: Promise<string[]>) {
let x1 = all(a);
let x2 = all(a, b);
let x3 = all(a, b, c);
let x4 = all(a, b, c, d);
}


//// [mappedTypesArraysTuples.js]
"use strict";
var y10 = unboxify(x10);
var y11 = unboxify(x11);
var y12 = unboxify(x12);
var y20 = nonpartial(x20);
var y21 = nonpartial(x21);
var y22 = nonpartial(x22);
function f1(a, b, c, d) {
var x1 = all(a);
var x2 = all(a, b);
var x3 = all(a, b, c);
var x4 = all(a, b, c, d);
}


//// [mappedTypesArraysTuples.d.ts]
declare type Box<T> = {
value: T;
};
declare type Boxified<T> = {
[P in keyof T]: Box<T[P]>;
};
declare type T00 = Boxified<[number, string?, ...boolean[]]>;
declare type T01 = Partial<[number, string?, ...boolean[]]>;
declare type T02 = Required<[number, string?, ...boolean[]]>;
declare type T10 = Boxified<string[]>;
declare type T11 = Partial<string[]>;
declare type T12 = Required<string[]>;
declare type T13 = Boxified<ReadonlyArray<string>>;
declare type T14 = Partial<ReadonlyArray<string>>;
declare type T15 = Required<ReadonlyArray<string>>;
declare type T20 = Boxified<(string | undefined)[]>;
declare type T21 = Partial<(string | undefined)[]>;
declare type T22 = Required<(string | undefined)[]>;
declare type T23 = Boxified<ReadonlyArray<string | undefined>>;
declare type T24 = Partial<ReadonlyArray<string | undefined>>;
declare type T25 = Required<ReadonlyArray<string | undefined>>;
declare type T30 = Boxified<Partial<string[]>>;
declare type T31 = Partial<Boxified<string[]>>;
declare type A = {
a: string;
};
declare type B = {
b: string;
};
declare type T40 = Boxified<A | A[] | ReadonlyArray<A> | [A, B] | string | string[]>;
declare function unboxify<T>(x: Boxified<T>): T;
declare let x10: [Box<number>, Box<string>, ...Box<boolean>[]];
declare let y10: [number, string, ...boolean[]];
declare let x11: Box<number>[];
declare let y11: number[];
declare let x12: {
a: Box<number>;
b: Box<string[]>;
};
declare let y12: {
a: number;
b: string[];
};
declare function nonpartial<T>(x: Partial<T>): T;
declare let x20: [number | undefined, string?, ...boolean[]];
declare let y20: [number, string, ...boolean[]];
declare let x21: (number | undefined)[];
declare let y21: number[];
declare let x22: {
a: number | undefined;
b?: string[];
};
declare let y22: {
a: number;
b: string[];
};
declare type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
declare type Awaitified<T> = {
[P in keyof T]: Awaited<T[P]>;
};
declare function all<T extends any[]>(...values: T): Promise<Awaitified<T>>;
declare function f1(a: number, b: Promise<number>, c: string[], d: Promise<string[]>): void;

0 comments on commit 4bc7f15

Please sign in to comment.