Skip to content

Commit 8a660bb

Browse files
author
Nathan Hawes
committed
[CodeCompletion][CSRanking] Still allow some filtering of soltions when solving for code completion to improve performance
We were previously completely skipping the "best" solution filtering the solver does to make sure we didn't miss any non-best but still viable solutions, as the completions generated from them can make them become the best solution. E.g: struct Foo { let onFoo = 10 } func foo(_ x: Int) -> Int { return 1 } func foo<T>(_ x: T) -> Foo { return Foo() } foo(3).<here> // the "best" solution is the one with the more-specialized foo(x: Int) overload In the example above we shouldn't remove the solution for foo(x: T) even though there is a single "best" solution (`foo(x: Int)`) as picking a completion result generated from it (`onFoo`) would make the foo(x: T) overload the best and only viable solution. Completely skipping this filtering as we were previously doing is overkill though and adversely affects performance. E.g. it makes sense to filter out and stop exploring solutions with overload choices for foo that required fixes for missing arguments if there is another solution with an overload choice that didn't require any fixes. This patch restores best solution filtering during code completion and instead updates the compareSolutions function it compare solutions based purely on their fixed score. Resolves rdar://problem/73282163
1 parent 6e0267f commit 8a660bb

File tree

3 files changed

+61
-8
lines changed

3 files changed

+61
-8
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2787,7 +2787,7 @@ class ConstraintSystem {
27872787
void
27882788
filterSolutions(SmallVectorImpl<Solution> &solutions,
27892789
bool minimize = false) {
2790-
if (solutions.size() < 2 || isForCodeCompletion())
2790+
if (solutions.size() < 2)
27912791
return;
27922792

27932793
if (auto best = findBestSolution(solutions, minimize)) {
@@ -4803,9 +4803,11 @@ class ConstraintSystem {
48034803
/// \param diff The differences among the solutions.
48044804
/// \param idx1 The index of the first solution.
48054805
/// \param idx2 The index of the second solution.
4806+
/// \param isForCodeCompletion Whether solving for code completion.
48064807
static SolutionCompareResult
48074808
compareSolutions(ConstraintSystem &cs, ArrayRef<Solution> solutions,
4808-
const SolutionDiff &diff, unsigned idx1, unsigned idx2);
4809+
const SolutionDiff &diff, unsigned idx1, unsigned idx2,
4810+
bool isForCodeCompletion);
48094811

48104812
public:
48114813
/// Increase the score of the given kind for the current (partial) solution

lib/Sema/CSRanking.cpp

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -757,14 +757,48 @@ static void addKeyPathDynamicMemberOverloads(
757757
}
758758
}
759759

760+
SolutionCompareResult compareSolutionsForCodeCompletion(
761+
ConstraintSystem &cs, ArrayRef<Solution> solutions, unsigned idx1,
762+
unsigned idx2) {
763+
764+
// When solving for code completion we can't consider one solution worse than
765+
// another according to the same rules as regular compilation. For example,
766+
// with the code below:
767+
//
768+
// func foo(_ x: Int) -> Int {}
769+
// func foo<T>(_ x: T) -> String {}
770+
// foo(3).<complete here> // Still want solutions with for both foo
771+
// // overloads - String and Int members are both
772+
// // valid here.
773+
//
774+
// the comparison for regular compilation considers the solution with the more
775+
// specialized `foo` overload `foo(_: Int)` to be better than the solution
776+
// with the generic overload `foo(_: T)` even though both are otherwise
777+
// viable. For code completion purposes offering members of 'String' based
778+
// on the solution with the generic overload is equally as import as offering
779+
// members of 'Int' as choosing one of those completions will then result in
780+
// regular compilation resolving the call to the generic overload instead.
781+
782+
if (solutions[idx1].getFixedScore() == solutions[idx2].getFixedScore())
783+
return SolutionCompareResult::Incomparable;
784+
return solutions[idx1].getFixedScore() < solutions[idx2].getFixedScore()
785+
? SolutionCompareResult::Better
786+
: SolutionCompareResult::Worse;
787+
}
788+
789+
760790
SolutionCompareResult ConstraintSystem::compareSolutions(
761791
ConstraintSystem &cs, ArrayRef<Solution> solutions,
762-
const SolutionDiff &diff, unsigned idx1, unsigned idx2) {
792+
const SolutionDiff &diff, unsigned idx1, unsigned idx2,
793+
bool isForCodeCompletion) {
763794
if (cs.isDebugMode()) {
764795
llvm::errs().indent(cs.solverState->depth * 2)
765796
<< "comparing solutions " << idx1 << " and " << idx2 <<"\n";
766797
}
767798

799+
if (isForCodeCompletion)
800+
return compareSolutionsForCodeCompletion(cs, solutions, idx1, idx2);
801+
768802
// Whether the solutions are identical.
769803
bool identical = true;
770804

@@ -800,7 +834,7 @@ SolutionCompareResult ConstraintSystem::compareSolutions(
800834

801835
SmallVector<SolutionDiff::OverloadDiff, 4> overloadDiff(diff.overloads);
802836
// Single type of keypath dynamic member lookup could refer to different
803-
// member overlaods, we have to do a pair-wise comparison in such cases
837+
// member overloads, we have to do a pair-wise comparison in such cases
804838
// otherwise ranking would miss some viable information e.g.
805839
// `_ = arr[0..<3]` could refer to subscript through writable or read-only
806840
// key path and each of them could also pick overload which returns `Slice<T>`
@@ -1300,7 +1334,8 @@ ConstraintSystem::findBestSolution(SmallVectorImpl<Solution> &viable,
13001334
SmallVector<bool, 16> losers(viable.size(), false);
13011335
unsigned bestIdx = 0;
13021336
for (unsigned i = 1, n = viable.size(); i != n; ++i) {
1303-
switch (compareSolutions(*this, viable, diff, i, bestIdx)) {
1337+
switch (compareSolutions(*this, viable, diff, i, bestIdx,
1338+
isForCodeCompletion())) {
13041339
case SolutionCompareResult::Identical:
13051340
// FIXME: Might want to warn about this in debug builds, so we can
13061341
// find a way to eliminate the redundancy in the search space.
@@ -1324,7 +1359,8 @@ ConstraintSystem::findBestSolution(SmallVectorImpl<Solution> &viable,
13241359
if (i == bestIdx)
13251360
continue;
13261361

1327-
switch (compareSolutions(*this, viable, diff, bestIdx, i)) {
1362+
switch (compareSolutions(*this, viable, diff, bestIdx, i,
1363+
isForCodeCompletion())) {
13281364
case SolutionCompareResult::Identical:
13291365
// FIXME: Might want to warn about this in debug builds, so we can
13301366
// find a way to eliminate the redundancy in the search space.
@@ -1376,7 +1412,8 @@ ConstraintSystem::findBestSolution(SmallVectorImpl<Solution> &viable,
13761412
if (losers[j])
13771413
continue;
13781414

1379-
switch (compareSolutions(*this, viable, diff, i, j)) {
1415+
switch (compareSolutions(*this, viable, diff, i, j,
1416+
isForCodeCompletion())) {
13801417
case SolutionCompareResult::Identical:
13811418
// FIXME: Dub one of these the loser arbitrarily?
13821419
break;

test/IDE/complete_ambiguous.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=REGULAR_MULTICLOSURE_APPLIED | %FileCheck %s --check-prefix=POINT_MEMBER
4949
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=BEST_SOLUTION_FILTER | %FileCheck %s --check-prefix=BEST_SOLUTION_FILTER
5050
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=BEST_SOLUTION_FILTER2 | %FileCheck %s --check-prefix=BEST_SOLUTION_FILTER
51+
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=BEST_SOLUTION_FILTER_GEN | %FileCheck %s --check-prefix=BEST_SOLUTION_FILTER_GEN
5152
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=MISSINGARG_INLINE | %FileCheck %s --check-prefix=MISSINGARG_INLINE
5253
// RUN: %swift-ide-test -code-completion -source-filename %s -code-completion-token=MISSINGARG_TRAILING | %FileCheck %s --check-prefix=MISSINGARG_TRAILING
5354

@@ -495,7 +496,7 @@ enum Enum123 {
495496
struct Struct123: Equatable {
496497
var structMem = Enum123.enumElem
497498
}
498-
func testNoBestSolutionFilter() {
499+
func testBestSolutionFilter() {
499500
let a = Struct123();
500501
let b = [Struct123]().first(where: { $0 == a && 1 + 90 * 5 / 8 == 45 * -10 })?.structMem != .#^BEST_SOLUTION_FILTER^#
501502
let c = min(10.3, 10 / 10.4) < 6 / 7 ? true : Optional(a)?.structMem != .#^BEST_SOLUTION_FILTER2^#
@@ -508,3 +509,16 @@ func testNoBestSolutionFilter() {
508509
// BEST_SOLUTION_FILTER-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Identical]: none[#Optional<Enum123>#]{{; name=.+$}}
509510
// BEST_SOLUTION_FILTER-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Identical]: some({#Enum123#})[#Optional<Enum123>#]{{; name=.+$}}
510511
// BEST_SOLUTION_FILTER: End completions
512+
513+
func testBestSolutionGeneric() {
514+
struct Test1 {}
515+
func genAndInt(_ x: Int) -> Int { return 1 }
516+
func genAndInt<T>(_ x: T) -> Test1 { return Test1() }
517+
518+
genAndInt(2).#^BEST_SOLUTION_FILTER_GEN^#
519+
}
520+
521+
// BEST_SOLUTION_FILTER_GEN: Begin completions
522+
// BEST_SOLUTION_FILTER_GEN-DAG: Keyword[self]/CurrNominal: self[#Int#]; name=self
523+
// BEST_SOLUTION_FILTER_GEN-DAG: Keyword[self]/CurrNominal: self[#Test1#]; name=self
524+
// BEST_SOLUTION_FILTER_GEN: End completions

0 commit comments

Comments
 (0)