Skip to content

Commit 37fa47e

Browse files
authored
Distribute mapped types over array/tuple intersections (#57801)
1 parent eed3234 commit 37fa47e

File tree

4 files changed

+152
-24
lines changed

4 files changed

+152
-24
lines changed

src/compiler/checker.ts

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14571,14 +14571,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1457114571
const constraint = getConstraintTypeFromMappedType(type);
1457214572
if (constraint.flags & TypeFlags.Index) {
1457314573
const baseConstraint = getBaseConstraintOfType((constraint as IndexType).type);
14574-
if (baseConstraint && everyType(baseConstraint, isArrayOrTupleType)) {
14574+
if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) {
1457514575
return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper));
1457614576
}
1457714577
}
1457814578
}
1457914579
return type;
1458014580
}
1458114581

14582+
function isArrayOrTupleOrIntersection(type: Type) {
14583+
return !!(type.flags & TypeFlags.Intersection) && every((type as IntersectionType).types, isArrayOrTupleType);
14584+
}
14585+
1458214586
function isMappedTypeGenericIndexedAccess(type: Type) {
1458314587
let objectType;
1458414588
return !!(type.flags & TypeFlags.IndexedAccess && getObjectFlags(objectType = (type as IndexedAccessType).objectType) & ObjectFlags.Mapped &&
@@ -19796,6 +19800,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1979619800
// * If T is a union type we distribute the mapped type over the union.
1979719801
// * If T is an array we map to an array where the element type has been transformed.
1979819802
// * If T is a tuple we map to a tuple where the element types have been transformed.
19803+
// * If T is an intersection of array or tuple types we map to an intersection of transformed array or tuple types.
1979919804
// * Otherwise we map to an object type where the type of each property has been transformed.
1980019805
// For example, when T is instantiated to a union type A | B, we produce { [P in keyof A]: X } |
1980119806
// { [P in keyof B]: X }, and when when T is instantiated to a union type A | undefined, we produce
@@ -19804,33 +19809,33 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1980419809
if (typeVariable) {
1980519810
const mappedTypeVariable = instantiateType(typeVariable, mapper);
1980619811
if (typeVariable !== mappedTypeVariable) {
19807-
return mapTypeWithAlias(
19808-
getReducedType(mappedTypeVariable),
19809-
t => {
19810-
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) {
19811-
if (!type.declaration.nameType) {
19812-
let constraint;
19813-
if (
19814-
isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 &&
19815-
(constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, isArrayOrTupleType)
19816-
) {
19817-
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
19818-
}
19819-
if (isTupleType(t)) {
19820-
return instantiateMappedTupleType(t, type, typeVariable, mapper);
19821-
}
19822-
}
19823-
return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper));
19824-
}
19825-
return t;
19826-
},
19827-
aliasSymbol,
19828-
aliasTypeArguments,
19829-
);
19812+
return mapTypeWithAlias(getReducedType(mappedTypeVariable), instantiateConstituent, aliasSymbol, aliasTypeArguments);
1983019813
}
1983119814
}
1983219815
// If the constraint type of the instantiation is the wildcard type, return the wildcard type.
1983319816
return instantiateType(getConstraintTypeFromMappedType(type), mapper) === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper, aliasSymbol, aliasTypeArguments);
19817+
19818+
function instantiateConstituent(t: Type): Type {
19819+
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) {
19820+
if (!type.declaration.nameType) {
19821+
let constraint;
19822+
if (
19823+
isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable!, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 &&
19824+
(constraint = getConstraintOfTypeParameter(typeVariable!)) && everyType(constraint, isArrayOrTupleType)
19825+
) {
19826+
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable!, t, mapper));
19827+
}
19828+
if (isTupleType(t)) {
19829+
return instantiateMappedTupleType(t, type, typeVariable!, mapper);
19830+
}
19831+
if (isArrayOrTupleOrIntersection(t)) {
19832+
return getIntersectionType(map((t as IntersectionType).types, instantiateConstituent));
19833+
}
19834+
}
19835+
return instantiateAnonymousType(type, prependTypeMapping(typeVariable!, t, mapper));
19836+
}
19837+
return t;
19838+
}
1983419839
}
1983519840

1983619841
function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//// [tests/cases/compiler/mappedArrayTupleIntersections.ts] ////
2+
3+
=== mappedArrayTupleIntersections.ts ===
4+
type Box<T> = { value: T };
5+
>Box : Symbol(Box, Decl(mappedArrayTupleIntersections.ts, 0, 0))
6+
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 0, 9))
7+
>value : Symbol(value, Decl(mappedArrayTupleIntersections.ts, 0, 15))
8+
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 0, 9))
9+
10+
type Boxify<T> = { [K in keyof T]: Box<T[K]> };
11+
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))
12+
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 1, 12))
13+
>K : Symbol(K, Decl(mappedArrayTupleIntersections.ts, 1, 20))
14+
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 1, 12))
15+
>Box : Symbol(Box, Decl(mappedArrayTupleIntersections.ts, 0, 0))
16+
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 1, 12))
17+
>K : Symbol(K, Decl(mappedArrayTupleIntersections.ts, 1, 20))
18+
19+
type T1 = Boxify<string[]>;
20+
>T1 : Symbol(T1, Decl(mappedArrayTupleIntersections.ts, 1, 47))
21+
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))
22+
23+
type T2 = Boxify<[string, string]>;
24+
>T2 : Symbol(T2, Decl(mappedArrayTupleIntersections.ts, 3, 27))
25+
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))
26+
27+
type T3 = Boxify<string[] & unknown[]>;
28+
>T3 : Symbol(T3, Decl(mappedArrayTupleIntersections.ts, 4, 35))
29+
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))
30+
31+
type T4 = Boxify<string[] & [string, string]>;
32+
>T4 : Symbol(T4, Decl(mappedArrayTupleIntersections.ts, 5, 39))
33+
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))
34+
35+
type T5 = Boxify<string[] & { x: string }>;
36+
>T5 : Symbol(T5, Decl(mappedArrayTupleIntersections.ts, 6, 46))
37+
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))
38+
>x : Symbol(x, Decl(mappedArrayTupleIntersections.ts, 7, 29))
39+
40+
// https://github.com/microsoft/TypeScript/issues/57744
41+
42+
type MustBeArray<T extends any[]> = T;
43+
>MustBeArray : Symbol(MustBeArray, Decl(mappedArrayTupleIntersections.ts, 7, 43))
44+
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 11, 17))
45+
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 11, 17))
46+
47+
type Hmm<T extends any[]> = T extends number[] ?
48+
>Hmm : Symbol(Hmm, Decl(mappedArrayTupleIntersections.ts, 11, 38))
49+
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 13, 9))
50+
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 13, 9))
51+
52+
MustBeArray<{ [I in keyof T]: 1 }> :
53+
>MustBeArray : Symbol(MustBeArray, Decl(mappedArrayTupleIntersections.ts, 7, 43))
54+
>I : Symbol(I, Decl(mappedArrayTupleIntersections.ts, 14, 19))
55+
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 13, 9))
56+
57+
never;
58+
59+
type X = Hmm<[3, 4, 5]>;
60+
>X : Symbol(X, Decl(mappedArrayTupleIntersections.ts, 15, 10))
61+
>Hmm : Symbol(Hmm, Decl(mappedArrayTupleIntersections.ts, 11, 38))
62+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//// [tests/cases/compiler/mappedArrayTupleIntersections.ts] ////
2+
3+
=== mappedArrayTupleIntersections.ts ===
4+
type Box<T> = { value: T };
5+
>Box : Box<T>
6+
>value : T
7+
8+
type Boxify<T> = { [K in keyof T]: Box<T[K]> };
9+
>Boxify : Boxify<T>
10+
11+
type T1 = Boxify<string[]>;
12+
>T1 : Box<string>[]
13+
14+
type T2 = Boxify<[string, string]>;
15+
>T2 : [Box<string>, Box<string>]
16+
17+
type T3 = Boxify<string[] & unknown[]>;
18+
>T3 : Box<string>[] & Box<unknown>[]
19+
20+
type T4 = Boxify<string[] & [string, string]>;
21+
>T4 : Box<string>[] & [Box<string>, Box<string>]
22+
23+
type T5 = Boxify<string[] & { x: string }>;
24+
>T5 : Boxify<string[] & { x: string; }>
25+
>x : string
26+
27+
// https://github.com/microsoft/TypeScript/issues/57744
28+
29+
type MustBeArray<T extends any[]> = T;
30+
>MustBeArray : T
31+
32+
type Hmm<T extends any[]> = T extends number[] ?
33+
>Hmm : Hmm<T>
34+
35+
MustBeArray<{ [I in keyof T]: 1 }> :
36+
never;
37+
38+
type X = Hmm<[3, 4, 5]>;
39+
>X : [1, 1, 1]
40+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
type Box<T> = { value: T };
5+
type Boxify<T> = { [K in keyof T]: Box<T[K]> };
6+
7+
type T1 = Boxify<string[]>;
8+
type T2 = Boxify<[string, string]>;
9+
type T3 = Boxify<string[] & unknown[]>;
10+
type T4 = Boxify<string[] & [string, string]>;
11+
type T5 = Boxify<string[] & { x: string }>;
12+
13+
// https://github.com/microsoft/TypeScript/issues/57744
14+
15+
type MustBeArray<T extends any[]> = T;
16+
17+
type Hmm<T extends any[]> = T extends number[] ?
18+
MustBeArray<{ [I in keyof T]: 1 }> :
19+
never;
20+
21+
type X = Hmm<[3, 4, 5]>;

0 commit comments

Comments
 (0)