Skip to content

Commit

Permalink
🤖 Pick PR #57801 (Distribute mapped types over array/...) into releas…
Browse files Browse the repository at this point in the history
…e-5.4 (#57832)

Co-authored-by: Anders Hejlsberg <andersh@microsoft.com>
  • Loading branch information
TypeScript Bot and ahejlsberg authored Mar 18, 2024
1 parent 609560f commit b45a418
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 24 deletions.
53 changes: 29 additions & 24 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14557,14 +14557,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const constraint = getConstraintTypeFromMappedType(type);
if (constraint.flags & TypeFlags.Index) {
const baseConstraint = getBaseConstraintOfType((constraint as IndexType).type);
if (baseConstraint && everyType(baseConstraint, isArrayOrTupleType)) {
if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) {
return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper));
}
}
}
return type;
}

function isArrayOrTupleOrIntersection(type: Type) {
return !!(type.flags & TypeFlags.Intersection) && every((type as IntersectionType).types, isArrayOrTupleType);
}

function isMappedTypeGenericIndexedAccess(type: Type) {
let objectType;
return !!(type.flags & TypeFlags.IndexedAccess && getObjectFlags(objectType = (type as IndexedAccessType).objectType) & ObjectFlags.Mapped &&
Expand Down Expand Up @@ -19759,6 +19763,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// * 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.
// * If T is an intersection of array or tuple types we map to an intersection of transformed array or tuple types.
// * 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
Expand All @@ -19767,33 +19772,33 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (typeVariable) {
const mappedTypeVariable = instantiateType(typeVariable, mapper);
if (typeVariable !== mappedTypeVariable) {
return mapTypeWithAlias(
getReducedType(mappedTypeVariable),
t => {
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) {
if (!type.declaration.nameType) {
let constraint;
if (
isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 &&
(constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, isArrayOrTupleType)
) {
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper));
}
if (isTupleType(t)) {
return instantiateMappedTupleType(t, type, typeVariable, mapper);
}
}
return instantiateAnonymousType(type, prependTypeMapping(typeVariable, t, mapper));
}
return t;
},
aliasSymbol,
aliasTypeArguments,
);
return mapTypeWithAlias(getReducedType(mappedTypeVariable), instantiateConstituent, aliasSymbol, aliasTypeArguments);
}
}
// If the constraint type of the instantiation is the wildcard type, return the wildcard type.
return instantiateType(getConstraintTypeFromMappedType(type), mapper) === wildcardType ? wildcardType : instantiateAnonymousType(type, mapper, aliasSymbol, aliasTypeArguments);

function instantiateConstituent(t: Type): Type {
if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType && !isErrorType(t)) {
if (!type.declaration.nameType) {
let constraint;
if (
isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable!, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 &&
(constraint = getConstraintOfTypeParameter(typeVariable!)) && everyType(constraint, isArrayOrTupleType)
) {
return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable!, t, mapper));
}
if (isTupleType(t)) {
return instantiateMappedTupleType(t, type, typeVariable!, mapper);
}
if (isArrayOrTupleOrIntersection(t)) {
return getIntersectionType(map((t as IntersectionType).types, instantiateConstituent));
}
}
return instantiateAnonymousType(type, prependTypeMapping(typeVariable!, t, mapper));
}
return t;
}
}

function getModifiedReadonlyState(state: boolean, modifiers: MappedTypeModifiers) {
Expand Down
62 changes: 62 additions & 0 deletions tests/baselines/reference/mappedArrayTupleIntersections.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//// [tests/cases/compiler/mappedArrayTupleIntersections.ts] ////

=== mappedArrayTupleIntersections.ts ===
type Box<T> = { value: T };
>Box : Symbol(Box, Decl(mappedArrayTupleIntersections.ts, 0, 0))
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 0, 9))
>value : Symbol(value, Decl(mappedArrayTupleIntersections.ts, 0, 15))
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 0, 9))

type Boxify<T> = { [K in keyof T]: Box<T[K]> };
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 1, 12))
>K : Symbol(K, Decl(mappedArrayTupleIntersections.ts, 1, 20))
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 1, 12))
>Box : Symbol(Box, Decl(mappedArrayTupleIntersections.ts, 0, 0))
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 1, 12))
>K : Symbol(K, Decl(mappedArrayTupleIntersections.ts, 1, 20))

type T1 = Boxify<string[]>;
>T1 : Symbol(T1, Decl(mappedArrayTupleIntersections.ts, 1, 47))
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))

type T2 = Boxify<[string, string]>;
>T2 : Symbol(T2, Decl(mappedArrayTupleIntersections.ts, 3, 27))
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))

type T3 = Boxify<string[] & unknown[]>;
>T3 : Symbol(T3, Decl(mappedArrayTupleIntersections.ts, 4, 35))
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))

type T4 = Boxify<string[] & [string, string]>;
>T4 : Symbol(T4, Decl(mappedArrayTupleIntersections.ts, 5, 39))
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))

type T5 = Boxify<string[] & { x: string }>;
>T5 : Symbol(T5, Decl(mappedArrayTupleIntersections.ts, 6, 46))
>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27))
>x : Symbol(x, Decl(mappedArrayTupleIntersections.ts, 7, 29))

// https://github.com/microsoft/TypeScript/issues/57744

type MustBeArray<T extends any[]> = T;
>MustBeArray : Symbol(MustBeArray, Decl(mappedArrayTupleIntersections.ts, 7, 43))
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 11, 17))
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 11, 17))

type Hmm<T extends any[]> = T extends number[] ?
>Hmm : Symbol(Hmm, Decl(mappedArrayTupleIntersections.ts, 11, 38))
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 13, 9))
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 13, 9))

MustBeArray<{ [I in keyof T]: 1 }> :
>MustBeArray : Symbol(MustBeArray, Decl(mappedArrayTupleIntersections.ts, 7, 43))
>I : Symbol(I, Decl(mappedArrayTupleIntersections.ts, 14, 19))
>T : Symbol(T, Decl(mappedArrayTupleIntersections.ts, 13, 9))

never;

type X = Hmm<[3, 4, 5]>;
>X : Symbol(X, Decl(mappedArrayTupleIntersections.ts, 15, 10))
>Hmm : Symbol(Hmm, Decl(mappedArrayTupleIntersections.ts, 11, 38))

40 changes: 40 additions & 0 deletions tests/baselines/reference/mappedArrayTupleIntersections.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//// [tests/cases/compiler/mappedArrayTupleIntersections.ts] ////

=== mappedArrayTupleIntersections.ts ===
type Box<T> = { value: T };
>Box : Box<T>
>value : T

type Boxify<T> = { [K in keyof T]: Box<T[K]> };
>Boxify : Boxify<T>

type T1 = Boxify<string[]>;
>T1 : Box<string>[]

type T2 = Boxify<[string, string]>;
>T2 : [Box<string>, Box<string>]

type T3 = Boxify<string[] & unknown[]>;
>T3 : Box<string>[] & Box<unknown>[]

type T4 = Boxify<string[] & [string, string]>;
>T4 : Box<string>[] & [Box<string>, Box<string>]

type T5 = Boxify<string[] & { x: string }>;
>T5 : Boxify<string[] & { x: string; }>
>x : string

// https://github.com/microsoft/TypeScript/issues/57744

type MustBeArray<T extends any[]> = T;
>MustBeArray : T

type Hmm<T extends any[]> = T extends number[] ?
>Hmm : Hmm<T>

MustBeArray<{ [I in keyof T]: 1 }> :
never;

type X = Hmm<[3, 4, 5]>;
>X : [1, 1, 1]

21 changes: 21 additions & 0 deletions tests/cases/compiler/mappedArrayTupleIntersections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// @strict: true
// @noEmit: true

type Box<T> = { value: T };
type Boxify<T> = { [K in keyof T]: Box<T[K]> };

type T1 = Boxify<string[]>;
type T2 = Boxify<[string, string]>;
type T3 = Boxify<string[] & unknown[]>;
type T4 = Boxify<string[] & [string, string]>;
type T5 = Boxify<string[] & { x: string }>;

// https://github.com/microsoft/TypeScript/issues/57744

type MustBeArray<T extends any[]> = T;

type Hmm<T extends any[]> = T extends number[] ?
MustBeArray<{ [I in keyof T]: 1 }> :
never;

type X = Hmm<[3, 4, 5]>;

0 comments on commit b45a418

Please sign in to comment.