Skip to content

Commit 066773b

Browse files
authored
Fix constraints of nested homomorphic mapped type instantiations (#58098)
1 parent 25de1b0 commit 066773b

File tree

5 files changed

+111
-8
lines changed

5 files changed

+111
-8
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14811,16 +14811,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1481114811
return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type));
1481214812
}
1481314813

14814-
function getResolvedApparentTypeOfMappedType(type: MappedType) {
14814+
function getResolvedApparentTypeOfMappedType(type: MappedType): Type {
1481514815
const target = (type.target ?? type) as MappedType;
1481614816
const typeVariable = getHomomorphicTypeVariable(target);
1481714817
if (typeVariable && !target.declaration.nameType) {
14818-
const constraint = getConstraintTypeFromMappedType(type);
14819-
if (constraint.flags & TypeFlags.Index) {
14820-
const baseConstraint = getBaseConstraintOfType((constraint as IndexType).type);
14821-
if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) {
14822-
return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper));
14823-
}
14818+
// We have a homomorphic mapped type or an instantiation of a homomorphic mapped type, i.e. a type
14819+
// of the form { [P in keyof T]: X }. Obtain the modifiers type (the T of the keyof T), and if it is
14820+
// another generic mapped type, recursively obtain its apparent type. Otherwise, obtain its base
14821+
// constraint. Then, if every constituent of the base constraint is an array or tuple type, apply
14822+
// this mapped type to the base constraint. It is safe to recurse when the modifiers type is a
14823+
// mapped type because we protect again circular constraints in getTypeFromMappedTypeNode.
14824+
const modifiersType = getModifiersTypeFromMappedType(type);
14825+
const baseConstraint = isGenericMappedType(modifiersType) ? getApparentTypeOfMappedType(modifiersType) : getBaseConstraintOfType(modifiersType);
14826+
if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) {
14827+
return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper));
1482414828
}
1482514829
}
1482614830
return type;

tests/baselines/reference/deepComparisons.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
=== Performance Stats ===
44
Assignability cache: 300 / 300 (nearest 100)
5-
Type Count: 2,000 / 2,100 (nearest 100)
5+
Type Count: 2,000 / 2,000 (nearest 100)
66
Instantiation count: 3,500 / 3,500 (nearest 500)
77
Symbol count: 26,500 / 27,000 (nearest 500)
88

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//// [tests/cases/compiler/homomorphicMappedTypeNesting.ts] ////
2+
3+
=== homomorphicMappedTypeNesting.ts ===
4+
// Repro from #58060
5+
6+
type Box<T extends string> = { v: T };
7+
>Box : Symbol(Box, Decl(homomorphicMappedTypeNesting.ts, 0, 0))
8+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 2, 9))
9+
>v : Symbol(v, Decl(homomorphicMappedTypeNesting.ts, 2, 30))
10+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 2, 9))
11+
12+
type Test<T extends string[]> = T
13+
>Test : Symbol(Test, Decl(homomorphicMappedTypeNesting.ts, 2, 38))
14+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 4, 10))
15+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 4, 10))
16+
17+
type UnboxArray<T> = {
18+
>UnboxArray : Symbol(UnboxArray, Decl(homomorphicMappedTypeNesting.ts, 4, 33))
19+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 6, 16))
20+
21+
[K in keyof T]: T[K] extends Box<infer R> ? R : never;
22+
>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 7, 5))
23+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 6, 16))
24+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 6, 16))
25+
>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 7, 5))
26+
>Box : Symbol(Box, Decl(homomorphicMappedTypeNesting.ts, 0, 0))
27+
>R : Symbol(R, Decl(homomorphicMappedTypeNesting.ts, 7, 42))
28+
>R : Symbol(R, Decl(homomorphicMappedTypeNesting.ts, 7, 42))
29+
30+
};
31+
32+
type Identity<T> = { [K in keyof T]: T[K] };
33+
>Identity : Symbol(Identity, Decl(homomorphicMappedTypeNesting.ts, 8, 2))
34+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 10, 14))
35+
>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 10, 22))
36+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 10, 14))
37+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 10, 14))
38+
>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 10, 22))
39+
40+
declare function fnBad<T extends Array<Box<string>>>(...args: T): Test<Identity<UnboxArray<T>>>;
41+
>fnBad : Symbol(fnBad, Decl(homomorphicMappedTypeNesting.ts, 10, 44))
42+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 12, 23))
43+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
44+
>Box : Symbol(Box, Decl(homomorphicMappedTypeNesting.ts, 0, 0))
45+
>args : Symbol(args, Decl(homomorphicMappedTypeNesting.ts, 12, 53))
46+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 12, 23))
47+
>Test : Symbol(Test, Decl(homomorphicMappedTypeNesting.ts, 2, 38))
48+
>Identity : Symbol(Identity, Decl(homomorphicMappedTypeNesting.ts, 8, 2))
49+
>UnboxArray : Symbol(UnboxArray, Decl(homomorphicMappedTypeNesting.ts, 4, 33))
50+
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 12, 23))
51+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//// [tests/cases/compiler/homomorphicMappedTypeNesting.ts] ////
2+
3+
=== homomorphicMappedTypeNesting.ts ===
4+
// Repro from #58060
5+
6+
type Box<T extends string> = { v: T };
7+
>Box : Box<T>
8+
> : ^^^^^^
9+
>v : T
10+
> : ^
11+
12+
type Test<T extends string[]> = T
13+
>Test : T
14+
> : ^
15+
16+
type UnboxArray<T> = {
17+
>UnboxArray : UnboxArray<T>
18+
> : ^^^^^^^^^^^^^
19+
20+
[K in keyof T]: T[K] extends Box<infer R> ? R : never;
21+
};
22+
23+
type Identity<T> = { [K in keyof T]: T[K] };
24+
>Identity : Identity<T>
25+
> : ^^^^^^^^^^^
26+
27+
declare function fnBad<T extends Array<Box<string>>>(...args: T): Test<Identity<UnboxArray<T>>>;
28+
>fnBad : <T extends Box<string>[]>(...args: T) => Test<Identity<UnboxArray<T>>>
29+
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^^^^
30+
>args : T
31+
> : ^
32+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// Repro from #58060
5+
6+
type Box<T extends string> = { v: T };
7+
8+
type Test<T extends string[]> = T
9+
10+
type UnboxArray<T> = {
11+
[K in keyof T]: T[K] extends Box<infer R> ? R : never;
12+
};
13+
14+
type Identity<T> = { [K in keyof T]: T[K] };
15+
16+
declare function fnBad<T extends Array<Box<string>>>(...args: T): Test<Identity<UnboxArray<T>>>;

0 commit comments

Comments
 (0)