diff --git a/internal/lsp/source/completion_literal.go b/internal/lsp/source/completion_literal.go index bdf1222b5a..004950dd21 100644 --- a/internal/lsp/source/completion_literal.go +++ b/internal/lsp/source/completion_literal.go @@ -21,14 +21,22 @@ import ( // literal generates composite literal, function literal, and make() // completion items. func (c *completer) literal(literalType types.Type, imp *importInfo) { - // Don't provide literal candidates for variadic function arguments. - // For example, don't provide "[]interface{}{}" in "fmt.Print(<>)". - if c.expectedType.variadic { - return - } - expType := c.expectedType.objType + if c.expectedType.variadic { + // Don't offer literal slice candidates for variadic arguments. + // For example, don't offer "[]interface{}{}" in "fmt.Print(<>)". + if c.expectedType.matchesVariadic(literalType) { + return + } + + // Otherwise, consider our expected type to be the variadic + // element type, not the slice type. + if slice, ok := expType.(*types.Slice); ok { + expType = slice.Elem() + } + } + // 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. @@ -54,19 +62,14 @@ func (c *completer) literal(literalType types.Type, imp *importInfo) { // Check if an object of type literalType or *literalType would // match our expected type. + var isPointer bool if !c.matchingType(literalType) { - literalType = types.NewPointer(literalType) - - if !c.matchingType(literalType) { + isPointer = true + if !c.matchingType(types.NewPointer(literalType)) { return } } - ptr, isPointer := literalType.(*types.Pointer) - if isPointer { - literalType = ptr.Elem() - } - var ( qf = c.qf sel = enclosingSelector(c.path, c.pos) @@ -303,8 +306,8 @@ func (c *completer) compositeLiteral(T types.Type, typeName string, matchScore f 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 { + // for structs that have no accessible fields. + if strct, ok := T.(*types.Struct); !ok || fieldsAccessible(strct, c.pkg.GetTypes()) { snip.WriteFinalTabstop() } snip.WriteText("}") diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go index 7ad9625fc8..00470f6cd3 100644 --- a/internal/lsp/source/util.go +++ b/internal/lsp/source/util.go @@ -380,6 +380,17 @@ func typeConversion(call *ast.CallExpr, info *types.Info) types.Type { return nil } +// fieldsAccessible returns whether s has at least one field accessible by p. +func fieldsAccessible(s *types.Struct, p *types.Package) bool { + for i := 0; i < s.NumFields(); i++ { + f := s.Field(i) + if f.Exported() || f.Pkg() == p { + return true + } + } + return false +} + func formatParams(s Snapshot, pkg Package, sig *types.Signature, qf types.Qualifier) []string { params := make([]string, 0, sig.Params().Len()) for i := 0; i < sig.Params().Len(); i++ { diff --git a/internal/lsp/testdata/snippets/literal_snippets.go.in b/internal/lsp/testdata/snippets/literal_snippets.go.in index 1a8e77563d..57489fb3e3 100644 --- a/internal/lsp/testdata/snippets/literal_snippets.go.in +++ b/internal/lsp/testdata/snippets/literal_snippets.go.in @@ -1,6 +1,7 @@ package snippets import ( + "bytes" "net/http" "sort" @@ -105,11 +106,20 @@ func _() { } func _() { - type myStruct struct{ i int } //@item(litStructType, "myStruct", "struct{...}", "struct") + type myStruct struct{ i int } //@item(litMyStructType, "myStruct", "struct{...}", "struct") + myStruct{} //@item(litMyStruct, "myStruct{}", "", "var") foo := func(s string, args ...myStruct) {} // Don't give literal slice candidate for variadic arg. - foo("", myStruct) //@complete(")", litStructType) + // Do give literal candidates for variadic element. + foo("", myStruct) //@complete(")", litMyStruct, litMyStructType) +} + +func _() { + Buffer{} //@item(litBuffer, "Buffer{}", "", "var") + + var b *bytes.Buffer + b = bytes.Bu //@snippet(" //", litBuffer, "Buffer{\\}", "Buffer{\\}") } func _() { diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden index ae1a263179..6ad6c57ce3 100644 --- a/internal/lsp/testdata/summary.txt.golden +++ b/internal/lsp/testdata/summary.txt.golden @@ -1,6 +1,6 @@ -- summary -- CompletionsCount = 215 -CompletionSnippetCount = 50 +CompletionSnippetCount = 51 UnimportedCompletionsCount = 3 DeepCompletionsCount = 5 FuzzyCompletionsCount = 7