mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
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 <rstambler@golang.org> Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
3113a4aab4
commit
c07e1c6ef6
@ -1101,16 +1101,41 @@ Nodes:
|
|||||||
|
|
||||||
if tv, ok := c.pkg.GetTypesInfo().Types[node.Fun]; ok {
|
if tv, ok := c.pkg.GetTypesInfo().Types[node.Fun]; ok {
|
||||||
if sig, ok := tv.Type.(*types.Signature); ok {
|
if sig, ok := tv.Type.(*types.Signature); ok {
|
||||||
if sig.Params().Len() == 0 {
|
numParams := sig.Params().Len()
|
||||||
|
if numParams == 0 {
|
||||||
return inf
|
return inf
|
||||||
}
|
}
|
||||||
i := indexExprAtPos(c.pos, node.Args)
|
|
||||||
// Make sure not to run past the end of expected parameters.
|
var (
|
||||||
if i >= sig.Params().Len() {
|
exprIdx = indexExprAtPos(c.pos, node.Args)
|
||||||
i = sig.Params().Len() - 1
|
isLastParam = exprIdx == numParams-1
|
||||||
|
beyondLastParam = exprIdx >= numParams
|
||||||
|
)
|
||||||
|
|
||||||
|
if sig.Variadic() {
|
||||||
|
// If we are beyond the last param or we are the last
|
||||||
|
// param w/ further expressions, we expect a single
|
||||||
|
// variadic item.
|
||||||
|
if beyondLastParam || isLastParam && len(node.Args) > numParams {
|
||||||
|
inf.objType = sig.Params().At(numParams - 1).Type().(*types.Slice).Elem()
|
||||||
|
break Nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise if we are at the last param then we are
|
||||||
|
// completing the variadic positition (i.e. we expect a
|
||||||
|
// slice type []T or an individual item T).
|
||||||
|
if isLastParam {
|
||||||
|
inf.variadic = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
inf.objType = sig.Params().At(i).Type()
|
|
||||||
inf.variadic = sig.Variadic() && i == sig.Params().Len()-1
|
// Make sure not to run past the end of expected parameters.
|
||||||
|
if beyondLastParam {
|
||||||
|
inf.objType = sig.Params().At(numParams - 1).Type()
|
||||||
|
} else {
|
||||||
|
inf.objType = sig.Params().At(exprIdx).Type()
|
||||||
|
}
|
||||||
|
|
||||||
break Nodes
|
break Nodes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1225,6 +1250,12 @@ func (ti typeInference) applyTypeNameModifiers(typ types.Type) types.Type {
|
|||||||
return typ
|
return typ
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// matchesVariadic returns true if we are completing a variadic
|
||||||
|
// parameter and candType is a compatible slice type.
|
||||||
|
func (ti typeInference) matchesVariadic(candType types.Type) bool {
|
||||||
|
return ti.variadic && types.AssignableTo(ti.objType, candType)
|
||||||
|
}
|
||||||
|
|
||||||
// findSwitchStmt returns an *ast.CaseClause's corresponding *ast.SwitchStmt or
|
// findSwitchStmt returns an *ast.CaseClause's corresponding *ast.SwitchStmt or
|
||||||
// *ast.TypeSwitchStmt. path should start from the case clause's first ancestor.
|
// *ast.TypeSwitchStmt. path should start from the case clause's first ancestor.
|
||||||
func findSwitchStmt(path []ast.Node, pos token.Pos, c *ast.CaseClause) ast.Stmt {
|
func findSwitchStmt(path []ast.Node, pos token.Pos, c *ast.CaseClause) ast.Stmt {
|
||||||
@ -1443,6 +1474,13 @@ func (c *completer) matchingCandidate(cand *candidate) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When completing the variadic parameter, say objType matches if
|
||||||
|
// []objType matches. This is because you can use []T or T for the
|
||||||
|
// variadic parameter.
|
||||||
|
if c.expectedType.variadic && typeMatches(types.NewSlice(objType)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
if c.expectedType.convertibleTo != nil {
|
if c.expectedType.convertibleTo != nil {
|
||||||
return types.ConvertibleTo(objType, c.expectedType.convertibleTo)
|
return types.ConvertibleTo(objType, c.expectedType.convertibleTo)
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,11 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
|||||||
snip = c.functionCallSnippet(label, params)
|
snip = c.functionCallSnippet(label, params)
|
||||||
results, writeParens := formatResults(sig.Results(), c.qf)
|
results, writeParens := formatResults(sig.Results(), c.qf)
|
||||||
detail = "func" + formatFunction(params, results, writeParens)
|
detail = "func" + formatFunction(params, results, writeParens)
|
||||||
|
|
||||||
|
// Add variadic "..." if we are using a function result to fill in a variadic parameter.
|
||||||
|
if sig.Results().Len() == 1 && c.expectedType.matchesVariadic(sig.Results().At(0).Type()) {
|
||||||
|
snip.WriteText("...")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch obj := obj.(type) {
|
switch obj := obj.(type) {
|
||||||
@ -73,6 +78,12 @@ func (c *completer) item(cand candidate) (CompletionItem, error) {
|
|||||||
if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall {
|
if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall {
|
||||||
expandFuncCall(sig)
|
expandFuncCall(sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add variadic "..." if we are using a variable to fill in a variadic parameter.
|
||||||
|
if c.expectedType.matchesVariadic(obj.Type()) {
|
||||||
|
snip = &snippet.Builder{}
|
||||||
|
snip.WriteText(insert + "...")
|
||||||
|
}
|
||||||
case *types.Func:
|
case *types.Func:
|
||||||
sig, ok := obj.Type().Underlying().(*types.Signature)
|
sig, ok := obj.Type().Underlying().(*types.Signature)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
6
internal/lsp/testdata/summary.txt.golden
vendored
6
internal/lsp/testdata/summary.txt.golden
vendored
@ -1,10 +1,10 @@
|
|||||||
-- summary --
|
-- summary --
|
||||||
CompletionsCount = 213
|
CompletionsCount = 214
|
||||||
CompletionSnippetCount = 40
|
CompletionSnippetCount = 43
|
||||||
UnimportedCompletionsCount = 3
|
UnimportedCompletionsCount = 3
|
||||||
DeepCompletionsCount = 5
|
DeepCompletionsCount = 5
|
||||||
FuzzyCompletionsCount = 7
|
FuzzyCompletionsCount = 7
|
||||||
RankedCompletionsCount = 8
|
RankedCompletionsCount = 16
|
||||||
CaseSensitiveCompletionsCount = 4
|
CaseSensitiveCompletionsCount = 4
|
||||||
DiagnosticsCount = 22
|
DiagnosticsCount = 22
|
||||||
FoldingRangesCount = 2
|
FoldingRangesCount = 2
|
||||||
|
23
internal/lsp/testdata/variadic/variadic.go.in
vendored
Normal file
23
internal/lsp/testdata/variadic/variadic.go.in
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package variadic
|
||||||
|
|
||||||
|
func foo(i int, strs ...string) {}
|
||||||
|
|
||||||
|
func bar() []string { //@item(vFunc, "bar", "func() []string", "func")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
var (
|
||||||
|
i int //@item(vInt, "i", "int", "var")
|
||||||
|
s string //@item(vStr, "s", "string", "var")
|
||||||
|
ss []string //@item(vStrSlice, "ss", "[]string", "var")
|
||||||
|
)
|
||||||
|
|
||||||
|
foo() //@rank(")", vInt, vStr),rank(")", vInt, vStrSlice)
|
||||||
|
foo(123, ) //@rank(")", vStr, vInt),rank(")", vStrSlice, vInt)
|
||||||
|
foo(123, "", ) //@rank(")", vStr, vInt),rank(")", vStr, vStrSlice)
|
||||||
|
foo(123, , "") //@rank(" ,", vStr, vInt),rank(")", vStr, vStrSlice)
|
||||||
|
|
||||||
|
// snippet will add the "..." for you
|
||||||
|
foo(123, ) //@snippet(")", vStrSlice, "ss...", "ss..."),snippet(")", vFunc, "bar()...", "bar()..."),snippet(")", vStr, "s", "s")
|
||||||
|
}
|
@ -15,6 +15,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@ -39,13 +40,13 @@ var UpdateGolden = flag.Bool("golden", false, "Update golden files")
|
|||||||
|
|
||||||
type Diagnostics map[span.URI][]source.Diagnostic
|
type Diagnostics map[span.URI][]source.Diagnostic
|
||||||
type CompletionItems map[token.Pos]*source.CompletionItem
|
type CompletionItems map[token.Pos]*source.CompletionItem
|
||||||
type Completions map[span.Span]Completion
|
type Completions map[span.Span][]Completion
|
||||||
type CompletionSnippets map[span.Span]CompletionSnippet
|
type CompletionSnippets map[span.Span][]CompletionSnippet
|
||||||
type UnimportedCompletions map[span.Span]Completion
|
type UnimportedCompletions map[span.Span][]Completion
|
||||||
type DeepCompletions map[span.Span]Completion
|
type DeepCompletions map[span.Span][]Completion
|
||||||
type FuzzyCompletions map[span.Span]Completion
|
type FuzzyCompletions map[span.Span][]Completion
|
||||||
type CaseSensitiveCompletions map[span.Span]Completion
|
type CaseSensitiveCompletions map[span.Span][]Completion
|
||||||
type RankCompletions map[span.Span]Completion
|
type RankCompletions map[span.Span][]Completion
|
||||||
type FoldingRanges []span.Span
|
type FoldingRanges []span.Span
|
||||||
type Formats []span.Span
|
type Formats []span.Span
|
||||||
type Imports []span.Span
|
type Imports []span.Span
|
||||||
@ -343,80 +344,67 @@ func Run(t *testing.T, tests Tests, data *Data) {
|
|||||||
t.Helper()
|
t.Helper()
|
||||||
checkData(t, data)
|
checkData(t, data)
|
||||||
|
|
||||||
|
eachCompletion := func(t *testing.T, cases map[span.Span][]Completion, test func(*testing.T, span.Span, Completion, CompletionItems)) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
for src, exp := range cases {
|
||||||
|
for i, e := range exp {
|
||||||
|
t.Run(spanName(src)+"_"+strconv.Itoa(i), func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
test(t, src, e, data.CompletionItems)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
t.Run("Completion", func(t *testing.T) {
|
t.Run("Completion", func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for src, test := range data.Completions {
|
eachCompletion(t, data.Completions, tests.Completion)
|
||||||
t.Run(spanName(src), func(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
tests.Completion(t, src, test, data.CompletionItems)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CompletionSnippets", func(t *testing.T) {
|
t.Run("CompletionSnippets", func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, placeholders := range []bool{true, false} {
|
for _, placeholders := range []bool{true, false} {
|
||||||
for src, expected := range data.CompletionSnippets {
|
for src, expecteds := range data.CompletionSnippets {
|
||||||
name := spanName(src)
|
for i, expected := range expecteds {
|
||||||
if placeholders {
|
name := spanName(src) + "_" + strconv.Itoa(i+1)
|
||||||
name += "_placeholders"
|
if placeholders {
|
||||||
|
name += "_placeholders"
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
tests.CompletionSnippet(t, src, expected, placeholders, data.CompletionItems)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UnimportedCompletion", func(t *testing.T) {
|
t.Run("UnimportedCompletion", func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for src, test := range data.UnimportedCompletions {
|
eachCompletion(t, data.UnimportedCompletions, tests.UnimportedCompletion)
|
||||||
t.Run(spanName(src), func(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
tests.UnimportedCompletion(t, src, test, data.CompletionItems)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("DeepCompletion", func(t *testing.T) {
|
t.Run("DeepCompletion", func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for src, test := range data.DeepCompletions {
|
eachCompletion(t, data.DeepCompletions, tests.DeepCompletion)
|
||||||
t.Run(spanName(src), func(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
tests.DeepCompletion(t, src, test, data.CompletionItems)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("FuzzyCompletion", func(t *testing.T) {
|
t.Run("FuzzyCompletion", func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for src, test := range data.FuzzyCompletions {
|
eachCompletion(t, data.FuzzyCompletions, tests.FuzzyCompletion)
|
||||||
t.Run(spanName(src), func(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
tests.FuzzyCompletion(t, src, test, data.CompletionItems)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("CaseSensitiveCompletion", func(t *testing.T) {
|
t.Run("CaseSensitiveCompletion", func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for src, test := range data.CaseSensitiveCompletions {
|
eachCompletion(t, data.CaseSensitiveCompletions, tests.CaseSensitiveCompletion)
|
||||||
t.Run(spanName(src), func(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
tests.CaseSensitiveCompletion(t, src, test, data.CompletionItems)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("RankCompletions", func(t *testing.T) {
|
t.Run("RankCompletions", func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for src, test := range data.RankCompletions {
|
eachCompletion(t, data.RankCompletions, tests.RankCompletion)
|
||||||
t.Run(spanName(src), func(t *testing.T) {
|
|
||||||
t.Helper()
|
|
||||||
tests.RankCompletion(t, src, test, data.CompletionItems)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Diagnostics", func(t *testing.T) {
|
t.Run("Diagnostics", func(t *testing.T) {
|
||||||
@ -594,13 +582,25 @@ func checkData(t *testing.T, data *Data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(buf, "CompletionsCount = %v\n", len(data.Completions))
|
snippetCount := 0
|
||||||
fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", len(data.CompletionSnippets))
|
for _, want := range data.CompletionSnippets {
|
||||||
fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", len(data.UnimportedCompletions))
|
snippetCount += len(want)
|
||||||
fmt.Fprintf(buf, "DeepCompletionsCount = %v\n", len(data.DeepCompletions))
|
}
|
||||||
fmt.Fprintf(buf, "FuzzyCompletionsCount = %v\n", len(data.FuzzyCompletions))
|
|
||||||
fmt.Fprintf(buf, "RankedCompletionsCount = %v\n", len(data.RankCompletions))
|
countCompletions := func(c map[span.Span][]Completion) (count int) {
|
||||||
fmt.Fprintf(buf, "CaseSensitiveCompletionsCount = %v\n", len(data.CaseSensitiveCompletions))
|
for _, want := range c {
|
||||||
|
count += len(want)
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(buf, "CompletionsCount = %v\n", countCompletions(data.Completions))
|
||||||
|
fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", snippetCount)
|
||||||
|
fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", countCompletions(data.UnimportedCompletions))
|
||||||
|
fmt.Fprintf(buf, "DeepCompletionsCount = %v\n", countCompletions(data.DeepCompletions))
|
||||||
|
fmt.Fprintf(buf, "FuzzyCompletionsCount = %v\n", countCompletions(data.FuzzyCompletions))
|
||||||
|
fmt.Fprintf(buf, "RankedCompletionsCount = %v\n", countCompletions(data.RankCompletions))
|
||||||
|
fmt.Fprintf(buf, "CaseSensitiveCompletionsCount = %v\n", countCompletions(data.CaseSensitiveCompletions))
|
||||||
fmt.Fprintf(buf, "DiagnosticsCount = %v\n", diagnosticsCount)
|
fmt.Fprintf(buf, "DiagnosticsCount = %v\n", diagnosticsCount)
|
||||||
fmt.Fprintf(buf, "FoldingRangesCount = %v\n", len(data.FoldingRanges))
|
fmt.Fprintf(buf, "FoldingRangesCount = %v\n", len(data.FoldingRanges))
|
||||||
fmt.Fprintf(buf, "FormatCount = %v\n", len(data.Formats))
|
fmt.Fprintf(buf, "FormatCount = %v\n", len(data.Formats))
|
||||||
@ -723,10 +723,10 @@ func (data *Data) collectDiagnostics(spn span.Span, msgSource, msg string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
|
func (data *Data) collectCompletions(typ CompletionTestType) func(span.Span, []token.Pos) {
|
||||||
result := func(m map[span.Span]Completion, src span.Span, expected []token.Pos) {
|
result := func(m map[span.Span][]Completion, src span.Span, expected []token.Pos) {
|
||||||
m[src] = Completion{
|
m[src] = append(m[src], Completion{
|
||||||
CompletionItems: expected,
|
CompletionItems: expected,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
switch typ {
|
switch typ {
|
||||||
case CompletionDeep:
|
case CompletionDeep:
|
||||||
@ -896,11 +896,11 @@ func (data *Data) collectSignatures(spn span.Span, signature string, activeParam
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain, placeholder string) {
|
func (data *Data) collectCompletionSnippets(spn span.Span, item token.Pos, plain, placeholder string) {
|
||||||
data.CompletionSnippets[spn] = CompletionSnippet{
|
data.CompletionSnippets[spn] = append(data.CompletionSnippets[spn], CompletionSnippet{
|
||||||
CompletionItem: item,
|
CompletionItem: item,
|
||||||
PlainSnippet: plain,
|
PlainSnippet: plain,
|
||||||
PlaceholderSnippet: placeholder,
|
PlaceholderSnippet: placeholder,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) {
|
func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user