diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index 119a6a9008..d1a98f7988 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -73,6 +73,12 @@ func (s *Server) toProtocolCompletionItems(candidates []source.CompletionItem, r insertText = candidate.Snippet() } + // This can happen if the client has snippets disabled but the + // candidate only supports snippet insertion. + if insertText == "" { + continue + } + item := protocol.CompletionItem{ Label: candidate.Label, Detail: candidate.Detail, diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go index 63c850f190..2cc30953cb 100644 --- a/internal/lsp/lsp_test.go +++ b/internal/lsp/lsp_test.go @@ -180,7 +180,7 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, snippets tests } } if got == nil { - t.Fatalf("%s: couldn't find completion matching %q", src.URI(), wantItem.Label) + t.Fatalf("%s:%d: couldn't find completion matching %q", src.URI(), src.Start().Line(), wantItem.Label) } var expected string if usePlaceholders { diff --git a/internal/lsp/snippet/snippet_builder.go b/internal/lsp/snippet/snippet_builder.go index a17091cab3..0348fdadf7 100644 --- a/internal/lsp/snippet/snippet_builder.go +++ b/internal/lsp/snippet/snippet_builder.go @@ -50,6 +50,13 @@ func (b *Builder) WritePlaceholder(fn func(*Builder)) { b.sb.WriteByte('}') } +// WriteFinalTabstop marks where cursor ends up after the user has +// cycled through all the normal tab stops. It defaults to the +// character after the snippet. +func (b *Builder) WriteFinalTabstop() { + fmt.Fprint(&b.sb, "$0") +} + // In addition to '\', '}', and '$', snippet choices also use '|' and ',' as // meta characters, so they must be escaped within the choices. var choiceReplacer = strings.NewReplacer( diff --git a/internal/lsp/snippet/snippet_builder_test.go b/internal/lsp/snippet/snippet_builder_test.go index bb47f52e47..331dd69107 100644 --- a/internal/lsp/snippet/snippet_builder_test.go +++ b/internal/lsp/snippet/snippet_builder_test.go @@ -48,4 +48,9 @@ func TestSnippetBuilder(t *testing.T) { expect(`${1|one,{ \} \$ \| " \, / \\,three|}`, func(b *Builder) { b.WriteChoice([]string{"one", `{ } $ | " , / \`, "three"}) }) + + expect("$0 hello", func(b *Builder) { + b.WriteFinalTabstop() + b.WriteText(" hello") + }) } diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go index 87f0a12401..665f28dd68 100644 --- a/internal/lsp/source/completion.go +++ b/internal/lsp/source/completion.go @@ -306,8 +306,12 @@ func (c *completer) found(obj types.Object, score float64, imp *imports.ImportIn imp: imp, } - if c.matchingType(&cand) { + if c.matchingCandidate(&cand) { cand.score *= highScore + } else if isTypeName(obj) { + // If obj is a *types.TypeName that didn't otherwise match, check + // if a literal object of this type makes a good candidate. + c.literal(obj.Type()) } // Favor shallow matches by lowering weight according to depth. @@ -651,6 +655,18 @@ func (c *completer) lexical() error { } } } + + if c.expectedType.objType != nil { + // If we have an expected type and it is _not_ a named type, see + // if an object literal makes a good candidate. Named types are + // handled during the normal lexical object search. For example, + // if our expected type is "[]int", this will add a candidate of + // "[]int{}". + if _, named := c.expectedType.objType.(*types.Named); !named { + c.literal(c.expectedType.objType) + } + } + return nil } @@ -880,6 +896,10 @@ type typeInference struct { // objType is the desired type of an object used at the query position. objType types.Type + // variadic is true if objType is a slice type from an initial + // variadic param. + variadic bool + // wantTypeName is true if we expect the name of a type. wantTypeName bool @@ -907,6 +927,7 @@ func expectedType(c *completer) typeInference { var ( modifiers []typeModifier + variadic bool typ types.Type convertibleTo types.Type ) @@ -958,6 +979,7 @@ Nodes: i = sig.Params().Len() - 1 } typ = sig.Params().At(i).Type() + variadic = sig.Variadic() && i == sig.Params().Len()-1 break Nodes } } @@ -1033,6 +1055,7 @@ Nodes: } return typeInference{ + variadic: variadic, objType: typ, modifiers: modifiers, convertibleTo: convertibleTo, @@ -1176,9 +1199,15 @@ Nodes: } } -// matchingType reports whether an object is a good completion candidate -// in the context of the expected type. -func (c *completer) matchingType(cand *candidate) bool { +// matchingType reports whether a type matches the expected type. +func (c *completer) matchingType(T types.Type) bool { + fakeObj := types.NewVar(token.NoPos, c.pkg.GetTypes(), "", T) + return c.matchingCandidate(&candidate{obj: fakeObj}) +} + +// matchingCandidate reports whether a candidate matches our type +// inferences. +func (c *completer) matchingCandidate(cand *candidate) bool { if isTypeName(cand.obj) { return c.matchingTypeName(cand) } diff --git a/internal/lsp/source/completion_literal.go b/internal/lsp/source/completion_literal.go new file mode 100644 index 0000000000..9e990dc3f4 --- /dev/null +++ b/internal/lsp/source/completion_literal.go @@ -0,0 +1,164 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package source + +import ( + "go/types" + "strings" + + "golang.org/x/tools/internal/lsp/snippet" +) + +// literal generates composite literal and make() completion items. +func (c *completer) literal(literalType types.Type) { + if c.expectedType.objType == nil { + return + } + + // Don't provide literal candidates for variadic function arguments. + // For example, don't provide "[]interface{}{}" in "fmt.Print(<>)". + if c.expectedType.variadic { + return + } + + // Avoid literal candidates if the expected type is an empty + // interface. It isn't very useful to suggest a literal candidate of + // every possible type. + if isEmptyInterface(c.expectedType.objType) { + return + } + + // We handle unnamed literal completions explicitly before searching + // for candidates. Avoid named-type literal completions for + // unnamed-type expected type since that results in duplicate + // candidates. For example, in + // + // type mySlice []int + // var []int = <> + // + // don't offer "mySlice{}" since we have already added a candidate + // of "[]int{}". + if _, named := literalType.(*types.Named); named { + if _, named := deref(c.expectedType.objType).(*types.Named); !named { + return + } + } + + // Check if an object of type literalType or *literalType would + // match our expected type. + if !c.matchingType(literalType) { + literalType = types.NewPointer(literalType) + + if !c.matchingType(literalType) { + return + } + } + + ptr, isPointer := literalType.(*types.Pointer) + if isPointer { + literalType = ptr.Elem() + } + + typeName := types.TypeString(literalType, c.qf) + + // A type name of "[]int" doesn't work very will with the matcher + // since "[" isn't a valid identifier prefix. Here we strip off the + // slice (and array) prefix yielding just "int". + matchName := typeName + switch t := literalType.(type) { + case *types.Slice: + matchName = types.TypeString(t.Elem(), c.qf) + case *types.Array: + matchName = types.TypeString(t.Elem(), c.qf) + } + + // If prefix matches the type name, client may want a composite literal. + if score := c.matcher.Score(matchName); score >= 0 { + if isPointer { + typeName = "&" + typeName + } + + switch t := literalType.Underlying().(type) { + case *types.Struct, *types.Array, *types.Slice, *types.Map: + c.compositeLiteral(t, typeName, float64(score)) + } + } + + // If prefix matches "make", client may want a "make()" + // invocation. We also include the type name to allow for more + // flexible fuzzy matching. + if score := c.matcher.Score("make." + matchName); !isPointer && score >= 0 { + switch literalType.Underlying().(type) { + case *types.Slice: + // The second argument to "make()" for slices is required, so default to "0". + c.makeCall(typeName, "0", float64(score)) + case *types.Map, *types.Chan: + // Maps and channels don't require the second argument, so omit + // to keep things simple for now. + c.makeCall(typeName, "", float64(score)) + } + } +} + +// literalCandidateScore is the base score for literal candidates. +// Literal candidates match the expected type so they should be high +// scoring, but we want them ranked below lexical objects of the +// correct type, so scale down highScore. +const literalCandidateScore = highScore / 2 + +// compositeLiteral adds a composite literal completion item for the given typeName. +func (c *completer) compositeLiteral(T types.Type, typeName string, matchScore float64) { + snip := &snippet.Builder{} + snip.WriteText(typeName + "{") + // Don't put the tab stop inside the composite literal curlies "{}" + // for structs that have no fields. + if strct, ok := T.(*types.Struct); !ok || strct.NumFields() > 0 { + snip.WriteFinalTabstop() + } + snip.WriteText("}") + + nonSnippet := typeName + "{}" + + c.items = append(c.items, CompletionItem{ + Label: nonSnippet, + InsertText: nonSnippet, + Score: matchScore * literalCandidateScore, + Kind: VariableCompletionItem, + snippet: snip, + }) +} + +// makeCall adds a completion item for a "make()" call given a specific type. +func (c *completer) makeCall(typeName string, secondArg string, matchScore float64) { + // Keep it simple and don't add any placeholders for optional "make()" arguments. + + snip := &snippet.Builder{} + snip.WriteText("make(" + typeName) + if secondArg != "" { + snip.WriteText(", ") + snip.WritePlaceholder(func(b *snippet.Builder) { + if c.opts.Placeholders { + b.WriteText(secondArg) + } + }) + } + snip.WriteText(")") + + var nonSnippet strings.Builder + nonSnippet.WriteString("make(" + typeName) + if secondArg != "" { + nonSnippet.WriteString(", ") + nonSnippet.WriteString(secondArg) + } + nonSnippet.WriteByte(')') + + c.items = append(c.items, CompletionItem{ + Label: nonSnippet.String(), + InsertText: nonSnippet.String(), + Score: matchScore * literalCandidateScore, + Kind: FunctionCompletionItem, + snippet: snip, + }) +} diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go index 1e575ccc1a..586b024b9b 100644 --- a/internal/lsp/source/util.go +++ b/internal/lsp/source/util.go @@ -329,6 +329,11 @@ func isFunc(obj types.Object) bool { return ok } +func isEmptyInterface(T types.Type) bool { + intf, _ := T.(*types.Interface) + return intf != nil && intf.NumMethods() == 0 +} + // typeConversion returns the type being converted to if call is a type // conversion expression. func typeConversion(call *ast.CallExpr, info *types.Info) types.Type { diff --git a/internal/lsp/testdata/nested_complit/nested_complit.go.in b/internal/lsp/testdata/nested_complit/nested_complit.go.in index 3c3c88f031..fb68ba4ac6 100644 --- a/internal/lsp/testdata/nested_complit/nested_complit.go.in +++ b/internal/lsp/testdata/nested_complit/nested_complit.go.in @@ -7,7 +7,8 @@ type ncBar struct { //@item(structNCBar, "ncBar", "struct{...}", "struct") } func _() { + []ncFoo{} //@item(litNCFoo, "[]ncFoo{}", "", "var") _ := ncBar{ - baz: [] //@complete(" //", structNCBar, structNCFoo) + baz: [] //@complete(" //", litNCFoo, structNCBar, structNCFoo) } } diff --git a/internal/lsp/testdata/snippets/literal_snippets.go b/internal/lsp/testdata/snippets/literal_snippets.go new file mode 100644 index 0000000000..0f207df407 --- /dev/null +++ b/internal/lsp/testdata/snippets/literal_snippets.go @@ -0,0 +1,98 @@ +package snippets + +func _() { + []int{} //@item(litIntSlice, "[]int{}", "", "var") + make([]int, 0) //@item(makeIntSlice, "make([]int, 0)", "", "func") + + var slice []int + slice = i //@snippet(" //", litIntSlice, "[]int{$0\\}", "[]int{$0\\}") + slice = m //@snippet(" //", makeIntSlice, "make([]int, ${1:})", "make([]int, ${1:0})") +} + +func _() { + type namedInt []int + + namedInt{} //@item(litNamedSlice, "namedInt{}", "", "var") + make(namedInt, 0) //@item(makeNamedSlice, "make(namedInt, 0)", "", "func") + + var namedSlice namedInt + namedSlice = n //@snippet(" //", litNamedSlice, "namedInt{$0\\}", "namedInt{$0\\}") + namedSlice = m //@snippet(" //", makeNamedSlice, "make(namedInt, ${1:})", "make(namedInt, ${1:0})") +} + +func _() { + make(chan int) //@item(makeChan, "make(chan int)", "", "func") + + var ch chan int + ch = m //@snippet(" //", makeChan, "make(chan int)", "make(chan int)") +} + +func _() { + map[string]struct{}{} //@item(litMap, "map[string]struct{}{}", "", "var") + make(map[string]struct{}) //@item(makeMap, "make(map[string]struct{})", "", "func") + + var m map[string]struct{} + m = m //@snippet(" //", litMap, "map[string]struct{\\}{$0\\}", "map[string]struct{\\}{$0\\}") + m = m //@snippet(" //", makeMap, "make(map[string]struct{\\})", "make(map[string]struct{\\})") + + struct{}{} //@item(litEmptyStruct, "struct{}{}", "", "var") + + m["hi"] = s //@snippet(" //", litEmptyStruct, "struct{\\}{\\}", "struct{\\}{\\}") +} + +func _() { + type myStruct struct{ i int } + + myStruct{} //@item(litStruct, "myStruct{}", "", "var") + &myStruct{} //@item(litStructPtr, "&myStruct{}", "", "var") + + var ms myStruct + ms = m //@snippet(" //", litStruct, "myStruct{$0\\}", "myStruct{$0\\}") + + var msPtr *myStruct + msPtr = m //@snippet(" //", litStructPtr, "&myStruct{$0\\}", "&myStruct{$0\\}") + + msPtr = &m //@snippet(" //", litStruct, "myStruct{$0\\}", "myStruct{$0\\}") +} + +type myImpl struct{} + +func (myImpl) foo() {} + +func (*myImpl) bar() {} + +func _() { + type myIntf interface { + foo() + } + + myImpl{} //@item(litImpl, "myImpl{}", "", "var") + + var mi myIntf + mi = m //@snippet(" //", litImpl, "myImpl{\\}", "myImpl{\\}") + + // only satisfied by pointer to myImpl + type myPtrIntf interface { + bar() + } + + &myImpl{} //@item(litImplPtr, "&myImpl{}", "", "var") + + var mpi myPtrIntf + mpi = m //@snippet(" //", litImplPtr, "&myImpl{\\}", "&myImpl{\\}") +} + +func _() { + var s struct{ i []int } //@item(litSliceField, "i", "[]int", "field") + var foo []int + // no literal completions after selector + foo = s.i //@complete(" //", litSliceField) +} + +func _() { + type myStruct struct{ i int } //@item(litStructType, "myStruct", "struct{...}", "struct") + + foo := func(s string, args ...myStruct) {} + // Don't give literal slice candidate for variadic arg. + foo("", myStruct) //@complete(")", litStructType) +} diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 4658362441..9dfabc4d47 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -30,8 +30,8 @@ import ( // We hardcode the expected number of test cases to ensure that all tests // are being executed. If a test is added, this number must be changed. const ( - ExpectedCompletionsCount = 162 - ExpectedCompletionSnippetCount = 16 + ExpectedCompletionsCount = 164 + ExpectedCompletionSnippetCount = 29 ExpectedDiagnosticsCount = 21 ExpectedFormatCount = 6 ExpectedImportCount = 2