Skip to content

Commit 4bc7f15

Browse files
authored
Merge pull request #26063 from Microsoft/mappedTypesArraysTuples
Improved mapped type support for arrays and tuples
2 parents 03c7a19 + efb6e0b commit 4bc7f15

File tree

6 files changed

+773
-7
lines changed

6 files changed

+773
-7
lines changed

src/compiler/checker.ts

+59-6
Original file line numberDiff line numberDiff line change
@@ -8436,6 +8436,10 @@ namespace ts {
84368436
return createTypeFromGenericGlobalType(globalArrayType, [elementType]);
84378437
}
84388438

8439+
function createReadonlyArrayType(elementType: Type): ObjectType {
8440+
return createTypeFromGenericGlobalType(globalReadonlyArrayType, [elementType]);
8441+
}
8442+
84398443
function getTypeFromArrayTypeNode(node: ArrayTypeNode): Type {
84408444
const links = getNodeLinks(node);
84418445
if (!links.resolvedType) {
@@ -10091,11 +10095,16 @@ namespace ts {
1009110095
}
1009210096

1009310097
function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type {
10094-
// Check if we have a homomorphic mapped type, i.e. a type of the form { [P in keyof T]: X } for some
10095-
// type variable T. If so, the mapped type is distributive over a union type and when T is instantiated
10096-
// to a union type A | B, we produce { [P in keyof A]: X } | { [P in keyof B]: X }. Furthermore, for
10097-
// homomorphic mapped types we leave primitive types alone. For example, when T is instantiated to a
10098-
// union type A | undefined, we produce { [P in keyof A]: X } | undefined.
10098+
// For a momomorphic mapped type { [P in keyof T]: X }, where T is some type variable, the mapping
10099+
// operation depends on T as follows:
10100+
// * If T is a primitive type no mapping is performed and the result is simply T.
10101+
// * If T is a union type we distribute the mapped type over the union.
10102+
// * If T is an array we map to an array where the element type has been transformed.
10103+
// * If T is a tuple we map to a tuple where the element types have been transformed.
10104+
// * Otherwise we map to an object type where the type of each property has been transformed.
10105+
// For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } |
10106+
// { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce
10107+
// { [P in keyof A]: X } | undefined.
1009910108
const constraintType = getConstraintTypeFromMappedType(type);
1010010109
if (constraintType.flags & TypeFlags.Index) {
1010110110
const typeVariable = (<IndexType>constraintType).type;
@@ -10104,7 +10113,11 @@ namespace ts {
1010410113
if (typeVariable !== mappedTypeVariable) {
1010510114
return mapType(mappedTypeVariable, t => {
1010610115
if (isMappableType(t)) {
10107-
return instantiateAnonymousType(type, createReplacementMapper(typeVariable, t, mapper));
10116+
const replacementMapper = createReplacementMapper(typeVariable, t, mapper);
10117+
return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
10118+
isReadonlyArrayType(t) ? createReadonlyArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) :
10119+
isTupleType(t) ? instantiateMappedTupleType(t, type, replacementMapper) :
10120+
instantiateAnonymousType(type, replacementMapper);
1010810121
}
1010910122
return t;
1011010123
});
@@ -10118,6 +10131,26 @@ namespace ts {
1011810131
return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection);
1011910132
}
1012010133

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

12481+
function isReadonlyArrayType(type: Type): boolean {
12482+
return !!(getObjectFlags(type) & ObjectFlags.Reference) && (<TypeReference>type).target === globalReadonlyArrayType;
12483+
}
12484+
1244812485
function isArrayLikeType(type: Type): boolean {
1244912486
// A type is array-like if it is a reference to the global Array or global ReadonlyArray type,
1245012487
// or if it is not the undefined or null type and if it is assignable to ReadonlyArray<any>
@@ -13000,6 +13037,22 @@ namespace ts {
1300013037
return undefined;
1300113038
}
1300213039
}
13040+
// For arrays and tuples we infer new arrays and tuples where the reverse mapping has been
13041+
// applied to the element type(s).
13042+
if (isArrayType(source)) {
13043+
return createArrayType(inferReverseMappedType((<TypeReference>source).typeArguments![0], target));
13044+
}
13045+
if (isReadonlyArrayType(source)) {
13046+
return createReadonlyArrayType(inferReverseMappedType((<TypeReference>source).typeArguments![0], target));
13047+
}
13048+
if (isTupleType(source)) {
13049+
const elementTypes = map(source.typeArguments || emptyArray, t => inferReverseMappedType(t, target));
13050+
const minLength = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
13051+
getTypeReferenceArity(source) - (source.target.hasRestElement ? 1 : 0) : source.target.minLength;
13052+
return createTupleType(elementTypes, minLength, source.target.hasRestElement, source.target.associatedNames);
13053+
}
13054+
// For all other object types we infer a new object type where the reverse mapping has been
13055+
// applied to the type of each property.
1300313056
const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType;
1300413057
reversed.source = source;
1300513058
reversed.mappedType = target;

tests/baselines/reference/mappedTypes4.types

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ type T01 = Readonly<A | B | C | null | undefined>;
103103
>null : null
104104

105105
type T02 = Boxified<A | B[] | C | string>
106-
>T02 : string | Boxified<A> | Boxified<C> | Boxified<B[]>
106+
>T02 : string | Boxified<A> | Boxified<C> | Box<B>[]
107107
>Boxified : Boxified<T>
108108
>A : A
109109
>B : B
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//// [mappedTypesArraysTuples.ts]
2+
type Box<T> = { value: T };
3+
type Boxified<T> = { [P in keyof T]: Box<T[P]> };
4+
5+
type T00 = Boxified<[number, string?, ...boolean[]]>;
6+
type T01 = Partial<[number, string?, ...boolean[]]>;
7+
type T02 = Required<[number, string?, ...boolean[]]>;
8+
9+
type T10 = Boxified<string[]>;
10+
type T11 = Partial<string[]>;
11+
type T12 = Required<string[]>;
12+
type T13 = Boxified<ReadonlyArray<string>>;
13+
type T14 = Partial<ReadonlyArray<string>>;
14+
type T15 = Required<ReadonlyArray<string>>;
15+
16+
type T20 = Boxified<(string | undefined)[]>;
17+
type T21 = Partial<(string | undefined)[]>;
18+
type T22 = Required<(string | undefined)[]>;
19+
type T23 = Boxified<ReadonlyArray<string | undefined>>;
20+
type T24 = Partial<ReadonlyArray<string | undefined>>;
21+
type T25 = Required<ReadonlyArray<string | undefined>>;
22+
23+
type T30 = Boxified<Partial<string[]>>;
24+
type T31 = Partial<Boxified<string[]>>;
25+
26+
type A = { a: string };
27+
type B = { b: string };
28+
29+
type T40 = Boxified<A | A[] | ReadonlyArray<A> | [A, B] | string | string[]>;
30+
31+
declare function unboxify<T>(x: Boxified<T>): T;
32+
33+
declare let x10: [Box<number>, Box<string>, ...Box<boolean>[]];
34+
let y10 = unboxify(x10);
35+
36+
declare let x11: Box<number>[];
37+
let y11 = unboxify(x11);
38+
39+
declare let x12: { a: Box<number>, b: Box<string[]> };
40+
let y12 = unboxify(x12);
41+
42+
declare function nonpartial<T>(x: Partial<T>): T;
43+
44+
declare let x20: [number | undefined, string?, ...boolean[]];
45+
let y20 = nonpartial(x20);
46+
47+
declare let x21: (number | undefined)[];
48+
let y21 = nonpartial(x21);
49+
50+
declare let x22: { a: number | undefined, b?: string[] };
51+
let y22 = nonpartial(x22);
52+
53+
type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
54+
type Awaitified<T> = { [P in keyof T]: Awaited<T[P]> };
55+
56+
declare function all<T extends any[]>(...values: T): Promise<Awaitified<T>>;
57+
58+
function f1(a: number, b: Promise<number>, c: string[], d: Promise<string[]>) {
59+
let x1 = all(a);
60+
let x2 = all(a, b);
61+
let x3 = all(a, b, c);
62+
let x4 = all(a, b, c, d);
63+
}
64+
65+
66+
//// [mappedTypesArraysTuples.js]
67+
"use strict";
68+
var y10 = unboxify(x10);
69+
var y11 = unboxify(x11);
70+
var y12 = unboxify(x12);
71+
var y20 = nonpartial(x20);
72+
var y21 = nonpartial(x21);
73+
var y22 = nonpartial(x22);
74+
function f1(a, b, c, d) {
75+
var x1 = all(a);
76+
var x2 = all(a, b);
77+
var x3 = all(a, b, c);
78+
var x4 = all(a, b, c, d);
79+
}
80+
81+
82+
//// [mappedTypesArraysTuples.d.ts]
83+
declare type Box<T> = {
84+
value: T;
85+
};
86+
declare type Boxified<T> = {
87+
[P in keyof T]: Box<T[P]>;
88+
};
89+
declare type T00 = Boxified<[number, string?, ...boolean[]]>;
90+
declare type T01 = Partial<[number, string?, ...boolean[]]>;
91+
declare type T02 = Required<[number, string?, ...boolean[]]>;
92+
declare type T10 = Boxified<string[]>;
93+
declare type T11 = Partial<string[]>;
94+
declare type T12 = Required<string[]>;
95+
declare type T13 = Boxified<ReadonlyArray<string>>;
96+
declare type T14 = Partial<ReadonlyArray<string>>;
97+
declare type T15 = Required<ReadonlyArray<string>>;
98+
declare type T20 = Boxified<(string | undefined)[]>;
99+
declare type T21 = Partial<(string | undefined)[]>;
100+
declare type T22 = Required<(string | undefined)[]>;
101+
declare type T23 = Boxified<ReadonlyArray<string | undefined>>;
102+
declare type T24 = Partial<ReadonlyArray<string | undefined>>;
103+
declare type T25 = Required<ReadonlyArray<string | undefined>>;
104+
declare type T30 = Boxified<Partial<string[]>>;
105+
declare type T31 = Partial<Boxified<string[]>>;
106+
declare type A = {
107+
a: string;
108+
};
109+
declare type B = {
110+
b: string;
111+
};
112+
declare type T40 = Boxified<A | A[] | ReadonlyArray<A> | [A, B] | string | string[]>;
113+
declare function unboxify<T>(x: Boxified<T>): T;
114+
declare let x10: [Box<number>, Box<string>, ...Box<boolean>[]];
115+
declare let y10: [number, string, ...boolean[]];
116+
declare let x11: Box<number>[];
117+
declare let y11: number[];
118+
declare let x12: {
119+
a: Box<number>;
120+
b: Box<string[]>;
121+
};
122+
declare let y12: {
123+
a: number;
124+
b: string[];
125+
};
126+
declare function nonpartial<T>(x: Partial<T>): T;
127+
declare let x20: [number | undefined, string?, ...boolean[]];
128+
declare let y20: [number, string, ...boolean[]];
129+
declare let x21: (number | undefined)[];
130+
declare let y21: number[];
131+
declare let x22: {
132+
a: number | undefined;
133+
b?: string[];
134+
};
135+
declare let y22: {
136+
a: number;
137+
b: string[];
138+
};
139+
declare type Awaited<T> = T extends PromiseLike<infer U> ? U : T;
140+
declare type Awaitified<T> = {
141+
[P in keyof T]: Awaited<T[P]>;
142+
};
143+
declare function all<T extends any[]>(...values: T): Promise<Awaitified<T>>;
144+
declare function f1(a: number, b: Promise<number>, c: string[], d: Promise<string[]>): void;

0 commit comments

Comments
 (0)