From 9f33bf1cd586a4028928d30b1bfe521788ec4da4 Mon Sep 17 00:00:00 2001 From: TypeScript Bot Date: Tue, 9 Apr 2024 18:38:50 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=A4=96=20Pick=20PR=20#58098=20(Fix=20cons?= =?UTF-8?q?traints=20of=20nested=20homomorph...)=20into=20release-5.4=20(#?= =?UTF-8?q?58118)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Anders Hejlsberg --- src/compiler/checker.ts | 18 ++++--- .../homomorphicMappedTypeNesting.symbols | 51 +++++++++++++++++++ .../homomorphicMappedTypeNesting.types | 25 +++++++++ .../compiler/homomorphicMappedTypeNesting.ts | 16 ++++++ 4 files changed, 103 insertions(+), 7 deletions(-) create mode 100644 tests/baselines/reference/homomorphicMappedTypeNesting.symbols create mode 100644 tests/baselines/reference/homomorphicMappedTypeNesting.types create mode 100644 tests/cases/compiler/homomorphicMappedTypeNesting.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c3d194211c56a..b2f5047e9d3ce 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14552,16 +14552,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type)); } - function getResolvedApparentTypeOfMappedType(type: MappedType) { + function getResolvedApparentTypeOfMappedType(type: MappedType): Type { const target = (type.target ?? type) as MappedType; const typeVariable = getHomomorphicTypeVariable(target); if (typeVariable && !target.declaration.nameType) { - const constraint = getConstraintTypeFromMappedType(type); - if (constraint.flags & TypeFlags.Index) { - const baseConstraint = getBaseConstraintOfType((constraint as IndexType).type); - if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) { - return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper)); - } + // We have a homomorphic mapped type or an instantiation of a homomorphic mapped type, i.e. a type + // of the form { [P in keyof T]: X }. Obtain the modifiers type (the T of the keyof T), and if it is + // another generic mapped type, recursively obtain its apparent type. Otherwise, obtain its base + // constraint. Then, if every constituent of the base constraint is an array or tuple type, apply + // this mapped type to the base constraint. It is safe to recurse when the modifiers type is a + // mapped type because we protect again circular constraints in getTypeFromMappedTypeNode. + const modifiersType = getModifiersTypeFromMappedType(type); + const baseConstraint = isGenericMappedType(modifiersType) ? getApparentTypeOfMappedType(modifiersType) : getBaseConstraintOfType(modifiersType); + if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) { + return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper)); } } return type; diff --git a/tests/baselines/reference/homomorphicMappedTypeNesting.symbols b/tests/baselines/reference/homomorphicMappedTypeNesting.symbols new file mode 100644 index 0000000000000..da939c18d6efb --- /dev/null +++ b/tests/baselines/reference/homomorphicMappedTypeNesting.symbols @@ -0,0 +1,51 @@ +//// [tests/cases/compiler/homomorphicMappedTypeNesting.ts] //// + +=== homomorphicMappedTypeNesting.ts === +// Repro from #58060 + +type Box = { v: T }; +>Box : Symbol(Box, Decl(homomorphicMappedTypeNesting.ts, 0, 0)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 2, 9)) +>v : Symbol(v, Decl(homomorphicMappedTypeNesting.ts, 2, 30)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 2, 9)) + +type Test = T +>Test : Symbol(Test, Decl(homomorphicMappedTypeNesting.ts, 2, 38)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 4, 10)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 4, 10)) + +type UnboxArray = { +>UnboxArray : Symbol(UnboxArray, Decl(homomorphicMappedTypeNesting.ts, 4, 33)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 6, 16)) + + [K in keyof T]: T[K] extends Box ? R : never; +>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 7, 5)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 6, 16)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 6, 16)) +>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 7, 5)) +>Box : Symbol(Box, Decl(homomorphicMappedTypeNesting.ts, 0, 0)) +>R : Symbol(R, Decl(homomorphicMappedTypeNesting.ts, 7, 42)) +>R : Symbol(R, Decl(homomorphicMappedTypeNesting.ts, 7, 42)) + +}; + +type Identity = { [K in keyof T]: T[K] }; +>Identity : Symbol(Identity, Decl(homomorphicMappedTypeNesting.ts, 8, 2)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 10, 14)) +>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 10, 22)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 10, 14)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 10, 14)) +>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 10, 22)) + +declare function fnBad>>(...args: T): Test>>; +>fnBad : Symbol(fnBad, Decl(homomorphicMappedTypeNesting.ts, 10, 44)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 12, 23)) +>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>Box : Symbol(Box, Decl(homomorphicMappedTypeNesting.ts, 0, 0)) +>args : Symbol(args, Decl(homomorphicMappedTypeNesting.ts, 12, 53)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 12, 23)) +>Test : Symbol(Test, Decl(homomorphicMappedTypeNesting.ts, 2, 38)) +>Identity : Symbol(Identity, Decl(homomorphicMappedTypeNesting.ts, 8, 2)) +>UnboxArray : Symbol(UnboxArray, Decl(homomorphicMappedTypeNesting.ts, 4, 33)) +>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 12, 23)) + diff --git a/tests/baselines/reference/homomorphicMappedTypeNesting.types b/tests/baselines/reference/homomorphicMappedTypeNesting.types new file mode 100644 index 0000000000000..82eb61f12bc6e --- /dev/null +++ b/tests/baselines/reference/homomorphicMappedTypeNesting.types @@ -0,0 +1,25 @@ +//// [tests/cases/compiler/homomorphicMappedTypeNesting.ts] //// + +=== homomorphicMappedTypeNesting.ts === +// Repro from #58060 + +type Box = { v: T }; +>Box : Box +>v : T + +type Test = T +>Test : T + +type UnboxArray = { +>UnboxArray : UnboxArray + + [K in keyof T]: T[K] extends Box ? R : never; +}; + +type Identity = { [K in keyof T]: T[K] }; +>Identity : Identity + +declare function fnBad>>(...args: T): Test>>; +>fnBad : []>(...args: T) => Test>> +>args : T + diff --git a/tests/cases/compiler/homomorphicMappedTypeNesting.ts b/tests/cases/compiler/homomorphicMappedTypeNesting.ts new file mode 100644 index 0000000000000..630c4d40cf51d --- /dev/null +++ b/tests/cases/compiler/homomorphicMappedTypeNesting.ts @@ -0,0 +1,16 @@ +// @strict: true +// @noEmit: true + +// Repro from #58060 + +type Box = { v: T }; + +type Test = T + +type UnboxArray = { + [K in keyof T]: T[K] extends Box ? R : never; +}; + +type Identity = { [K in keyof T]: T[K] }; + +declare function fnBad>>(...args: T): Test>>;