From 63d746bc4c5f3cee42e65462705a038e4a7412fa Mon Sep 17 00:00:00 2001
From: Anders Hejlsberg <andersh@microsoft.com>
Date: Mon, 11 Sep 2017 10:24:27 -0700
Subject: [PATCH 1/2] Higher order inference for mapped, index and lookup types

---
 src/compiler/checker.ts | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index ff5d1c661e866..4f2f065f4f315 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -10386,7 +10386,7 @@ namespace ts {
         // results for union and intersection types for performance reasons.
         function couldContainTypeVariables(type: Type): boolean {
             const objectFlags = getObjectFlags(type);
-            return !!(type.flags & TypeFlags.TypeVariable ||
+            return !!(type.flags & (TypeFlags.TypeVariable | TypeFlags.Index) ||
                 objectFlags & ObjectFlags.Reference && forEach((<TypeReference>type).typeArguments, couldContainTypeVariables) ||
                 objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.Class) ||
                 objectFlags & ObjectFlags.Mapped ||
@@ -10554,6 +10554,13 @@ namespace ts {
                         inferFromTypes(sourceTypes[i], targetTypes[i]);
                     }
                 }
+                else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
+                    inferFromTypes((<IndexType>source).type, (<IndexType>target).type);
+                }
+                else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
+                    inferFromTypes((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType);
+                    inferFromTypes((<IndexedAccessType>source).indexType, (<IndexedAccessType>target).indexType);
+                }
                 else if (target.flags & TypeFlags.UnionOrIntersection) {
                     const targetTypes = (<UnionOrIntersectionType>target).types;
                     let typeVariableCount = 0;
@@ -10627,6 +10634,12 @@ namespace ts {
             }
 
             function inferFromObjectTypes(source: Type, target: Type) {
+                if (isGenericMappedType(source) && isGenericMappedType(target)) {
+                    // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer
+                    // from S to T and from X to Y.
+                    inferFromTypes(getConstraintTypeFromMappedType(<MappedType>source), getConstraintTypeFromMappedType(<MappedType>target));
+                    inferFromTypes(getTemplateTypeFromMappedType(<MappedType>source), getTemplateTypeFromMappedType(<MappedType>target));
+                }
                 if (getObjectFlags(target) & ObjectFlags.Mapped) {
                     const constraintType = getConstraintTypeFromMappedType(<MappedType>target);
                     if (constraintType.flags & TypeFlags.Index) {

From 0823eba8a3257b80df68494ac0d534ac9b6bd16b Mon Sep 17 00:00:00 2001
From: Anders Hejlsberg <andersh@microsoft.com>
Date: Mon, 11 Sep 2017 10:38:46 -0700
Subject: [PATCH 2/2] Add tests

---
 .../higherOrderMappedIndexLookupInference.js  |  43 ++++++++
 ...herOrderMappedIndexLookupInference.symbols |  98 +++++++++++++++++
 ...igherOrderMappedIndexLookupInference.types | 104 ++++++++++++++++++
 .../higherOrderMappedIndexLookupInference.ts  |  25 +++++
 4 files changed, 270 insertions(+)
 create mode 100644 tests/baselines/reference/higherOrderMappedIndexLookupInference.js
 create mode 100644 tests/baselines/reference/higherOrderMappedIndexLookupInference.symbols
 create mode 100644 tests/baselines/reference/higherOrderMappedIndexLookupInference.types
 create mode 100644 tests/cases/compiler/higherOrderMappedIndexLookupInference.ts

diff --git a/tests/baselines/reference/higherOrderMappedIndexLookupInference.js b/tests/baselines/reference/higherOrderMappedIndexLookupInference.js
new file mode 100644
index 0000000000000..e8e09a59713cb
--- /dev/null
+++ b/tests/baselines/reference/higherOrderMappedIndexLookupInference.js
@@ -0,0 +1,43 @@
+//// [higherOrderMappedIndexLookupInference.ts]
+// @strict
+
+function f1(a: <T>() => keyof T, b: <U>() => keyof U) {
+    a = b;
+    b = a;
+}
+
+function f2(a: <T, K extends keyof T>() => T[K], b: <U, L extends keyof U>() => U[L]) {
+    a = b;
+    b = a;
+}
+
+function f3(a: <T>() => { [K in keyof T]: T[K] }, b: <U>() => { [K in keyof U]: U[K] }) {
+    a = b;
+    b = a;
+}
+
+// Repro from #18338
+
+type IdMapped<T> = { [K in keyof T]: T[K] }
+
+declare const f: <T>() => IdMapped<T>;
+declare const g: <U>() => { [K in keyof U]: U[K] };
+
+const h: typeof g = f;
+
+
+//// [higherOrderMappedIndexLookupInference.js]
+// @strict
+function f1(a, b) {
+    a = b;
+    b = a;
+}
+function f2(a, b) {
+    a = b;
+    b = a;
+}
+function f3(a, b) {
+    a = b;
+    b = a;
+}
+var h = f;
diff --git a/tests/baselines/reference/higherOrderMappedIndexLookupInference.symbols b/tests/baselines/reference/higherOrderMappedIndexLookupInference.symbols
new file mode 100644
index 0000000000000..44ecb1e6d0219
--- /dev/null
+++ b/tests/baselines/reference/higherOrderMappedIndexLookupInference.symbols
@@ -0,0 +1,98 @@
+=== tests/cases/compiler/higherOrderMappedIndexLookupInference.ts ===
+// @strict
+
+function f1(a: <T>() => keyof T, b: <U>() => keyof U) {
+>f1 : Symbol(f1, Decl(higherOrderMappedIndexLookupInference.ts, 0, 0))
+>a : Symbol(a, Decl(higherOrderMappedIndexLookupInference.ts, 2, 12))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 2, 16))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 2, 16))
+>b : Symbol(b, Decl(higherOrderMappedIndexLookupInference.ts, 2, 32))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 2, 37))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 2, 37))
+
+    a = b;
+>a : Symbol(a, Decl(higherOrderMappedIndexLookupInference.ts, 2, 12))
+>b : Symbol(b, Decl(higherOrderMappedIndexLookupInference.ts, 2, 32))
+
+    b = a;
+>b : Symbol(b, Decl(higherOrderMappedIndexLookupInference.ts, 2, 32))
+>a : Symbol(a, Decl(higherOrderMappedIndexLookupInference.ts, 2, 12))
+}
+
+function f2(a: <T, K extends keyof T>() => T[K], b: <U, L extends keyof U>() => U[L]) {
+>f2 : Symbol(f2, Decl(higherOrderMappedIndexLookupInference.ts, 5, 1))
+>a : Symbol(a, Decl(higherOrderMappedIndexLookupInference.ts, 7, 12))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 7, 16))
+>K : Symbol(K, Decl(higherOrderMappedIndexLookupInference.ts, 7, 18))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 7, 16))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 7, 16))
+>K : Symbol(K, Decl(higherOrderMappedIndexLookupInference.ts, 7, 18))
+>b : Symbol(b, Decl(higherOrderMappedIndexLookupInference.ts, 7, 48))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 7, 53))
+>L : Symbol(L, Decl(higherOrderMappedIndexLookupInference.ts, 7, 55))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 7, 53))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 7, 53))
+>L : Symbol(L, Decl(higherOrderMappedIndexLookupInference.ts, 7, 55))
+
+    a = b;
+>a : Symbol(a, Decl(higherOrderMappedIndexLookupInference.ts, 7, 12))
+>b : Symbol(b, Decl(higherOrderMappedIndexLookupInference.ts, 7, 48))
+
+    b = a;
+>b : Symbol(b, Decl(higherOrderMappedIndexLookupInference.ts, 7, 48))
+>a : Symbol(a, Decl(higherOrderMappedIndexLookupInference.ts, 7, 12))
+}
+
+function f3(a: <T>() => { [K in keyof T]: T[K] }, b: <U>() => { [K in keyof U]: U[K] }) {
+>f3 : Symbol(f3, Decl(higherOrderMappedIndexLookupInference.ts, 10, 1))
+>a : Symbol(a, Decl(higherOrderMappedIndexLookupInference.ts, 12, 12))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 12, 16))
+>K : Symbol(K, Decl(higherOrderMappedIndexLookupInference.ts, 12, 27))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 12, 16))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 12, 16))
+>K : Symbol(K, Decl(higherOrderMappedIndexLookupInference.ts, 12, 27))
+>b : Symbol(b, Decl(higherOrderMappedIndexLookupInference.ts, 12, 49))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 12, 54))
+>K : Symbol(K, Decl(higherOrderMappedIndexLookupInference.ts, 12, 65))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 12, 54))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 12, 54))
+>K : Symbol(K, Decl(higherOrderMappedIndexLookupInference.ts, 12, 65))
+
+    a = b;
+>a : Symbol(a, Decl(higherOrderMappedIndexLookupInference.ts, 12, 12))
+>b : Symbol(b, Decl(higherOrderMappedIndexLookupInference.ts, 12, 49))
+
+    b = a;
+>b : Symbol(b, Decl(higherOrderMappedIndexLookupInference.ts, 12, 49))
+>a : Symbol(a, Decl(higherOrderMappedIndexLookupInference.ts, 12, 12))
+}
+
+// Repro from #18338
+
+type IdMapped<T> = { [K in keyof T]: T[K] }
+>IdMapped : Symbol(IdMapped, Decl(higherOrderMappedIndexLookupInference.ts, 15, 1))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 19, 14))
+>K : Symbol(K, Decl(higherOrderMappedIndexLookupInference.ts, 19, 22))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 19, 14))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 19, 14))
+>K : Symbol(K, Decl(higherOrderMappedIndexLookupInference.ts, 19, 22))
+
+declare const f: <T>() => IdMapped<T>;
+>f : Symbol(f, Decl(higherOrderMappedIndexLookupInference.ts, 21, 13))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 21, 18))
+>IdMapped : Symbol(IdMapped, Decl(higherOrderMappedIndexLookupInference.ts, 15, 1))
+>T : Symbol(T, Decl(higherOrderMappedIndexLookupInference.ts, 21, 18))
+
+declare const g: <U>() => { [K in keyof U]: U[K] };
+>g : Symbol(g, Decl(higherOrderMappedIndexLookupInference.ts, 22, 13))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 22, 18))
+>K : Symbol(K, Decl(higherOrderMappedIndexLookupInference.ts, 22, 29))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 22, 18))
+>U : Symbol(U, Decl(higherOrderMappedIndexLookupInference.ts, 22, 18))
+>K : Symbol(K, Decl(higherOrderMappedIndexLookupInference.ts, 22, 29))
+
+const h: typeof g = f;
+>h : Symbol(h, Decl(higherOrderMappedIndexLookupInference.ts, 24, 5))
+>g : Symbol(g, Decl(higherOrderMappedIndexLookupInference.ts, 22, 13))
+>f : Symbol(f, Decl(higherOrderMappedIndexLookupInference.ts, 21, 13))
+
diff --git a/tests/baselines/reference/higherOrderMappedIndexLookupInference.types b/tests/baselines/reference/higherOrderMappedIndexLookupInference.types
new file mode 100644
index 0000000000000..f1c2b0aa17ba4
--- /dev/null
+++ b/tests/baselines/reference/higherOrderMappedIndexLookupInference.types
@@ -0,0 +1,104 @@
+=== tests/cases/compiler/higherOrderMappedIndexLookupInference.ts ===
+// @strict
+
+function f1(a: <T>() => keyof T, b: <U>() => keyof U) {
+>f1 : (a: <T>() => keyof T, b: <U>() => keyof U) => void
+>a : <T>() => keyof T
+>T : T
+>T : T
+>b : <U>() => keyof U
+>U : U
+>U : U
+
+    a = b;
+>a = b : <U>() => keyof U
+>a : <T>() => keyof T
+>b : <U>() => keyof U
+
+    b = a;
+>b = a : <T>() => keyof T
+>b : <U>() => keyof U
+>a : <T>() => keyof T
+}
+
+function f2(a: <T, K extends keyof T>() => T[K], b: <U, L extends keyof U>() => U[L]) {
+>f2 : (a: <T, K extends keyof T>() => T[K], b: <U, L extends keyof U>() => U[L]) => void
+>a : <T, K extends keyof T>() => T[K]
+>T : T
+>K : K
+>T : T
+>T : T
+>K : K
+>b : <U, L extends keyof U>() => U[L]
+>U : U
+>L : L
+>U : U
+>U : U
+>L : L
+
+    a = b;
+>a = b : <U, L extends keyof U>() => U[L]
+>a : <T, K extends keyof T>() => T[K]
+>b : <U, L extends keyof U>() => U[L]
+
+    b = a;
+>b = a : <T, K extends keyof T>() => T[K]
+>b : <U, L extends keyof U>() => U[L]
+>a : <T, K extends keyof T>() => T[K]
+}
+
+function f3(a: <T>() => { [K in keyof T]: T[K] }, b: <U>() => { [K in keyof U]: U[K] }) {
+>f3 : (a: <T>() => { [K in keyof T]: T[K]; }, b: <U>() => { [K in keyof U]: U[K]; }) => void
+>a : <T>() => { [K in keyof T]: T[K]; }
+>T : T
+>K : K
+>T : T
+>T : T
+>K : K
+>b : <U>() => { [K in keyof U]: U[K]; }
+>U : U
+>K : K
+>U : U
+>U : U
+>K : K
+
+    a = b;
+>a = b : <U>() => { [K in keyof U]: U[K]; }
+>a : <T>() => { [K in keyof T]: T[K]; }
+>b : <U>() => { [K in keyof U]: U[K]; }
+
+    b = a;
+>b = a : <T>() => { [K in keyof T]: T[K]; }
+>b : <U>() => { [K in keyof U]: U[K]; }
+>a : <T>() => { [K in keyof T]: T[K]; }
+}
+
+// Repro from #18338
+
+type IdMapped<T> = { [K in keyof T]: T[K] }
+>IdMapped : IdMapped<T>
+>T : T
+>K : K
+>T : T
+>T : T
+>K : K
+
+declare const f: <T>() => IdMapped<T>;
+>f : <T>() => IdMapped<T>
+>T : T
+>IdMapped : IdMapped<T>
+>T : T
+
+declare const g: <U>() => { [K in keyof U]: U[K] };
+>g : <U>() => { [K in keyof U]: U[K]; }
+>U : U
+>K : K
+>U : U
+>U : U
+>K : K
+
+const h: typeof g = f;
+>h : <U>() => { [K in keyof U]: U[K]; }
+>g : <U>() => { [K in keyof U]: U[K]; }
+>f : <T>() => IdMapped<T>
+
diff --git a/tests/cases/compiler/higherOrderMappedIndexLookupInference.ts b/tests/cases/compiler/higherOrderMappedIndexLookupInference.ts
new file mode 100644
index 0000000000000..9615628391542
--- /dev/null
+++ b/tests/cases/compiler/higherOrderMappedIndexLookupInference.ts
@@ -0,0 +1,25 @@
+// @strict
+
+function f1(a: <T>() => keyof T, b: <U>() => keyof U) {
+    a = b;
+    b = a;
+}
+
+function f2(a: <T, K extends keyof T>() => T[K], b: <U, L extends keyof U>() => U[L]) {
+    a = b;
+    b = a;
+}
+
+function f3(a: <T>() => { [K in keyof T]: T[K] }, b: <U>() => { [K in keyof U]: U[K] }) {
+    a = b;
+    b = a;
+}
+
+// Repro from #18338
+
+type IdMapped<T> = { [K in keyof T]: T[K] }
+
+declare const f: <T>() => IdMapped<T>;
+declare const g: <U>() => { [K in keyof U]: U[K] };
+
+const h: typeof g = f;