Distribute mapped types over array/tuple intersections (#57801)

This commit is contained in:
Anders Hejlsberg
2024-03-18 10:07:43 -07:00
committed by GitHub
parent eed3234acb
commit 37fa47e2e5
4 changed files with 152 additions and 24 deletions

View File

@@ -14571,7 +14571,7 @@ 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));
}
}
@@ -14579,6 +14579,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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 &&
@@ -19796,6 +19800,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
@@ -19804,33 +19809,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) {

View File

@@ -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))

View File

@@ -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]

View File

@@ -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]>;