diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index 5088762f6e..edcffa56ea 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -1101,16 +1101,41 @@ Nodes: if tv, ok := c.pkg.GetTypesInfo().Types[node.Fun]; ok { if sig, ok := tv.Type.(*types.Signature); ok { - if sig.Params().Len() == 0 { + numParams := sig.Params().Len() + if numParams == 0 { return inf } - i := indexExprAtPos(c.pos, node.Args) - // Make sure not to run past the end of expected parameters. - if i >= sig.Params().Len() { - i = sig.Params().Len() - 1 + + var ( + exprIdx = indexExprAtPos(c.pos, node.Args) + 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 } } @@ -1225,6 +1250,12 @@ func (ti typeInference) applyTypeNameModifiers(typ types.Type) types.Type { 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 // *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 { @@ -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 { return types.ConvertibleTo(objType, c.expectedType.convertibleTo) } diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go index 05b36b9fc9..c6537f6c78 100644 --- a/internal/lsp/source/completion_format.go +++ b/internal/lsp/source/completion_format.go @@ -49,6 +49,11 @@ func (c *completer) item(cand candidate) (CompletionItem, error) { snip = c.functionCallSnippet(label, params) results, writeParens := formatResults(sig.Results(), c.qf) 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) { @@ -73,6 +78,12 @@ func (c *completer) item(cand candidate) (CompletionItem, error) { if sig, ok := obj.Type().Underlying().(*types.Signature); ok && cand.expandFuncCall { 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: sig, ok := obj.Type().Underlying().(*types.Signature) if !ok { diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden index 51af997c10..15fa98b3ff 100644 --- a/internal/lsp/testdata/summary.txt.golden +++ b/internal/lsp/testdata/summary.txt.golden @@ -1,10 +1,10 @@ -- summary -- -CompletionsCount = 213 -CompletionSnippetCount = 40 +CompletionsCount = 214 +CompletionSnippetCount = 43 UnimportedCompletionsCount = 3 DeepCompletionsCount = 5 FuzzyCompletionsCount = 7 -RankedCompletionsCount = 8 +RankedCompletionsCount = 16 CaseSensitiveCompletionsCount = 4 DiagnosticsCount = 22 FoldingRangesCount = 2 diff --git a/internal/lsp/testdata/variadic/variadic.go.in b/internal/lsp/testdata/variadic/variadic.go.in new file mode 100644 index 0000000000..76e3a56655 --- /dev/null +++ b/internal/lsp/testdata/variadic/variadic.go.in @@ -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") +} diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 4b47ff6183..95d5a13a7a 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -15,6 +15,7 @@ import ( "io/ioutil" "path/filepath" "sort" + "strconv" "strings" "sync" "testing" @@ -39,13 +40,13 @@ var UpdateGolden = flag.Bool("golden", false, "Update golden files") type Diagnostics map[span.URI][]source.Diagnostic type CompletionItems map[token.Pos]*source.CompletionItem -type Completions map[span.Span]Completion -type CompletionSnippets map[span.Span]CompletionSnippet -type UnimportedCompletions map[span.Span]Completion -type DeepCompletions map[span.Span]Completion -type FuzzyCompletions map[span.Span]Completion -type CaseSensitiveCompletions map[span.Span]Completion -type RankCompletions map[span.Span]Completion +type Completions map[span.Span][]Completion +type CompletionSnippets map[span.Span][]CompletionSnippet +type UnimportedCompletions map[span.Span][]Completion +type DeepCompletions map[span.Span][]Completion +type FuzzyCompletions map[span.Span][]Completion +type CaseSensitiveCompletions map[span.Span][]Completion +type RankCompletions map[span.Span][]Completion type FoldingRanges []span.Span type Formats []span.Span type Imports []span.Span @@ -343,80 +344,67 @@ func Run(t *testing.T, tests Tests, data *Data) { t.Helper() 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.Helper() - for src, test := range data.Completions { - t.Run(spanName(src), func(t *testing.T) { - t.Helper() - tests.Completion(t, src, test, data.CompletionItems) - }) - } + eachCompletion(t, data.Completions, tests.Completion) }) t.Run("CompletionSnippets", func(t *testing.T) { t.Helper() for _, placeholders := range []bool{true, false} { - for src, expected := range data.CompletionSnippets { - name := spanName(src) - if placeholders { - name += "_placeholders" + for src, expecteds := range data.CompletionSnippets { + for i, expected := range expecteds { + name := spanName(src) + "_" + strconv.Itoa(i+1) + 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.Helper() - for src, test := range data.UnimportedCompletions { - t.Run(spanName(src), func(t *testing.T) { - t.Helper() - tests.UnimportedCompletion(t, src, test, data.CompletionItems) - }) - } + eachCompletion(t, data.UnimportedCompletions, tests.UnimportedCompletion) }) t.Run("DeepCompletion", func(t *testing.T) { t.Helper() - for src, test := range data.DeepCompletions { - t.Run(spanName(src), func(t *testing.T) { - t.Helper() - tests.DeepCompletion(t, src, test, data.CompletionItems) - }) - } + eachCompletion(t, data.DeepCompletions, tests.DeepCompletion) }) t.Run("FuzzyCompletion", func(t *testing.T) { t.Helper() - for src, test := range data.FuzzyCompletions { - t.Run(spanName(src), func(t *testing.T) { - t.Helper() - tests.FuzzyCompletion(t, src, test, data.CompletionItems) - }) - } + eachCompletion(t, data.FuzzyCompletions, tests.FuzzyCompletion) }) t.Run("CaseSensitiveCompletion", func(t *testing.T) { t.Helper() - for src, test := range data.CaseSensitiveCompletions { - t.Run(spanName(src), func(t *testing.T) { - t.Helper() - tests.CaseSensitiveCompletion(t, src, test, data.CompletionItems) - }) - } + eachCompletion(t, data.CaseSensitiveCompletions, tests.CaseSensitiveCompletion) }) t.Run("RankCompletions", func(t *testing.T) { t.Helper() - for src, test := range data.RankCompletions { - t.Run(spanName(src), func(t *testing.T) { - t.Helper() - tests.RankCompletion(t, src, test, data.CompletionItems) - }) - } + eachCompletion(t, data.RankCompletions, tests.RankCompletion) }) 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)) - fmt.Fprintf(buf, "CompletionSnippetCount = %v\n", len(data.CompletionSnippets)) - fmt.Fprintf(buf, "UnimportedCompletionsCount = %v\n", len(data.UnimportedCompletions)) - 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)) - fmt.Fprintf(buf, "CaseSensitiveCompletionsCount = %v\n", len(data.CaseSensitiveCompletions)) + snippetCount := 0 + for _, want := range data.CompletionSnippets { + snippetCount += len(want) + } + + countCompletions := func(c map[span.Span][]Completion) (count int) { + 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, "FoldingRangesCount = %v\n", len(data.FoldingRanges)) 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) { - result := func(m map[span.Span]Completion, src span.Span, expected []token.Pos) { - m[src] = Completion{ + result := func(m map[span.Span][]Completion, src span.Span, expected []token.Pos) { + m[src] = append(m[src], Completion{ CompletionItems: expected, - } + }) } switch typ { 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) { - data.CompletionSnippets[spn] = CompletionSnippet{ + data.CompletionSnippets[spn] = append(data.CompletionSnippets[spn], CompletionSnippet{ CompletionItem: item, PlainSnippet: plain, PlaceholderSnippet: placeholder, - } + }) } func (data *Data) collectLinks(spn span.Span, link string, note *expect.Note, fset *token.FileSet) {