Skip to content

Commit 43887ef

Browse files
committed
[CodeCompletion] Only filter solutions based on number of fixes
rdar://73282163
1 parent e8c76a1 commit 43887ef

File tree

7 files changed

+122
-13
lines changed

7 files changed

+122
-13
lines changed

include/swift/Sema/ConstraintSystem.h

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,10 @@ class TypeVariableType::Implementation {
365365

366366
bool isTypeSequence() const;
367367

368+
/// Determine whether this type variable represents a code completion
369+
/// expression.
370+
bool isCodeCompletionToken() const;
371+
368372
/// Retrieve the representative of the equivalence class to which this
369373
/// type variable belongs.
370374
///
@@ -3032,10 +3036,25 @@ class ConstraintSystem {
30323036
if (solutions.size() < 2)
30333037
return;
30343038

3035-
if (auto best = findBestSolution(solutions, minimize)) {
3036-
if (*best != 0)
3037-
solutions[0] = std::move(solutions[*best]);
3038-
solutions.erase(solutions.begin() + 1, solutions.end());
3039+
if (isForCodeCompletion()) {
3040+
// For code completion remove solutions that have more fixes than the
3041+
// minimum but don't filter based on the lower-priority scores because we
3042+
// want to show all options even if they need to go through more
3043+
// conversions.
3044+
Score minScore = std::min_element(solutions.begin(), solutions.end(),
3045+
[](const Solution &a, const Solution &b) {
3046+
return a.getFixedScore() < b.getFixedScore();
3047+
})->getFixedScore();
3048+
3049+
llvm::erase_if(solutions, [&](const Solution &S) {
3050+
return S.getFixedScore().Data[SK_Fix] > minScore.Data[SK_Fix];
3051+
});
3052+
} else {
3053+
if (auto best = findBestSolution(solutions, minimize)) {
3054+
if (*best != 0)
3055+
solutions[0] = std::move(solutions[*best]);
3056+
solutions.erase(solutions.begin() + 1, solutions.end());
3057+
}
30393058
}
30403059
}
30413060

lib/Sema/CSBindings.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1788,7 +1788,12 @@ bool TypeVarBindingProducer::computeNext() {
17881788
// Allow solving for T even for a binding kind where that's invalid
17891789
// if fixes are allowed, because that gives us the opportunity to
17901790
// match T? values to the T binding by adding an unwrap fix.
1791-
if (binding.Kind == BindingKind::Subtypes || CS.shouldAttemptFixes()) {
1791+
// But don't allow it when solving for code completion because otherwise
1792+
// we might also receive T as a contextual type and we can't differentiate
1793+
// that completion results should only be considered to have a convertible,
1794+
// not identical type relation.
1795+
if ((binding.Kind == BindingKind::Subtypes || CS.shouldAttemptFixes()) &&
1796+
!TypeVar->getImpl().isCodeCompletionToken()) {
17921797
// If we were unsuccessful solving for T?, try solving for T.
17931798
if (auto objTy = type->getOptionalObjectType()) {
17941799
// If T is a type variable, only attempt this if both the

lib/Sema/CSRanking.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,21 @@ bool ConstraintSystem::worseThanBestSolution() const {
130130
if (getASTContext().TypeCheckerOpts.DisableConstraintSolverPerformanceHacks)
131131
return false;
132132

133-
if (!solverState || !solverState->BestScore ||
134-
CurrentScore <= *solverState->BestScore)
133+
if (!solverState || !solverState->BestScore)
135134
return false;
136135

136+
if (isForCodeCompletion()) {
137+
// For code completion, we only filter based on SK_Fix, not the entire
138+
// score. See ConstraintSystem::filterSolutions.
139+
if (CurrentScore.Data[SK_Fix] <= solverState->BestScore->Data[SK_Fix]) {
140+
return false;
141+
}
142+
} else {
143+
if (CurrentScore <= *solverState->BestScore) {
144+
return false;
145+
}
146+
}
147+
137148
if (isDebugMode()) {
138149
llvm::errs().indent(solverState->depth * 2)
139150
<< "(solution is worse than the best solution)\n";

lib/Sema/CSSolver.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,14 @@ Solution ConstraintSystem::finalize() {
7373

7474
// Update the best score we've seen so far.
7575
auto &ctx = getASTContext();
76-
assert(ctx.TypeCheckerOpts.DisableConstraintSolverPerformanceHacks ||
77-
!solverState->BestScore || CurrentScore <= *solverState->BestScore);
76+
if (isForCodeCompletion()) {
77+
assert(ctx.TypeCheckerOpts.DisableConstraintSolverPerformanceHacks ||
78+
!solverState->BestScore ||
79+
CurrentScore.Data[SK_Fix] <= solverState->BestScore->Data[SK_Fix]);
80+
} else {
81+
assert(ctx.TypeCheckerOpts.DisableConstraintSolverPerformanceHacks ||
82+
!solverState->BestScore || CurrentScore <= *solverState->BestScore);
83+
}
7884

7985
if (!solverState->BestScore || CurrentScore <= *solverState->BestScore) {
8086
solverState->BestScore = CurrentScore;

lib/Sema/TypeCheckConstraints.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ bool TypeVariableType::Implementation::isTypeSequence() const {
130130
&& locator->getGenericParameter()->isTypeSequence();
131131
}
132132

133+
bool TypeVariableType::Implementation::isCodeCompletionToken() const {
134+
return locator && locator->directlyAt<CodeCompletionExpr>();
135+
}
136+
133137
void *operator new(size_t bytes, ConstraintSystem& cs,
134138
size_t alignment) {
135139
return cs.getAllocator().Allocate(bytes, alignment);

test/IDE/complete_ambiguous.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,8 @@ struct Struct123: Equatable {
448448
}
449449
func testBestSolutionFilter() {
450450
let a = Struct123();
451-
let b = [Struct123]().first(where: { $0 == a && 1 + 90 * 5 / 8 == 45 * -10 })?.structMem != .#^BEST_SOLUTION_FILTER?xfail=rdar73282163^#
452-
let c = min(10.3, 10 / 10.4) < 6 / 7 ? true : Optional(a)?.structMem != .#^BEST_SOLUTION_FILTER2?check=BEST_SOLUTION_FILTER;xfail=rdar73282163^#
451+
let b = [Struct123]().first(where: { $0 == a && 1 + 90 * 5 / 8 == 45 * -10 })?.structMem != .#^BEST_SOLUTION_FILTER^#
452+
let c = min(10.3, 10 / 10.4) < 6 / 7 ? true : Optional(a)?.structMem != .#^BEST_SOLUTION_FILTER2?check=BEST_SOLUTION_FILTER^#
453453
}
454454

455455
// BEST_SOLUTION_FILTER: Begin completions
@@ -465,7 +465,7 @@ func testBestSolutionGeneric() {
465465
func genAndInt(_ x: Int) -> Int { return 1 }
466466
func genAndInt<T>(_ x: T) -> Test1 { return Test1() }
467467

468-
genAndInt(2).#^BEST_SOLUTION_FILTER_GEN?xfail=rdar73282163^#
468+
genAndInt(2).#^BEST_SOLUTION_FILTER_GEN^#
469469
}
470470

471471
// BEST_SOLUTION_FILTER_GEN: Begin completions

test/IDE/complete_unresolved_members.swift

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,18 @@ class C6 {
259259

260260
class C6 {
261261
func f1(e: SomeEnum1) {
262-
if let x = Optional(e) where x == .#^UNRESOLVED_25?check=UNRESOLVED_3^#
262+
if let x = Optional(e) where x == .#^UNRESOLVED_25^#
263+
// UNRESOLVED_25: Begin completions, 9 items
264+
// UNRESOLVED_25-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: North[#SomeEnum1#];
265+
// UNRESOLVED_25-DAG: Decl[EnumElement]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: South[#SomeEnum1#];
266+
// UNRESOLVED_25-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Invalid]: hash({#(self): SomeEnum1#})[#(into: inout Hasher) -> Void#];
267+
// UNRESOLVED_25-DAG: Keyword[nil]/None/Erase[1]/TypeRelation[Identical]: nil[#SomeEnum1?#]; name=nil
268+
// UNRESOLVED_25-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Identical]: none[#Optional<SomeEnum1>#]; name=none
269+
// UNRESOLVED_25-DAG: Decl[EnumElement]/CurrNominal/IsSystem/TypeRelation[Identical]: some({#SomeEnum1#})[#Optional<SomeEnum1>#];
270+
// UNRESOLVED_25-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: map({#(self): Optional<SomeEnum1>#})[#((SomeEnum1) throws -> U) -> U?#];
271+
// UNRESOLVED_25-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem: flatMap({#(self): Optional<SomeEnum1>#})[#((SomeEnum1) throws -> U?) -> U?#];
272+
// UNRESOLVED_25-DAG: Decl[InstanceMethod]/CurrNominal/IsSystem/TypeRelation[Invalid]: hash({#(self): Optional<SomeEnum1>#})[#(into: inout Hasher) -> Void#];
273+
// UNRESOLVED_25: End completions
263274
}
264275
}
265276
class C7 {}
@@ -721,3 +732,56 @@ func testSameType() {
721732
// SUGAR_TYPE: End completions
722733
}
723734

735+
func testOverloadWithConvertibleArg() {
736+
// This test case used to cause problems because the second overload needs a value to optional conversion, which increases the solution’s score in one of the later compontens.
737+
// We were prematurely filtering the solution because its score is worse than the 0-score of the first overload (which only ignores the second argument, which doesn't increase the score)
738+
struct S {
739+
func xxx(_ foo: Foo) { }
740+
func xxx(_ bar: Bar, _ other: Int?) { }
741+
}
742+
743+
struct Foo {
744+
init(x: String) {}
745+
}
746+
747+
struct Bar {
748+
static let top: Bar = Bar()
749+
}
750+
751+
S().xxx(.#^CONVERTIBLE_ARG_OVERLOAD^#, 32)
752+
// CONVERTIBLE_ARG_OVERLOAD: Begin completions, 2 items
753+
// CONVERTIBLE_ARG_OVERLOAD-DAG: Decl[StaticVar]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: top[#Bar#];
754+
// CONVERTIBLE_ARG_OVERLOAD-DAG: Decl[Constructor]/CurrNominal/TypeRelation[Identical]: init()[#Bar#];
755+
// CONVERTIBLE_ARG_OVERLOAD: End completions
756+
}
757+
758+
func testOverloadWithConvertibleArgWithFixesInScore() {
759+
// same as testOverloadWithConvertibleArg but with solutions that contain a fix-score > 0 because the result builder has no body for the initialization of FStack.
760+
761+
@resultBuilder struct ResBuilder {
762+
static func buildBlock<Content>(_ content: Content) -> Content { fatalError() }
763+
}
764+
765+
struct S<Content> {
766+
init(@ResBuilder content: () -> Content) {}
767+
768+
func xxx(_ foo: Foo) -> Self { return self }
769+
func xxx(_ bar: Bar = .top, _ other: Int?) -> Self { return self }
770+
}
771+
772+
struct Foo {
773+
init(x: String) {}
774+
}
775+
776+
struct Bar {
777+
static let top: Bar = Bar()
778+
}
779+
780+
S() {}
781+
.xxx(.#^CONVERTIBLE_ARG_OVERLOAD_WITH_FIX_IN_SCORE^#top, 32)
782+
// CONVERTIBLE_ARG_OVERLOAD_WITH_FIX_IN_SCORE: Begin completions, 2 items
783+
// CONVERTIBLE_ARG_OVERLOAD_WITH_FIX_IN_SCORE-DAG: Decl[StaticVar]/CurrNominal/Flair[ExprSpecific]/TypeRelation[Identical]: top[#Bar#]; name=top
784+
// CONVERTIBLE_ARG_OVERLOAD_WITH_FIX_IN_SCORE-DAG: Decl[Constructor]/CurrNominal/TypeRelation[Identical]: init()[#Bar#]; name=init()
785+
// CONVERTIBLE_ARG_OVERLOAD_WITH_FIX_IN_SCORE: End completions
786+
787+
}

0 commit comments

Comments
 (0)