Skip to content

Commit 499c44c

Browse files
weswighamsandersn
authored andcommitted
Cache simplified indexed accesses to better handle circularly constrained indexed acceses (#24072)
1 parent 18670da commit 499c44c

File tree

8 files changed

+53
-5
lines changed

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
@@ -8719,6 +8719,10 @@ namespace ts {
87198719
// Transform an indexed access to a simpler form, if possible. Return the simpler form, or return
87208720
// the type itself if no transformation is possible.
87218721
function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type {
8722+
if (type.simplified) {
8723+
return type.simplified === circularConstraintType ? type : type.simplified;
8724+
}
8725+
type.simplified = circularConstraintType;
87228726
const objectType = type.objectType;
87238727
if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType)) {
87248728
// Given an indexed access type T[K], if T is an intersection containing one or more generic types and one or
@@ -8736,7 +8740,7 @@ namespace ts {
87368740
regularTypes.push(t);
87378741
}
87388742
}
8739-
return getUnionType([
8743+
return type.simplified = getUnionType([
87408744
getSimplifiedType(getIndexedAccessType(getIntersectionType(regularTypes), type.indexType)),
87418745
getIntersectionType(stringIndexTypes)
87428746
]);
@@ -8747,23 +8751,23 @@ namespace ts {
87478751
// eventually anyway, but it easier to reason about.
87488752
if (some((<IntersectionType>objectType).types, isMappedTypeToNever)) {
87498753
const nonNeverTypes = filter((<IntersectionType>objectType).types, t => !isMappedTypeToNever(t));
8750-
return getSimplifiedType(getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType));
8754+
return type.simplified = getSimplifiedType(getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType));
87518755
}
87528756
}
87538757
// If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper
87548758
// that substitutes the index type for P. For example, for an index access { [P in K]: Box<T[P]> }[X], we
87558759
// construct the type Box<T[X]>. We do not further simplify the result because mapped types can be recursive
87568760
// and we might never terminate.
87578761
if (isGenericMappedType(objectType)) {
8758-
return substituteIndexedMappedType(objectType, type);
8762+
return type.simplified = substituteIndexedMappedType(objectType, type);
87598763
}
87608764
if (objectType.flags & TypeFlags.TypeParameter) {
87618765
const constraint = getConstraintFromTypeParameter(objectType as TypeParameter);
87628766
if (constraint && isGenericMappedType(constraint)) {
8763-
return substituteIndexedMappedType(constraint, type);
8767+
return type.simplified = substituteIndexedMappedType(constraint, type);
87648768
}
87658769
}
8766-
return type;
8770+
return type.simplified = type;
87678771
}
87688772

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

src/compiler/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3990,6 +3990,7 @@ namespace ts {
39903990
objectType: Type;
39913991
indexType: Type;
39923992
constraint?: Type;
3993+
simplified?: Type;
39933994
}
39943995

39953996
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
@@ -2263,6 +2263,7 @@ declare namespace ts {
22632263
objectType: Type;
22642264
indexType: Type;
22652265
constraint?: Type;
2266+
simplified?: Type;
22662267
}
22672268
type TypeVariable = TypeParameter | IndexedAccessType;
22682269
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
@@ -2263,6 +2263,7 @@ declare namespace ts {
22632263
objectType: Type;
22642264
indexType: Type;
22652265
constraint?: Type;
2266+
simplified?: Type;
22662267
}
22672268
type TypeVariable = TypeParameter | IndexedAccessType;
22682269
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)