diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3981ebc1255ff..4abe2f0eec1b9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14557,7 +14557,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)); } } @@ -14565,6 +14565,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 && @@ -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 @@ -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) { diff --git a/tests/baselines/reference/mappedArrayTupleIntersections.symbols b/tests/baselines/reference/mappedArrayTupleIntersections.symbols new file mode 100644 index 0000000000000..354bdb031d901 --- /dev/null +++ b/tests/baselines/reference/mappedArrayTupleIntersections.symbols @@ -0,0 +1,62 @@ +//// [tests/cases/compiler/mappedArrayTupleIntersections.ts] //// + +=== mappedArrayTupleIntersections.ts === +type Box = { 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 = { [K in keyof T]: Box }; +>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; +>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; +>T3 : Symbol(T3, Decl(mappedArrayTupleIntersections.ts, 4, 35)) +>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27)) + +type T4 = Boxify; +>T4 : Symbol(T4, Decl(mappedArrayTupleIntersections.ts, 5, 39)) +>Boxify : Symbol(Boxify, Decl(mappedArrayTupleIntersections.ts, 0, 27)) + +type T5 = Boxify; +>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; +>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 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)) + diff --git a/tests/baselines/reference/mappedArrayTupleIntersections.types b/tests/baselines/reference/mappedArrayTupleIntersections.types new file mode 100644 index 0000000000000..db4a465853fc9 --- /dev/null +++ b/tests/baselines/reference/mappedArrayTupleIntersections.types @@ -0,0 +1,40 @@ +//// [tests/cases/compiler/mappedArrayTupleIntersections.ts] //// + +=== mappedArrayTupleIntersections.ts === +type Box = { value: T }; +>Box : Box +>value : T + +type Boxify = { [K in keyof T]: Box }; +>Boxify : Boxify + +type T1 = Boxify; +>T1 : Box[] + +type T2 = Boxify<[string, string]>; +>T2 : [Box, Box] + +type T3 = Boxify; +>T3 : Box[] & Box[] + +type T4 = Boxify; +>T4 : Box[] & [Box, Box] + +type T5 = Boxify; +>T5 : Boxify +>x : string + +// https://github.com/microsoft/TypeScript/issues/57744 + +type MustBeArray = T; +>MustBeArray : T + +type Hmm = T extends number[] ? +>Hmm : Hmm + + MustBeArray<{ [I in keyof T]: 1 }> : + never; + +type X = Hmm<[3, 4, 5]>; +>X : [1, 1, 1] + diff --git a/tests/cases/compiler/mappedArrayTupleIntersections.ts b/tests/cases/compiler/mappedArrayTupleIntersections.ts new file mode 100644 index 0000000000000..4947e71a5d39c --- /dev/null +++ b/tests/cases/compiler/mappedArrayTupleIntersections.ts @@ -0,0 +1,21 @@ +// @strict: true +// @noEmit: true + +type Box = { value: T }; +type Boxify = { [K in keyof T]: Box }; + +type T1 = Boxify; +type T2 = Boxify<[string, string]>; +type T3 = Boxify; +type T4 = Boxify; +type T5 = Boxify; + +// https://github.com/microsoft/TypeScript/issues/57744 + +type MustBeArray = T; + +type Hmm = T extends number[] ? + MustBeArray<{ [I in keyof T]: 1 }> : + never; + +type X = Hmm<[3, 4, 5]>;