Skip to content

Commit 3e094ed

Browse files
authored
Only call getLowerBoundOfKeyType on non-generic mapped types (#56280)
1 parent 9302332 commit 3e094ed

File tree

4 files changed

+62
-18
lines changed

4 files changed

+62
-18
lines changed

src/compiler/checker.ts

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17493,30 +17493,27 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1749317493
return constraintType;
1749417494
}
1749517495
const keyTypes: Type[] = [];
17496-
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
17497-
// We have a { [P in keyof T]: X }
17498-
17499-
// `getApparentType` on the T in a generic mapped type can trigger a circularity
17500-
// (conditionals and `infer` types create a circular dependency in the constraint resolution)
17501-
// so we only eagerly manifest the keys if the constraint is nongeneric
17502-
if (!isGenericIndexType(constraintType)) {
17503-
const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
17504-
forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, !!(indexFlags & IndexFlags.StringsOnly), addMemberForKeyType);
17505-
}
17506-
else {
17507-
// we have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer the whole `keyof whatever` for later
17508-
// since it's not safe to resolve the shape of modifier type
17496+
// Calling getApparentType on the `T` of a `keyof T` in the constraint type of a generic mapped type can
17497+
// trigger a circularity. For example, `T extends { [P in keyof T & string as Captitalize<P>]: any }` is
17498+
// a circular definition. For this reason, we only eagerly manifest the keys if the constraint is non-generic.
17499+
if (isGenericIndexType(constraintType)) {
17500+
if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
17501+
// We have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer
17502+
// the whole `keyof whatever` for later since it's not safe to resolve the shape of modifier type.
1750917503
return getIndexTypeForGenericType(type, indexFlags);
1751017504
}
17505+
// Include the generic component in the resulting type.
17506+
forEachType(constraintType, addMemberForKeyType);
17507+
}
17508+
else if (isMappedTypeWithKeyofConstraintDeclaration(type)) {
17509+
const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T'
17510+
forEachMappedTypePropertyKeyTypeAndIndexSignatureKeyType(modifiersType, TypeFlags.StringOrNumberLiteralOrUnique, !!(indexFlags & IndexFlags.StringsOnly), addMemberForKeyType);
1751117511
}
1751217512
else {
1751317513
forEachType(getLowerBoundOfKeyType(constraintType), addMemberForKeyType);
1751417514
}
17515-
if (isGenericIndexType(constraintType)) { // include the generic component in the resulting type
17516-
forEachType(constraintType, addMemberForKeyType);
17517-
}
17518-
// we had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the original constraintType,
17519-
// so we can return the union that preserves aliases/origin data if possible
17515+
// We had to pick apart the constraintType to potentially map/filter it - compare the final resulting list with the
17516+
// original constraintType, so we can return the union that preserves aliases/origin data if possible.
1752017517
const result = indexFlags & IndexFlags.NoIndexSignatures ? filterType(getUnionType(keyTypes), t => !(t.flags & (TypeFlags.Any | TypeFlags.String))) : getUnionType(keyTypes);
1752117518
if (result.flags & TypeFlags.Union && constraintType.flags & TypeFlags.Union && getTypeListId((result as UnionType).types) === getTypeListId((constraintType as UnionType).types)) {
1752217519
return constraintType;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//// [tests/cases/compiler/circularMappedTypeConstraint.ts] ////
2+
3+
=== circularMappedTypeConstraint.ts ===
4+
// Repro from #56232
5+
6+
declare function foo2<T extends { [P in keyof T & string as Capitalize<P>]: V }, V extends string>(a: T): T;
7+
>foo2 : Symbol(foo2, Decl(circularMappedTypeConstraint.ts, 0, 0))
8+
>T : Symbol(T, Decl(circularMappedTypeConstraint.ts, 2, 22))
9+
>P : Symbol(P, Decl(circularMappedTypeConstraint.ts, 2, 35))
10+
>T : Symbol(T, Decl(circularMappedTypeConstraint.ts, 2, 22))
11+
>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --))
12+
>P : Symbol(P, Decl(circularMappedTypeConstraint.ts, 2, 35))
13+
>V : Symbol(V, Decl(circularMappedTypeConstraint.ts, 2, 80))
14+
>V : Symbol(V, Decl(circularMappedTypeConstraint.ts, 2, 80))
15+
>a : Symbol(a, Decl(circularMappedTypeConstraint.ts, 2, 99))
16+
>T : Symbol(T, Decl(circularMappedTypeConstraint.ts, 2, 22))
17+
>T : Symbol(T, Decl(circularMappedTypeConstraint.ts, 2, 22))
18+
19+
export const r2 = foo2({A: "a"});
20+
>r2 : Symbol(r2, Decl(circularMappedTypeConstraint.ts, 3, 12))
21+
>foo2 : Symbol(foo2, Decl(circularMappedTypeConstraint.ts, 0, 0))
22+
>A : Symbol(A, Decl(circularMappedTypeConstraint.ts, 3, 24))
23+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//// [tests/cases/compiler/circularMappedTypeConstraint.ts] ////
2+
3+
=== circularMappedTypeConstraint.ts ===
4+
// Repro from #56232
5+
6+
declare function foo2<T extends { [P in keyof T & string as Capitalize<P>]: V }, V extends string>(a: T): T;
7+
>foo2 : <T extends { [P in keyof T & string as Capitalize<P>]: V; }, V extends string>(a: T) => T
8+
>a : T
9+
10+
export const r2 = foo2({A: "a"});
11+
>r2 : { A: string; }
12+
>foo2({A: "a"}) : { A: string; }
13+
>foo2 : <T extends { [P in keyof T & string as Capitalize<P>]: V; }, V extends string>(a: T) => T
14+
>{A: "a"} : { A: string; }
15+
>A : string
16+
>"a" : "a"
17+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// Repro from #56232
5+
6+
declare function foo2<T extends { [P in keyof T & string as Capitalize<P>]: V }, V extends string>(a: T): T;
7+
export const r2 = foo2({A: "a"});

0 commit comments

Comments
 (0)