From 1cbf0c2f84b65a1b48408d40c62b3c2df22ff9cb Mon Sep 17 00:00:00 2001
From: Wesley Wigham <wewigham@microsoft.com>
Date: Tue, 11 Dec 2018 15:12:10 -0800
Subject: [PATCH] Check for reentrancy in fillMissingTypeArguments and use
 defaults instead if so

---
 src/compiler/checker.ts                         | 17 ++++++++++++++---
 .../selfRecrusiveTypeParameterWithDefault.js    |  7 +++++++
 ...elfRecrusiveTypeParameterWithDefault.symbols | 12 ++++++++++++
 .../selfRecrusiveTypeParameterWithDefault.types |  6 ++++++
 .../selfRecrusiveTypeParameterWithDefault.ts    |  3 +++
 5 files changed, 42 insertions(+), 3 deletions(-)
 create mode 100644 tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.js
 create mode 100644 tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.symbols
 create mode 100644 tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.types
 create mode 100644 tests/cases/compiler/selfRecrusiveTypeParameterWithDefault.ts

diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 73c93f48cbfa0..b3888acb5837c 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -70,6 +70,7 @@ namespace ts {
 
         const emptySymbols = createSymbolTable();
         const identityMapper: (type: Type) => Type = identity;
+        const fillMissingTypeArgumentState = createMap<true>();
 
         const compilerOptions = host.getCompilerOptions();
         const languageVersion = getEmitScriptTarget(compilerOptions);
@@ -7771,14 +7772,24 @@ namespace ts {
             const numTypeArguments = length(typeArguments);
             if (isJavaScriptImplicitAny || (numTypeArguments >= minTypeArgumentCount && numTypeArguments <= numTypeParameters)) {
                 const result = typeArguments ? typeArguments.slice() : [];
-
+                const stateKey = getTypeListId(typeArguments) + ">" + getTypeListId(typeParameters);
                 // Map an unsatisfied type parameter with a default type.
                 // If a type parameter does not have a default type, or if the default type
                 // is a forward reference, the empty object type is used.
                 const baseDefaultType = getDefaultTypeArgumentType(isJavaScriptImplicitAny);
                 const circularityMapper = createTypeMapper(typeParameters!, map(typeParameters!, () => baseDefaultType));
-                for (let i = numTypeArguments; i < numTypeParameters; i++) {
-                    result[i] = instantiateType(getConstraintFromTypeParameter(typeParameters![i]) || baseDefaultType, circularityMapper);
+                if (fillMissingTypeArgumentState.has(stateKey)) {
+                    // If we are already in the process of filling the type argument list's defaults, we cannot recur into the constraint again and must the the base
+                    for (let i = numTypeArguments; i < numTypeParameters; i++) {
+                        result[i] = baseDefaultType;
+                    }
+                }
+                else {
+                    fillMissingTypeArgumentState.set(stateKey, true);
+                    for (let i = numTypeArguments; i < numTypeParameters; i++) {
+                        result[i] = instantiateType(getConstraintFromTypeParameter(typeParameters![i]) || baseDefaultType, circularityMapper);
+                    }
+                    fillMissingTypeArgumentState.delete(stateKey);
                 }
                 for (let i = numTypeArguments; i < numTypeParameters; i++) {
                     const mapper = createTypeMapper(typeParameters!, result);
diff --git a/tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.js b/tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.js
new file mode 100644
index 0000000000000..83ca074653015
--- /dev/null
+++ b/tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.js
@@ -0,0 +1,7 @@
+//// [selfRecrusiveTypeParameterWithDefault.ts]
+interface JoiObject {}
+
+interface AbstractSchema<Schema extends AbstractSchema = any, Value = any> extends JoiObject { x; }
+
+
+//// [selfRecrusiveTypeParameterWithDefault.js]
diff --git a/tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.symbols b/tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.symbols
new file mode 100644
index 0000000000000..b9231899d02b8
--- /dev/null
+++ b/tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.symbols
@@ -0,0 +1,12 @@
+=== tests/cases/compiler/selfRecrusiveTypeParameterWithDefault.ts ===
+interface JoiObject {}
+>JoiObject : Symbol(JoiObject, Decl(selfRecrusiveTypeParameterWithDefault.ts, 0, 0))
+
+interface AbstractSchema<Schema extends AbstractSchema = any, Value = any> extends JoiObject { x; }
+>AbstractSchema : Symbol(AbstractSchema, Decl(selfRecrusiveTypeParameterWithDefault.ts, 0, 22))
+>Schema : Symbol(Schema, Decl(selfRecrusiveTypeParameterWithDefault.ts, 2, 25))
+>AbstractSchema : Symbol(AbstractSchema, Decl(selfRecrusiveTypeParameterWithDefault.ts, 0, 22))
+>Value : Symbol(Value, Decl(selfRecrusiveTypeParameterWithDefault.ts, 2, 61))
+>JoiObject : Symbol(JoiObject, Decl(selfRecrusiveTypeParameterWithDefault.ts, 0, 0))
+>x : Symbol(AbstractSchema.x, Decl(selfRecrusiveTypeParameterWithDefault.ts, 2, 94))
+
diff --git a/tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.types b/tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.types
new file mode 100644
index 0000000000000..f8f31d3d905b3
--- /dev/null
+++ b/tests/baselines/reference/selfRecrusiveTypeParameterWithDefault.types
@@ -0,0 +1,6 @@
+=== tests/cases/compiler/selfRecrusiveTypeParameterWithDefault.ts ===
+interface JoiObject {}
+
+interface AbstractSchema<Schema extends AbstractSchema = any, Value = any> extends JoiObject { x; }
+>x : any
+
diff --git a/tests/cases/compiler/selfRecrusiveTypeParameterWithDefault.ts b/tests/cases/compiler/selfRecrusiveTypeParameterWithDefault.ts
new file mode 100644
index 0000000000000..a43a0c27783b6
--- /dev/null
+++ b/tests/cases/compiler/selfRecrusiveTypeParameterWithDefault.ts
@@ -0,0 +1,3 @@
+interface JoiObject {}
+
+interface AbstractSchema<Schema extends AbstractSchema = any, Value = any> extends JoiObject { x; }