Skip to content

Commit 5da0906

Browse files
committed
[CodeCompletion] Add optional top-N results that precede literals
Experiment with having some locals or nominal members come first, but only a few so that the literals are still predictably near the top. Part of rdar://problem/23865118
1 parent 670f193 commit 5da0906

File tree

6 files changed

+156
-7
lines changed

6 files changed

+156
-7
lines changed

test/SourceKit/CodeComplete/complete_sort_order.swift

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ func test() {
4444
// CONTEXT: key.name: "__COLUMN__"
4545

4646
// RUN: %complete-test -tok=STMT_0 %s | FileCheck %s -check-prefix=STMT
47-
// RUN: %complete-test -tok=EXPR_0 %s | FileCheck %s -check-prefix=EXPR
4847
func test1() {
4948
#^STMT_0^#
5049
}
@@ -56,6 +55,7 @@ func test1() {
5655
// STMT: func
5756
// STMT: foo(a: Int)
5857

58+
// RUN: %complete-test -top=0 -tok=EXPR_0 %s | FileCheck %s -check-prefix=EXPR
5959
func test2() {
6060
(#^EXPR_0^#)
6161
}
@@ -70,3 +70,63 @@ func test2() {
7070
// EXPR: (values)
7171
// EXPR: nil
7272
// EXPR: foo(a: Int)
73+
74+
// Top 1
75+
// RUN: %complete-test -top=1 -tok=EXPR_1 %s | FileCheck %s -check-prefix=EXPR_TOP_1
76+
func test3(x: Int) {
77+
let y = x
78+
let z = x
79+
let zzz = x
80+
(#^EXPR_1^#)
81+
}
82+
// EXPR_TOP_1: x
83+
// EXPR_TOP_1: 0
84+
// EXPR_TOP_1: "abc"
85+
// EXPR_TOP_1: true
86+
// EXPR_TOP_1: false
87+
// EXPR_TOP_1: [#Color(colorLiteralRed: Float, green: Float, blue: Float, alpha: Float)#]
88+
// EXPR_TOP_1: [#Image(imageLiteral: String)#]
89+
// EXPR_TOP_1: [values]
90+
// EXPR_TOP_1: [key: value]
91+
// EXPR_TOP_1: (values)
92+
// EXPR_TOP_1: nil
93+
// EXPR_TOP_1: y
94+
// EXPR_TOP_1: z
95+
// EXPR_TOP_1: zzz
96+
97+
// Top 3
98+
// RUN: %complete-test -top=3 -tok=EXPR_2 %s | FileCheck %s -check-prefix=EXPR_TOP_3
99+
func test4(x: Int) {
100+
let y = x
101+
let z = x
102+
let zzz = x
103+
(#^EXPR_2^#)
104+
}
105+
// EXPR_TOP_3: x
106+
// EXPR_TOP_3: y
107+
// EXPR_TOP_3: z
108+
// EXPR_TOP_3: 0
109+
// EXPR_TOP_3: "abc"
110+
// EXPR_TOP_3: true
111+
// EXPR_TOP_3: false
112+
// EXPR_TOP_3: [#Color(colorLiteralRed: Float, green: Float, blue: Float, alpha: Float)#]
113+
// EXPR_TOP_3: [#Image(imageLiteral: String)#]
114+
// EXPR_TOP_3: [values]
115+
// EXPR_TOP_3: [key: value]
116+
// EXPR_TOP_3: (values)
117+
// EXPR_TOP_3: nil
118+
// EXPR_TOP_3: zzz
119+
120+
// Top 3 with type matching
121+
// RUN: %complete-test -top=3 -tok=EXPR_3 %s | FileCheck %s -check-prefix=EXPR_TOP_3_TYPE_MATCH
122+
func test4(x: Int) {
123+
let y: String = ""
124+
let z: String = y
125+
let zzz = x
126+
let bar: Int = #^EXPR_3^#
127+
}
128+
// EXPR_TOP_3_TYPE_MATCH: x
129+
// EXPR_TOP_3_TYPE_MATCH: zzz
130+
// EXPR_TOP_3_TYPE_MATCH: 0
131+
// EXPR_TOP_3_TYPE_MATCH: y
132+
// EXPR_TOP_3_TYPE_MATCH: z

test/SourceKit/CodeComplete/complete_type_match.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// RUN: %complete-test -tok=TOP_LEVEL_0 %s | FileCheck %s -check-prefix=TOP_LEVEL_0
2-
// RUN: %complete-test -tok=TOP_LEVEL_1 %s | FileCheck %s -check-prefix=TOP_LEVEL_1
3-
// RUN: %complete-test -tok=TOP_LEVEL_2 %s | FileCheck %s -check-prefix=TOP_LEVEL_2
4-
// RUN: %complete-test -tok=TOP_LEVEL_3 %s | FileCheck %s -check-prefix=TOP_LEVEL_3
5-
// RUN: %complete-test -group=none -tok=CROSS_CONTEXT_0 %s | FileCheck %s -check-prefix=CROSS_CONTEXT_0
6-
// RUN: %complete-test -group=none -tok=FROM_METHOD_0 %s | FileCheck %s -check-prefix=FROM_METHOD_0
1+
// RUN: %complete-test -top=0 -tok=TOP_LEVEL_0 %s | FileCheck %s -check-prefix=TOP_LEVEL_0
2+
// RUN: %complete-test -top=0 -tok=TOP_LEVEL_1 %s | FileCheck %s -check-prefix=TOP_LEVEL_1
3+
// RUN: %complete-test -top=0 -tok=TOP_LEVEL_2 %s | FileCheck %s -check-prefix=TOP_LEVEL_2
4+
// RUN: %complete-test -top=0 -tok=TOP_LEVEL_3 %s | FileCheck %s -check-prefix=TOP_LEVEL_3
5+
// RUN: %complete-test -top=0 -group=none -tok=CROSS_CONTEXT_0 %s | FileCheck %s -check-prefix=CROSS_CONTEXT_0
6+
// RUN: %complete-test -top=0 -group=none -tok=FROM_METHOD_0 %s | FileCheck %s -check-prefix=FROM_METHOD_0
77

88
let valueA = [0]
99
let valueS = ""

tools/SourceKit/lib/SwiftLang/CodeCompletionOrganizer.cpp

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,78 @@ static int compareLiterals(Item &a_, Item &b_) {
742742
return 0;
743743
}
744744

745+
static bool isTopNonLiteralResult(Item &item, ResultBucket literalBucket) {
746+
if (isa<Group>(item))
747+
return true; // FIXME: should have a semantic context for groups.
748+
auto *completion = cast<Result>(item).value;
749+
750+
switch (literalBucket) {
751+
case ResultBucket::Literal:
752+
return completion->getSemanticContext() <=
753+
SemanticContextKind::CurrentNominal;
754+
case ResultBucket::LiteralTypeMatch:
755+
return completion->getExpectedTypeRelation() >= Completion::Convertible;
756+
default:
757+
llvm_unreachable("invalid literal bucket");
758+
}
759+
}
760+
761+
static void sortTopN(const Options &options, Group *group,
762+
bool hasExpectedTypes) {
763+
764+
auto &contents = group->contents;
765+
if (contents.empty() || options.showTopNonLiteralResults == 0)
766+
return;
767+
768+
auto best = getResultBucket(*contents[0], hasExpectedTypes);
769+
if (best == ResultBucket::LiteralTypeMatch || best == ResultBucket::Literal) {
770+
771+
unsigned beginNewIndex = 0;
772+
unsigned endNewIndex = 0;
773+
for (unsigned i = 1; i < contents.size(); ++i) {
774+
auto bucket = getResultBucket(*contents[i], hasExpectedTypes);
775+
if (bucket < best) {
776+
// This algorithm assumes we don't have both literal and
777+
// literal-type-match at the start of the list.
778+
assert(bucket != ResultBucket::Literal);
779+
if (isTopNonLiteralResult(*contents[i], best)) {
780+
beginNewIndex = i;
781+
endNewIndex = beginNewIndex + 1;
782+
for (; endNewIndex < contents.size() &&
783+
endNewIndex < beginNewIndex + options.showTopNonLiteralResults;
784+
++endNewIndex) {
785+
if (!isTopNonLiteralResult(*contents[endNewIndex], best))
786+
break;
787+
}
788+
}
789+
break;
790+
}
791+
}
792+
793+
if (!beginNewIndex)
794+
return;
795+
796+
assert(endNewIndex > beginNewIndex && endNewIndex < contents.size());
797+
798+
// Temporarily copy the first result to temporary storage.
799+
SmallVector<Item *, 16> firstResults;
800+
for (unsigned i = 0; i < endNewIndex; ++i) {
801+
firstResults.push_back(contents[i].release());
802+
}
803+
804+
// Swap the literals with the next few results.
805+
for (unsigned ci = 0, i = beginNewIndex; i < endNewIndex; ++i, ++ci) {
806+
assert(ci < contents.size() && !contents[ci]);
807+
contents[ci] = std::unique_ptr<Item>(firstResults[i]);
808+
}
809+
unsigned topN = endNewIndex - beginNewIndex;
810+
for (unsigned ci = topN, i = 0; i < beginNewIndex; ++i, ++ci) {
811+
assert(ci < contents.size() && !contents[ci]);
812+
contents[ci] = std::unique_ptr<Item>(firstResults[i]);
813+
}
814+
}
815+
}
816+
745817
static void sortRecursive(const Options &options, Group *group,
746818
bool hasExpectedTypes) {
747819
// Sort all of the subgroups first, and fill in the bucket for each result.
@@ -805,6 +877,8 @@ static void sortRecursive(const Options &options, Group *group,
805877

806878
void CodeCompletionOrganizer::Impl::sort(Options options) {
807879
sortRecursive(options, rootGroup.get(), completionHasExpectedTypes);
880+
if (options.showTopNonLiteralResults != 0)
881+
sortTopN(options, rootGroup.get(), completionHasExpectedTypes);
808882
}
809883

810884
void CodeCompletionOrganizer::Impl::groupStemsRecursive(

tools/SourceKit/lib/SwiftLang/CodeCompletionOrganizer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ struct Options {
3939
bool hideByNameStyle = true;
4040
bool fuzzyMatching = true;
4141
unsigned minFuzzyLength = 2;
42+
unsigned showTopNonLiteralResults = 3;
4243

4344
// Options for combining priorities. The defaults are chosen so that a fuzzy
4445
// match just breaks ties within a semantic context. If semanticContextWeight

tools/SourceKit/lib/SwiftLang/SwiftCompletion.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,6 +783,7 @@ static void translateCodeCompletionOptions(OptionsDictionary &from,
783783
static UIdent KeyAddInnerOperators("key.codecomplete.addinneroperators");
784784
static UIdent KeyAddInitsToTopLevel("key.codecomplete.addinitstotoplevel");
785785
static UIdent KeyFuzzyMatching("key.codecomplete.fuzzymatching");
786+
static UIdent KeyTopNonLiteral("key.codecomplete.showtopnonliteralresults");
786787
static UIdent KeyContextWeight("key.codecomplete.sort.contextweight");
787788
static UIdent KeyFuzzyWeight("key.codecomplete.sort.fuzzyweight");
788789
static UIdent KeyPopularityBonus("key.codecomplete.sort.popularitybonus");
@@ -806,6 +807,7 @@ static void translateCodeCompletionOptions(OptionsDictionary &from,
806807
from.valueForOption(KeyFuzzyWeight, to.fuzzyMatchWeight);
807808
from.valueForOption(KeyPopularityBonus, to.popularityBonus);
808809
from.valueForOption(KeyHideByName, to.hideByNameStyle);
810+
from.valueForOption(KeyTopNonLiteral, to.showTopNonLiteralResults);
809811
}
810812

811813
static void translateFilterRules(ArrayRef<FilterRule> rawFilterRules,

tools/SourceKit/tools/complete-test/complete-test.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ struct TestOptions {
4747
Optional<unsigned> hideUnderscores;
4848
Optional<bool> hideByName;
4949
Optional<bool> hideLowPriority;
50+
Optional<unsigned> showTopNonLiteral;
5051
Optional<bool> fuzzyMatching;
5152
Optional<unsigned> fuzzyWeight;
5253
Optional<unsigned> popularityBonus;
@@ -91,6 +92,7 @@ static sourcekitd_uid_t KeyAddInitsToTopLevel;
9192
static sourcekitd_uid_t KeyFuzzyMatching;
9293
static sourcekitd_uid_t KeyFuzzyWeight;
9394
static sourcekitd_uid_t KeyPopularityBonus;
95+
static sourcekitd_uid_t KeyTopNonLiteral;
9496
static sourcekitd_uid_t KeyKind;
9597
static sourcekitd_uid_t KeyResults;
9698
static sourcekitd_uid_t KeyPopular;
@@ -224,6 +226,13 @@ static bool parseOptions(ArrayRef<const char *> args, TestOptions &options,
224226
options.unpopularAPI = value;
225227
} else if (opt == "filter-rules") {
226228
options.filterRulesJSON = value;
229+
} else if (opt == "top") {
230+
unsigned uval;
231+
if (value.getAsInteger(10, uval)) {
232+
error = "unrecognized integer value for -tope=";
233+
return false;
234+
}
235+
options.showTopNonLiteral = uval;
227236
}
228237
}
229238

@@ -302,6 +311,8 @@ static int skt_main(int argc, const char **argv) {
302311
sourcekitd_uid_get_from_cstr("key.codecomplete.sort.fuzzyweight");
303312
KeyPopularityBonus =
304313
sourcekitd_uid_get_from_cstr("key.codecomplete.sort.popularitybonus");
314+
KeyTopNonLiteral =
315+
sourcekitd_uid_get_from_cstr("key.codecomplete.showtopnonliteralresults");
305316
KeySourceFile = sourcekitd_uid_get_from_cstr("key.sourcefile");
306317
KeySourceText = sourcekitd_uid_get_from_cstr("key.sourcetext");
307318
KeyName = sourcekitd_uid_get_from_cstr("key.name");
@@ -609,6 +620,7 @@ static bool codeCompleteRequest(sourcekitd_uid_t requestUID, const char *name,
609620
addIntOption(KeyHideUnderscores, options.hideUnderscores);
610621
addIntOption(KeyFuzzyWeight, options.fuzzyWeight);
611622
addIntOption(KeyPopularityBonus, options.popularityBonus);
623+
addIntOption(KeyTopNonLiteral, options.showTopNonLiteral);
612624

613625
if (filterText)
614626
sourcekitd_request_dictionary_set_string(opts, KeyFilterText, filterText);

0 commit comments

Comments
 (0)