Skip to content

Commit ba4bf21

Browse files
authored
Cache simplified indexed accesses to better handle circularly constrained indexed acceses (#24072)
1 parent 7e3af08 commit ba4bf21

8 files changed

+53
-5
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8692,6 +8692,10 @@ namespace ts {
86928692
// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
86938693
// the type itself if no transformation is possible.
86948694
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
8695+
if (type.simplified) {
8696+
return type.simplified === circularConstraintType ? type : type.simplified;
8697+
}
8698+
type.simplified = circularConstraintType;
86958699
const objectType = type.objectType;
86968700
if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType)) {
86978701
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
@@ -8709,7 +8713,7 @@ namespace ts {
87098713
regularTypes.push(t);
87108714
}
87118715
}
8712-
return getUnionType([
8716+
return type.simplified = getUnionType([
87138717
getSimplifiedType(getIndexedAccessType(getIntersectionType(regularTypes), type.indexType)),
87148718
getIntersectionType(stringIndexTypes)
87158719
]);
@@ -8720,23 +8724,23 @@ namespace ts {
87208724
// eventually anyway, but it easier to reason about.
87218725
if (some((<IntersectionType>objectType).types, isMappedTypeToNever)) {
87228726
const nonNeverTypes = filter((<IntersectionType>objectType).types, t => !isMappedTypeToNever(t));
8723-
return getSimplifiedType(getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType));
8727+
return type.simplified = getSimplifiedType(getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType));
87248728
}
87258729
}
87268730
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
87278731
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
87288732
// construct the type Box<T[X]>. We do not further simplify the result because mapped types can be recursive
87298733
// and we might never terminate.
87308734
if (isGenericMappedType(objectType)) {
8731-
return substituteIndexedMappedType(objectType, type);
8735+
return type.simplified = substituteIndexedMappedType(objectType, type);
87328736
}
87338737
if (objectType.flags & TypeFlags.TypeParameter) {
87348738
const constraint = getConstraintFromTypeParameter(objectType as TypeParameter);
87358739
if (constraint && isGenericMappedType(constraint)) {
8736-
return substituteIndexedMappedType(constraint, type);
8740+
return type.simplified = substituteIndexedMappedType(constraint, type);
87378741
}
87388742
}
8739-
return type;
8743+
return type.simplified = type;
87408744
}
87418745

87428746
function substituteIndexedMappedType(objectType: MappedType, type: IndexedAccessType) {

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3973,6 +3973,7 @@ namespace ts {
39733973
objectType: Type;
39743974
indexType: Type;
39753975
constraint?: Type;
3976+
simplified?: Type;
39763977
}
39773978

39783979
export type TypeVariable = TypeParameter | IndexedAccessType;

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,6 +2248,7 @@ declare namespace ts {
22482248
objectType: Type;
22492249
indexType: Type;
22502250
constraint?: Type;
2251+
simplified?: Type;
22512252
}
22522253
type TypeVariable = TypeParameter | IndexedAccessType;
22532254
interface IndexType extends InstantiableType {

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2248,6 +2248,7 @@ declare namespace ts {
22482248
objectType: Type;
22492249
indexType: Type;
22502250
constraint?: Type;
2251+
simplified?: Type;
22512252
}
22522253
type TypeVariable = TypeParameter | IndexedAccessType;
22532254
interface IndexType extends InstantiableType {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//// [circularConstrainedMappedTypeNoCrash.ts]
2+
type Loop<T, U extends Loop<T, U>> = {
3+
[P in keyof T]: U[P] extends boolean ? number : string;
4+
};
5+
6+
//// [circularConstrainedMappedTypeNoCrash.js]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
=== tests/cases/compiler/circularConstrainedMappedTypeNoCrash.ts ===
2+
type Loop<T, U extends Loop<T, U>> = {
3+
>Loop : Symbol(Loop, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 0))
4+
>T : Symbol(T, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 10))
5+
>U : Symbol(U, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 12))
6+
>Loop : Symbol(Loop, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 0))
7+
>T : Symbol(T, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 10))
8+
>U : Symbol(U, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 12))
9+
10+
[P in keyof T]: U[P] extends boolean ? number : string;
11+
>P : Symbol(P, Decl(circularConstrainedMappedTypeNoCrash.ts, 1, 5))
12+
>T : Symbol(T, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 10))
13+
>U : Symbol(U, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 12))
14+
>P : Symbol(P, Decl(circularConstrainedMappedTypeNoCrash.ts, 1, 5))
15+
16+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
=== tests/cases/compiler/circularConstrainedMappedTypeNoCrash.ts ===
2+
type Loop<T, U extends Loop<T, U>> = {
3+
>Loop : Loop<T, U>
4+
>T : T
5+
>U : U
6+
>Loop : Loop<T, U>
7+
>T : T
8+
>U : U
9+
10+
[P in keyof T]: U[P] extends boolean ? number : string;
11+
>P : P
12+
>T : T
13+
>U : U
14+
>P : P
15+
16+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
type Loop<T, U extends Loop<T, U>> = {
2+
[P in keyof T]: U[P] extends boolean ? number : string;
3+
};

0 commit comments

Comments
 (0)