Merge pull request #14322 from xedin/rdar-36838495-4.1

[4.1][CSSolver] Fix performance regression related to contraction of closure parameters
diff --git a/lib/Sema/CSSolver.cpp b/lib/Sema/CSSolver.cpp
index 66ac650..eb103ef 100644
--- a/lib/Sema/CSSolver.cpp
+++ b/lib/Sema/CSSolver.cpp
@@ -1787,9 +1787,11 @@
     auto &score = bestNonGenericScore->Data;
     // Let's skip generic overload choices only in case if
     // non-generic score indicates that there were no forced
-    // unwrappings of optional(s) and no unavailable overload
-    // choices present in the solution.
-    if (score[SK_ForceUnchecked] == 0 && score[SK_Unavailable] == 0)
+    // unwrappings of optional(s), no unavailable overload
+    // choices present in the solution, and there are no
+    // non-trivial function conversions.
+    if (score[SK_ForceUnchecked] == 0 && score[SK_Unavailable] == 0 &&
+        score[SK_FunctionConversion] == 0)
       return true;
   }
 
diff --git a/lib/Sema/ConstraintGraph.cpp b/lib/Sema/ConstraintGraph.cpp
index 3698f76..3ba0365 100644
--- a/lib/Sema/ConstraintGraph.cpp
+++ b/lib/Sema/ConstraintGraph.cpp
@@ -675,13 +675,39 @@
 
         auto isParamBindingConstraint = kind == ConstraintKind::BindParam;
 
-        // If the parameter is allowed to bind to `inout` let's not
-        // try to contract the edge connecting parameter declaration to
-        // it's use in the body. If parameter declaration is bound to
-        // `inout` it's use has to be bound to `l-value`, which can't
-        // happen once equivalence classes of parameter and argument are merged.
-        if (isParamBindingConstraint && tyvar1->getImpl().canBindToInOut())
-          continue;
+        // If the argument is allowed to bind to `inout`, in general,
+        // it's invalid to contract the edge between argument and parameter,
+        // but if we can prove that there are no possible bindings
+        // which result in attempt to bind `inout` type to argument
+        // type variable, we should go ahead and allow (temporary)
+        // contraction, because that greatly helps with performance.
+        // Such action is valid because argument type variable can
+        // only get its bindings from related overload, which gives
+        // us enough information to decided on l-valueness.
+        if (isParamBindingConstraint && tyvar1->getImpl().canBindToInOut()) {
+          bool isNotContractable = true;
+          if (auto bindings = CS.getPotentialBindings(tyvar1)) {
+            for (auto &binding : bindings.Bindings) {
+              auto type = binding.BindingType;
+              isNotContractable = type.findIf([&](Type nestedType) -> bool {
+                if (auto tv = nestedType->getAs<TypeVariableType>()) {
+                  if (!tv->getImpl().mustBeMaterializable())
+                    return true;
+                }
+
+                return nestedType->is<InOutType>();
+              });
+
+              // If there is at least one non-contractable binding, let's
+              // not risk contracting this edge.
+              if (isNotContractable)
+                break;
+            }
+          }
+
+          if (isNotContractable)
+            continue;
+        }
 
         auto rep1 = CS.getRepresentative(tyvar1);
         auto rep2 = CS.getRepresentative(tyvar2);
diff --git a/validation-test/Sema/type_checker_perf/fast/rdar36838495.swift b/validation-test/Sema/type_checker_perf/fast/rdar36838495.swift
new file mode 100644
index 0000000..a9f7cd5
--- /dev/null
+++ b/validation-test/Sema/type_checker_perf/fast/rdar36838495.swift
@@ -0,0 +1,36 @@
+// RUN: %target-typecheck-verify-swift -solver-expression-time-threshold=1
+// REQUIRES: tools-release,no_asserts
+
+struct T {
+  enum B {
+    case a
+    case b
+  }
+
+  let name: String
+  let b: B
+}
+
+struct A {
+  enum C {
+    case a
+    case b
+    case c
+  }
+
+  let name: String
+  let t: T
+  let c: C
+
+  var isX: Bool {
+    return self.t.b == .a
+  }
+}
+
+let x: [String: A] = [:]
+let _ = x.values.filter { $0.isX }
+                .filter { $0.t.b != .a }
+                .filter { $0.c == .a || $0.c == .b }
+                .filter { $0.isX }
+                .filter { $0.t.b != .a }
+                .sorted { $0.name < $1.name }