Skip to content

Commit c07e1c6

Browse files
muirdmstamblerre
authored andcommitted
internal/lsp: improve variadic completion
Improve candidate ranking when completing the variadic parameter of function calls. Using the example: func foo(strs ...string) {} - When completing foo(<>), we prefer candidates of type []string or string (previously we only preferred []string). - When completing foo("hi", <>), we prefer candidates of type string (previously we preferred []string). - When completing foo(<>), we use a snippet to add on the "..." automatically to candidates of type []string. I also fixed completion tests to work properly when you have multiple notes referring to the same position. For example: foo() //@rank(")", a, b),rank(")", a, c) Previously the second "rank" was silently overwriting the first because they both refer to the same ")". Fixes golang/go#34334. Change-Id: I4f64be44a4ccbb533fb7682738c759cbca3a93cd Reviewed-on: https://go-review.googlesource.com/c/tools/+/205117 Reviewed-by: Rebecca Stambler <[email protected]> Run-TryBot: Rebecca Stambler <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent 3113a4a commit c07e1c6

File tree

5 files changed

+144
-72
lines changed

5 files changed

+144
-72
lines changed

internal/lsp/source/completion.go

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,16 +1101,41 @@ Nodes:
11011101

11021102
if tv, ok := c.pkg.GetTypesInfo().Types[node.Fun]; ok {
11031103
if sig, ok := tv.Type.(*types.Signature); ok {
1104-
if sig.Params().Len() == 0 {
1104+
numParams := sig.Params().Len()
1105+
if numParams == 0 {
11051106
return inf
11061107
}
1107-
i := indexExprAtPos(c.pos, node.Args)
1108+
1109+
var (
1110+
exprIdx = indexExprAtPos(c.pos, node.Args)
1111+
isLastParam = exprIdx == numParams-1
1112+
beyondLastParam = exprIdx >= numParams
1113+
)
1114+
1115+
if sig.Variadic() {
1116+
// If we are beyond the last param or we are the last
1117+
// param w/ further expressions, we expect a single
1118+
// variadic item.
1119+
if beyondLastParam || isLastParam && len(node.Args) > numParams {
1120+
inf.objType = sig.Params().At(numParams - 1).Type().(*types.Slice).Elem()
1121+
break Nodes
1122+
}
1123+
1124+
// Otherwise if we are at the last param then we are
1125+
// completing the variadic positition (i.e. we expect a
1126+
// slice type []T or an individual item T).
1127+
if isLastParam {
1128+
inf.variadic = true
1129+
}
1130+
}
1131+
11081132
// Make sure not to run past the end of expected parameters.
1109-
if i >= sig.Params().Len() {
1110-
i = sig.Params().Len() - 1
1133+
if beyondLastParam {
1134+
inf.objType = sig.Params().At(numParams - 1).Type()
1135+
} else {
1136+
inf.objType = sig.Params().At(exprIdx).Type()
11111137
}
1112-
inf.objType = sig.Params().At(i).Type()
1113-
inf.variadic = sig.Variadic() && i == sig.Params().Len()-1
1138+
11141139
break Nodes
11151140
}
11161141
}
@@ -1225,6 +1250,12 @@ func (ti typeInference) applyTypeNameModifiers(typ types.Type) types.Type {
12251250
return typ
12261251
}
12271252

1253+
// matchesVariadic returns true if we are completing a variadic
1254+
// parameter and candType is a compatible slice type.
1255+
func (ti typeInference) matchesVariadic(candType types.Type) bool {
1256+
return ti.variadic && types.AssignableTo(ti.objType, candType)
1257+
}
1258+
12281259
// findSwitchStmt returns an *ast.CaseClause's corresponding *ast.SwitchStmt or
12291260
// *ast.TypeSwitchStmt. path should start from the case clause's first ancestor.
12301261
func findSwitchStmt(path []ast.Node, pos token.Pos, c *ast.CaseClause) ast.Stmt {
@@ -1443,6 +1474,13 @@ func (c *completer) matchingCandidate(cand *candidate) bool {
14431474
}
14441475
}
14451476

1477+
// When completing the variadic parameter, say objType matches if
1478+
// []objType matches. This is because you can use []T or T for the
1479+
// variadic parameter.
1480+
if c.expectedType.variadic && typeMatches(types.NewSlice(objType)) {
1481+
return true
1482+
}
1483+
14461484
if c.expectedType.convertibleTo != nil {
14471485
return types.ConvertibleTo(objType, c.expectedType.convertibleTo)
14481486
}

internal/lsp/source/completion_format.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
4949
snip = c.functionCallSnippet(label, params)
5050
results, writeParens := formatResults(sig.Results(), c.qf)
5151
detail = "func" + formatFunction(params, results, writeParens)
52+
53+
// Add variadic "..." if we are using a function result to fill in a variadic parameter.
54+
if sig.Results().Len() == 1 && c.expectedType.matchesVariadic(sig.Results().At(0).Type()) {
55+
snip.WriteText("...")
56+
}
5257
}
5358

5459
switch obj := obj.(type) {
@@ -73,6 +78,12 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
7378
if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall {
7479
expandFuncCall(sig)
7580
}
81+
82+
// Add variadic "..." if we are using a variable to fill in a variadic parameter.
83+
if c.expectedType.matchesVariadic(obj.Type()) {
84+
snip = &snippet.Builder{}
85+
snip.WriteText(insert + "...")
86+
}
7687
case *types.Func:
7788
sig, ok := obj.Type().Underlying().(*types.Signature)
7889
if !ok {

internal/lsp/testdata/summary.txt.golden

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
-- summary --
2-
CompletionsCount = 213
3-
CompletionSnippetCount = 40
2+
CompletionsCount = 214
3+
CompletionSnippetCount = 43
44
UnimportedCompletionsCount = 3
55
DeepCompletionsCount = 5
66
FuzzyCompletionsCount = 7
7-
RankedCompletionsCount = 8
7+
RankedCompletionsCount = 16
88
CaseSensitiveCompletionsCount = 4
99
DiagnosticsCount = 22
1010
FoldingRangesCount = 2
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package variadic
2+
3+
func foo(i int, strs ...string) {}
4+
5+
func bar() []string { //@item(vFunc, "bar", "func() []string", "func")
6+
return nil
7+
}
8+
9+
func _() {
10+
var (
11+
i int //@item(vInt, "i", "int", "var")
12+
s string //@item(vStr, "s", "string", "var")
13+
ss []string //@item(vStrSlice, "ss", "[]string", "var")
14+
)
15+
16+
foo() //@rank(")", vInt, vStr),rank(")", vInt, vStrSlice)
17+
foo(123, ) //@rank(")", vStr, vInt),rank(")", vStrSlice, vInt)
18+
foo(123, "", ) //@rank(")", vStr, vInt),rank(")", vStr, vStrSlice)
19+
foo(123, , "") //@rank(" ,", vStr, vInt),rank(")", vStr, vStrSlice)
20+
21+
// snippet will add the "..." for you
22+
foo(123, ) //@snippet(")", vStrSlice, "ss...", "ss..."),snippet(")", vFunc, "bar()...", "bar()..."),snippet(")", vStr, "s", "s")
23+
}

internal/lsp/tests/tests.go

Lines changed: 63 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"io/ioutil"
1616
"path/filepath"
1717
"sort"
18+
"strconv"
1819
"strings"
1920
"sync"
2021
"testing"
@@ -39,13 +40,13 @@ var UpdateGolden = flag.Bool("golden", false, "Update golden files")
3940

4041
type Diagnostics map[span.URI][]source.Diagnostic
4142
type CompletionItems map[token.Pos]*source.CompletionItem
42-
type Completions map[span.Span]Completion
43-
type CompletionSnippets map[span.Span]CompletionSnippet
44-
type UnimportedCompletions map[span.Span]Completion
45-
type DeepCompletions map[span.Span]Completion
46-
type FuzzyCompletions map[span.Span]Completion
47-
type CaseSensitiveCompletions map[span.Span]Completion
48-
type RankCompletions map[span.Span]Completion
43+
type Completions map[span.Span][]Completion
44+
type CompletionSnippets map[span.Span][]CompletionSnippet
45+
type UnimportedCompletions map[span.Span][]Completion
46+
type DeepCompletions map[span.Span][]Completion
47+
type FuzzyCompletions map[span.Span][]Completion
48+
type CaseSensitiveCompletions map[span.Span][]Completion
49+
type RankCompletions map[span.Span][]Completion
4950
type FoldingRanges []span.Span
5051
type Formats []span.Span
5152
type Imports []span.Span
@@ -343,80 +344,67 @@ func Run(t *testing.T, tests Tests, data *Data) {
343344
t.Helper()
344345
checkData(t, data)
345346

346-
t.Run("Completion", func(t *testing.T) {
347+
eachCompletion := func(t *testing.T, cases map[span.Span][]Completion, test func(*testing.T, span.Span, Completion, CompletionItems)) {
347348
t.Helper()
348-
for src, test := range data.Completions {
349-
t.Run(spanName(src), func(t *testing.T) {
350-
t.Helper()
351-
tests.Completion(t, src, test, data.CompletionItems)
352-
})
349+
350+
for src, exp := range cases {
351+
for i, e := range exp {
352+
t.Run(spanName(src)+"_"+strconv.Itoa(i), func(t *testing.T) {
353+
t.Helper()
354+
test(t, src, e, data.CompletionItems)
355+
})
356+
}
357+
353358
}
359+
}
360+
361+
t.Run("Completion", func(t *testing.T) {
362+
t.Helper()
363+
eachCompletion(t, data.Completions, tests.Completion)
354364
})
355365

356366
t.Run("CompletionSnippets", func(t *testing.T) {
357367
t.Helper()
358368
for _, placeholders := range []bool{true, false} {
359-
for src, expected := range data.CompletionSnippets {
360-
name := spanName(src)
361-
if placeholders {
362-
name += "_placeholders"
369+
for src, expecteds := range data.CompletionSnippets {
370+
for i, expected := range expecteds {
371+
name := spanName(src) + "_" + strconv.Itoa(i+1)
372+
if placeholders {
373+
name += "_placeholders"
374+
}
375+
376+
t.Run(name, func(t *testing.T) {
377+
t.Helper()
378+
tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems)
379+
})
363380
}
364-
t.Run(name, func(t *testing.T) {
365-
t.Helper()
366-
tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems)
367-
})
368381
}
369382
}
370383
})
371384

372385
t.Run("UnimportedCompletion", func(t *testing.T) {
373386
t.Helper()
374-
for src, test := range data.UnimportedCompletions {
375-
t.Run(spanName(src), func(t *testing.T) {
376-
t.Helper()
377-
tests.UnimportedCompletion(t, src, test, data.CompletionItems)
378-
})
379-
}
387+
eachCompletion(t, data.UnimportedCompletions, tests.UnimportedCompletion)
380388
})
381389

382390
t.Run("DeepCompletion", func(t *testing.T) {
383391
t.Helper()
384-
for src, test := range data.DeepCompletions {
385-
t.Run(spanName(src), func(t *testing.T) {
386-
t.Helper()
387-
tests.DeepCompletion(t, src, test, data.CompletionItems)
388-
})
389-
}
392+
eachCompletion(t, data.DeepCompletions, tests.DeepCompletion)
390393
})
391394

392395
t.Run("FuzzyCompletion", func(t *testing.T) {
393396
t.Helper()
394-
for src, test := range data.FuzzyCompletions {
395-
t.Run(spanName(src), func(t *testing.T) {
396-
t.Helper()
397-
tests.FuzzyCompletion(t, src, test, data.CompletionItems)
398-
})
399-
}
397+
eachCompletion(t, data.FuzzyCompletions, tests.FuzzyCompletion)
400398
})
401399

402400
t.Run("CaseSensitiveCompletion", func(t *testing.T) {
403401
t.Helper()
404-
for src, test := range data.CaseSensitiveCompletions {
405-
t.Run(spanName(src), func(t *testing.T) {
406-
t.Helper()
407-
tests.CaseSensitiveCompletion(t, src, test, data.CompletionItems)
408-
})
409-
}
402+
eachCompletion(t, data.CaseSensitiveCompletions, tests.CaseSensitiveCompletion)
410403
})
411404

412405
t.Run("RankCompletions", func(t *testing.T) {
413406
t.Helper()
414-
for src, test := range data.RankCompletions {
415-
t.Run(spanName(src), func(t *testing.T) {
416-
t.Helper()
417-
tests.RankCompletion(t, src, test, data.CompletionItems)
418-
})
419-
}
407+
eachCompletion(t, data.RankCompletions, tests.RankCompletion)
420408
})
421409

422410
t.Run("Diagnostics", func(t *testing.T) {
@@ -594,13 +582,25 @@ func checkData(t *testing.T, data *Data) {
594582
}
595583
}
596584

597-
fmt.Fprintf(buf, "CompletionsCount = %v\n", len(data.Completions))
598-
fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", len(data.CompletionSnippets))
599-
fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", len(data.UnimportedCompletions))
600-
fmt.Fprintf(buf, "DeepCompletionsCount = %v\n", len(data.DeepCompletions))
601-
fmt.Fprintf(buf, "FuzzyCompletionsCount = %v\n", len(data.FuzzyCompletions))
602-
fmt.Fprintf(buf, "RankedCompletionsCount = %v\n", len(data.RankCompletions))
603-
fmt.Fprintf(buf, "CaseSensitiveCompletionsCount = %v\n", len(data.CaseSensitiveCompletions))
585+
snippetCount := 0
586+
for _, want := range data.CompletionSnippets {
587+
snippetCount += len(want)
588+
}
589+
590+
countCompletions := func(c map[span.Span][]Completion) (count int) {
591+
for _, want := range c {
592+
count += len(want)
593+
}
594+
return count
595+
}
596+
597+
fmt.Fprintf(buf, "CompletionsCount = %v\n", countCompletions(data.Completions))
598+
fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", snippetCount)
599+
fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", countCompletions(data.UnimportedCompletions))
600+
fmt.Fprintf(buf, "DeepCompletionsCount = %v\n", countCompletions(data.DeepCompletions))
601+
fmt.Fprintf(buf, "FuzzyCompletionsCount = %v\n", countCompletions(data.FuzzyCompletions))
602+
fmt.Fprintf(buf, "RankedCompletionsCount = %v\n", countCompletions(data.RankCompletions))
603+
fmt.Fprintf(buf, "CaseSensitiveCompletionsCount = %v\n", countCompletions(data.CaseSensitiveCompletions))
604604
fmt.Fprintf(buf, "DiagnosticsCount = %v\n", diagnosticsCount)
605605
fmt.Fprintf(buf, "FoldingRangesCount = %v\n", len(data.FoldingRanges))
606606
fmt.Fprintf(buf, "FormatCount = %v\n", len(data.Formats))
@@ -723,10 +723,10 @@ func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg string) {
723723
}
724724

725725
func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
726-
result := func(m map[span.Span]Completion, src span.Span, expected []token.Pos) {
727-
m[src] = Completion{
726+
result := func(m map[span.Span][]Completion, src span.Span, expected []token.Pos) {
727+
m[src] = append(m[src], Completion{
728728
CompletionItems: expected,
729-
}
729+
})
730730
}
731731
switch typ {
732732
case CompletionDeep:
@@ -896,11 +896,11 @@ func (data *Data) collectSignatures(spn span.Span, signature string, activeParam
896896
}
897897

898898
func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain, placeholder string) {
899-
data.CompletionSnippets[spn] = CompletionSnippet{
899+
data.CompletionSnippets[spn] = append(data.CompletionSnippets[spn], CompletionSnippet{
900900
CompletionItem: item,
901901
PlainSnippet: plain,
902902
PlaceholderSnippet: placeholder,
903-
}
903+
})
904904
}
905905

906906
func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) {

0 commit comments

Comments
 (0)