From ebad04f1337a1239b788ab6503d619814b373dc4 Mon Sep 17 00:00:00 2001 From: Hamish Knight Date: Mon, 1 Sep 2025 10:56:45 +0100 Subject: [PATCH] [CS] Avoid increasing score for unbound outer type vars for completion When doing code completion it's entirely expected we'll end up with ambiguities in the body of a closure if we're completing e.g `someOverloadedFn(#^CC^#)`. As such we don't want to penalize the solution for unbound type variables outside of the body since that will prevent us from being able to eagerly prune e.g disfavored overloads in the outer scope. This gives up to a 7% perf win in the stress tester. --- lib/Sema/CSStep.cpp | 10 +++++++- test/IDE/complete_closure_ambiguity.swift | 31 +++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 test/IDE/complete_closure_ambiguity.swift diff --git a/lib/Sema/CSStep.cpp b/lib/Sema/CSStep.cpp index 45c2e10d20bd3..a13787aa5227b 100644 --- a/lib/Sema/CSStep.cpp +++ b/lib/Sema/CSStep.cpp @@ -981,7 +981,15 @@ StepResult ConjunctionStep::resume(bool prevFailed) { ++numHoles; } } - CS.increaseScore(SK_Hole, Conjunction->getLocator(), numHoles); + // Increase the score for each hole we bind. Avoid doing this for + // completion since it's entirely expected we'll end up with + // ambiguities in the body of a closure if we're completing e.g + // `someOverloadedFn(#^CC^#)`. As such we don't want to penalize the + // solution for unbound type variables outside of the body since + // that will prevent us from being able to eagerly prune e.g + // disfavored overloads in the outer scope. + if (!CS.isForCodeCompletion()) + CS.increaseScore(SK_Hole, Conjunction->getLocator(), numHoles); } if (CS.worseThanBestSolution()) diff --git a/test/IDE/complete_closure_ambiguity.swift b/test/IDE/complete_closure_ambiguity.swift new file mode 100644 index 0000000000000..a1daa7be26079 --- /dev/null +++ b/test/IDE/complete_closure_ambiguity.swift @@ -0,0 +1,31 @@ +// RUN: %empty-directory(%t) +// RUN: %batch-code-completion -debug-constraints 2> %t/constraints.log +// RUN: %FileCheck %s -check-prefix CONSTRAINTS < %t/constraints.log +// RUN: %FileCheck %s -check-prefix CONSTRAINTS-NOT < %t/constraints.log + +protocol P1 {} +protocol P2 {} + +func foo(_ fn: () -> T) {} + +@_disfavoredOverload +func foo(_ fn: () -> T) {} + +func bar(_ x: Int) -> Int {} +func bar(_ x: String) -> String {} + +// Make sure we eagerly prune the disfavored overload of 'foo', despite the +// ambiguity in the closure body. +foo { + let x = bar(#^COMPLETE^#) + // COMPLETE: Decl[FreeFunction]/CurrModule/Flair[ArgLabels]: ['(']{#(x): Int#}[')'][#Int#]; name=: + // COMPLETE: Decl[FreeFunction]/CurrModule/Flair[ArgLabels]: ['(']{#(x): String#}[')'][#String#]; name=: + + return x +} + +// CONSTRAINTS: attempting disjunction choice {{.*}}:12:6 +// CONSTRAINTS: increasing 'disfavored overload' score +// CONSTRAINTS: solution is worse than the best solution + +// CONSTRAINTS-NOT-NOT: increasing 'hole'