diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b00073557ca..9a443e6c444 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10369,7 +10369,15 @@ namespace ts { if (typeVariable) { const mappedTypeVariable = instantiateType(typeVariable, mapper); if (typeVariable !== mappedTypeVariable) { - return mapType(mappedTypeVariable, t => { + // If we are already in the process of creating an instantiation of this mapped type, + // return the error type. This situation only arises if we are instantiating the mapped + // type for an array or tuple type, as we then need to eagerly resolve the (possibly + // circular) element type(s). + if (type.instantiating) { + return errorType; + } + type.instantiating = true; + const result = mapType(mappedTypeVariable, t => { if (t.flags & (TypeFlags.AnyOrUnknown | TypeFlags.InstantiableNonPrimitive | TypeFlags.Object | TypeFlags.Intersection) && t !== wildcardType) { const replacementMapper = createReplacementMapper(typeVariable, t, mapper); return isArrayType(t) ? createArrayType(instantiateMappedTypeTemplate(type, numberType, /*isOptional*/ true, replacementMapper)) : @@ -10379,6 +10387,8 @@ namespace ts { } return t; }); + type.instantiating = false; + return result; } } return instantiateAnonymousType(type, mapper); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3bd103579f1..5763ab3eff0 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4074,6 +4074,7 @@ namespace ts { templateType?: Type; modifiersType?: Type; resolvedApparentType?: Type; + instantiating?: boolean; } export interface EvolvingArrayType extends ObjectType { diff --git a/tests/baselines/reference/recursiveMappedTypes.errors.txt b/tests/baselines/reference/recursiveMappedTypes.errors.txt index 8279f4f60a6..b702cf7e316 100644 --- a/tests/baselines/reference/recursiveMappedTypes.errors.txt +++ b/tests/baselines/reference/recursiveMappedTypes.errors.txt @@ -31,4 +31,14 @@ tests/cases/conformance/types/mapped/recursiveMappedTypes.ts(12,11): error TS231 [K in keyof Recurse1]: Recurse1[K] ~~~~~~~~~~~~~~ !!! error TS2313: Type parameter 'K' has a circular constraint. - } \ No newline at end of file + } + + // Repro from #27881 + + export type Circular = {[P in keyof T]: Circular}; + type tup = [number, number, number, number]; + + function foo(arg: Circular): tup { + return arg; + } + \ No newline at end of file diff --git a/tests/baselines/reference/recursiveMappedTypes.js b/tests/baselines/reference/recursiveMappedTypes.js index 50610708664..612c9d3fca4 100644 --- a/tests/baselines/reference/recursiveMappedTypes.js +++ b/tests/baselines/reference/recursiveMappedTypes.js @@ -11,19 +11,28 @@ type Recurse1 = { type Recurse2 = { [K in keyof Recurse1]: Recurse1[K] -} +} + +// Repro from #27881 + +export type Circular = {[P in keyof T]: Circular}; +type tup = [number, number, number, number]; + +function foo(arg: Circular): tup { + return arg; +} + //// [recursiveMappedTypes.js] +"use strict"; // Recursive mapped types simply appear empty +exports.__esModule = true; +function foo(arg) { + return arg; +} //// [recursiveMappedTypes.d.ts] -declare type Recurse = { - [K in keyof Recurse]: Recurse[K]; -}; -declare type Recurse1 = { - [K in keyof Recurse2]: Recurse2[K]; -}; -declare type Recurse2 = { - [K in keyof Recurse1]: Recurse1[K]; +export declare type Circular = { + [P in keyof T]: Circular; }; diff --git a/tests/baselines/reference/recursiveMappedTypes.symbols b/tests/baselines/reference/recursiveMappedTypes.symbols index 20ebbedc00a..7c0e2ffaa23 100644 --- a/tests/baselines/reference/recursiveMappedTypes.symbols +++ b/tests/baselines/reference/recursiveMappedTypes.symbols @@ -30,3 +30,28 @@ type Recurse2 = { >Recurse1 : Symbol(Recurse1, Decl(recursiveMappedTypes.ts, 4, 1)) >K : Symbol(K, Decl(recursiveMappedTypes.ts, 11, 5)) } + +// Repro from #27881 + +export type Circular = {[P in keyof T]: Circular}; +>Circular : Symbol(Circular, Decl(recursiveMappedTypes.ts, 12, 1)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 16, 21)) +>P : Symbol(P, Decl(recursiveMappedTypes.ts, 16, 28)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 16, 21)) +>Circular : Symbol(Circular, Decl(recursiveMappedTypes.ts, 12, 1)) +>T : Symbol(T, Decl(recursiveMappedTypes.ts, 16, 21)) + +type tup = [number, number, number, number]; +>tup : Symbol(tup, Decl(recursiveMappedTypes.ts, 16, 56)) + +function foo(arg: Circular): tup { +>foo : Symbol(foo, Decl(recursiveMappedTypes.ts, 17, 44)) +>arg : Symbol(arg, Decl(recursiveMappedTypes.ts, 19, 13)) +>Circular : Symbol(Circular, Decl(recursiveMappedTypes.ts, 12, 1)) +>tup : Symbol(tup, Decl(recursiveMappedTypes.ts, 16, 56)) +>tup : Symbol(tup, Decl(recursiveMappedTypes.ts, 16, 56)) + + return arg; +>arg : Symbol(arg, Decl(recursiveMappedTypes.ts, 19, 13)) +} + diff --git a/tests/baselines/reference/recursiveMappedTypes.types b/tests/baselines/reference/recursiveMappedTypes.types index aae3280b605..5741f711e9d 100644 --- a/tests/baselines/reference/recursiveMappedTypes.types +++ b/tests/baselines/reference/recursiveMappedTypes.types @@ -18,3 +18,20 @@ type Recurse2 = { [K in keyof Recurse1]: Recurse1[K] } + +// Repro from #27881 + +export type Circular = {[P in keyof T]: Circular}; +>Circular : Circular + +type tup = [number, number, number, number]; +>tup : [number, number, number, number] + +function foo(arg: Circular): tup { +>foo : (arg: [any, any, any, any]) => [number, number, number, number] +>arg : [any, any, any, any] + + return arg; +>arg : [any, any, any, any] +} + diff --git a/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts b/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts index 7a78ad9dc4a..188efa2f51a 100644 --- a/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts +++ b/tests/cases/conformance/types/mapped/recursiveMappedTypes.ts @@ -12,4 +12,13 @@ type Recurse1 = { type Recurse2 = { [K in keyof Recurse1]: Recurse1[K] -} \ No newline at end of file +} + +// Repro from #27881 + +export type Circular = {[P in keyof T]: Circular}; +type tup = [number, number, number, number]; + +function foo(arg: Circular): tup { + return arg; +}