diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index caa75a45eb987..b85acc03d468f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -8692,6 +8692,10 @@ namespace ts { // Transform an indexed access to a simpler form, if possible. Return the simpler form, or return // the type itself if no transformation is possible. function getSimplifiedIndexedAccessType(type: IndexedAccessType): Type { + if (type.simplified) { + return type.simplified === circularConstraintType ? type : type.simplified; + } + type.simplified = circularConstraintType; const objectType = type.objectType; if (objectType.flags & TypeFlags.Intersection && isGenericObjectType(objectType)) { // 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 { regularTypes.push(t); } } - return getUnionType([ + return type.simplified = getUnionType([ getSimplifiedType(getIndexedAccessType(getIntersectionType(regularTypes), type.indexType)), getIntersectionType(stringIndexTypes) ]); @@ -8720,7 +8724,7 @@ namespace ts { // eventually anyway, but it easier to reason about. if (some((objectType).types, isMappedTypeToNever)) { const nonNeverTypes = filter((objectType).types, t => !isMappedTypeToNever(t)); - return getSimplifiedType(getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType)); + return type.simplified = getSimplifiedType(getIndexedAccessType(getIntersectionType(nonNeverTypes), type.indexType)); } } // If the object type is a mapped type { [P in K]: E }, where K is generic, instantiate E using a mapper @@ -8728,15 +8732,15 @@ namespace ts { // construct the type Box. We do not further simplify the result because mapped types can be recursive // and we might never terminate. if (isGenericMappedType(objectType)) { - return substituteIndexedMappedType(objectType, type); + return type.simplified = substituteIndexedMappedType(objectType, type); } if (objectType.flags & TypeFlags.TypeParameter) { const constraint = getConstraintFromTypeParameter(objectType as TypeParameter); if (constraint && isGenericMappedType(constraint)) { - return substituteIndexedMappedType(constraint, type); + return type.simplified = substituteIndexedMappedType(constraint, type); } } - return type; + return type.simplified = type; } function substituteIndexedMappedType(objectType: MappedType, type: IndexedAccessType) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 4b2a7c080d216..5c1069538d0a1 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3973,6 +3973,7 @@ namespace ts { objectType: Type; indexType: Type; constraint?: Type; + simplified?: Type; } export type TypeVariable = TypeParameter | IndexedAccessType; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c069dbf4d0473..c8b84abca4c30 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2248,6 +2248,7 @@ declare namespace ts { objectType: Type; indexType: Type; constraint?: Type; + simplified?: Type; } type TypeVariable = TypeParameter | IndexedAccessType; interface IndexType extends InstantiableType { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4887de7fd2fc2..d30b11856ae1d 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2248,6 +2248,7 @@ declare namespace ts { objectType: Type; indexType: Type; constraint?: Type; + simplified?: Type; } type TypeVariable = TypeParameter | IndexedAccessType; interface IndexType extends InstantiableType { diff --git a/tests/baselines/reference/circularConstrainedMappedTypeNoCrash.js b/tests/baselines/reference/circularConstrainedMappedTypeNoCrash.js new file mode 100644 index 0000000000000..4448a5b858976 --- /dev/null +++ b/tests/baselines/reference/circularConstrainedMappedTypeNoCrash.js @@ -0,0 +1,6 @@ +//// [circularConstrainedMappedTypeNoCrash.ts] +type Loop> = { + [P in keyof T]: U[P] extends boolean ? number : string; +}; + +//// [circularConstrainedMappedTypeNoCrash.js] diff --git a/tests/baselines/reference/circularConstrainedMappedTypeNoCrash.symbols b/tests/baselines/reference/circularConstrainedMappedTypeNoCrash.symbols new file mode 100644 index 0000000000000..a83439bafb9d3 --- /dev/null +++ b/tests/baselines/reference/circularConstrainedMappedTypeNoCrash.symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/circularConstrainedMappedTypeNoCrash.ts === +type Loop> = { +>Loop : Symbol(Loop, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 0)) +>T : Symbol(T, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 10)) +>U : Symbol(U, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 12)) +>Loop : Symbol(Loop, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 0)) +>T : Symbol(T, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 10)) +>U : Symbol(U, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 12)) + + [P in keyof T]: U[P] extends boolean ? number : string; +>P : Symbol(P, Decl(circularConstrainedMappedTypeNoCrash.ts, 1, 5)) +>T : Symbol(T, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 10)) +>U : Symbol(U, Decl(circularConstrainedMappedTypeNoCrash.ts, 0, 12)) +>P : Symbol(P, Decl(circularConstrainedMappedTypeNoCrash.ts, 1, 5)) + +}; diff --git a/tests/baselines/reference/circularConstrainedMappedTypeNoCrash.types b/tests/baselines/reference/circularConstrainedMappedTypeNoCrash.types new file mode 100644 index 0000000000000..315fa8351c166 --- /dev/null +++ b/tests/baselines/reference/circularConstrainedMappedTypeNoCrash.types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/circularConstrainedMappedTypeNoCrash.ts === +type Loop> = { +>Loop : Loop +>T : T +>U : U +>Loop : Loop +>T : T +>U : U + + [P in keyof T]: U[P] extends boolean ? number : string; +>P : P +>T : T +>U : U +>P : P + +}; diff --git a/tests/cases/compiler/circularConstrainedMappedTypeNoCrash.ts b/tests/cases/compiler/circularConstrainedMappedTypeNoCrash.ts new file mode 100644 index 0000000000000..9e60b3e86cd5c --- /dev/null +++ b/tests/cases/compiler/circularConstrainedMappedTypeNoCrash.ts @@ -0,0 +1,3 @@ +type Loop> = { + [P in keyof T]: U[P] extends boolean ? number : string; +}; \ No newline at end of file