1
0
mirror of https://github.com/golang/go synced 2024-10-01 09:28:37 -06:00
go/internal/lsp/source/completion_literal.go
Muir Manders 3721262b3e internal/lsp: support taking address for completion candidates
We now support taking the address of objects to make better completion
candidates. For example:

i := 123
var p *int = <> // now you get a candidate for "&i"

This required that we track addressability better, particularly when
searching for deep candidates. Now each candidate knows if it is
addressable, and the deep search propagates addressability to child
candidates appropriately.

The basic propagation logic is:

- In-scope *types.Var candidates are addressable. This handles your
  basic "foo" variable whose address if "&foo".

- Surrounding selector is addressable based on type checker info. This
  knows "foo.bar.<>" is addressable but "foo.bar().<>" isn't

- When evaluating deep completions, fields after a function call lose
  addressability, but fields after a pointer regain addressability. For
  example, "foo.bar()" isn't addressable, but "foo.bar().baz" is
  addressable if "bar()" returns a pointer.

Fixes golang/go#36132.

Change-Id: I6a8659eb8c203262aedf86844ac39a2d1e81ecc4
Reviewed-on: https://go-review.googlesource.com/c/tools/+/212399
Run-TryBot: Muir Manders <muir@mnd.rs>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
2019-12-23 23:54:10 +00:00

387 lines
11 KiB
Go

// 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/ast"
"go/token"
"go/types"
"strings"
"unicode"
"golang.org/x/tools/internal/lsp/diff"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/snippet"
"golang.org/x/tools/internal/telemetry/log"
)
// literal generates composite literal, function literal, and make()
// completion items.
func (c *completer) literal(literalType types.Type, imp *importInfo) {
if !c.opts.Literal {
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.
if expType != nil && isEmptyInterface(expType) {
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 && expType != nil {
if _, named := deref(expType).(*types.Named); !named {
return
}
}
// Check if an object of type literalType would match our expected type.
cand := candidate{
obj: c.fakeObj(literalType),
addressable: true,
}
if !c.matchingCandidate(&cand) {
return
}
var (
qf = c.qf
sel = enclosingSelector(c.path, c.pos)
)
// Don't qualify the type name if we are in a selector expression
// since the package name is already present.
if sel != nil {
qf = func(_ *types.Package) string { return "" }
}
typeName := types.TypeString(literalType, 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(), qf)
case *types.Array:
matchName = types.TypeString(t.Elem(), qf)
}
addlEdits, err := c.importEdits(imp)
if err != nil {
log.Error(c.ctx, "error adding import for literal candidate", err)
return
}
// If prefix matches the type name, client may want a composite literal.
if score := c.matcher.Score(matchName); score >= 0 {
if cand.takeAddress {
if sel != nil {
// If we are in a selector we must place the "&" before the selector.
// For example, "foo.B<>" must complete to "&foo.Bar{}", not
// "foo.&Bar{}".
edits, err := referenceEdit(c.snapshot.View().Session().Cache().FileSet(), c.mapper, sel)
if err != nil {
log.Error(c.ctx, "error making edit for literal pointer completion", err)
return
}
addlEdits = append(addlEdits, edits...)
} else {
// Otherwise we can stick the "&" directly before the type name.
typeName = "&" + typeName
}
}
switch t := literalType.Underlying().(type) {
case *types.Struct, *types.Array, *types.Slice, *types.Map:
c.compositeLiteral(t, typeName, float64(score), addlEdits)
case *types.Signature:
// Add a literal completion for a signature type that implements
// an interface. For example, offer "http.HandlerFunc()" when
// expected type is "http.Handler".
if isInterface(expType) {
c.basicLiteral(t, typeName, float64(score), addlEdits)
}
case *types.Basic:
// Add a literal completion for basic types that implement our
// expected interface (e.g. named string type http.Dir
// implements http.FileSystem), or are identical to our expected
// type (i.e. yielding a type conversion such as "float64()").
if isInterface(expType) || types.Identical(expType, literalType) {
c.basicLiteral(t, typeName, float64(score), addlEdits)
}
}
}
// 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); !cand.takeAddress && 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), addlEdits)
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), addlEdits)
}
}
// If prefix matches "func", client may want a function literal.
if score := c.matcher.Score("func"); !cand.takeAddress && score >= 0 && !isInterface(expType) {
switch t := literalType.Underlying().(type) {
case *types.Signature:
c.functionLiteral(t, float64(score))
}
}
}
// referenceEdit produces text edits that prepend a "&" operator to the
// specified node.
func referenceEdit(fset *token.FileSet, m *protocol.ColumnMapper, node ast.Node) ([]protocol.TextEdit, error) {
rng := newMappedRange(fset, m, node.Pos(), node.Pos())
spn, err := rng.Span()
if err != nil {
return nil, err
}
return ToProtocolEdits(m, []diff.TextEdit{{
Span: spn,
NewText: "&",
}})
}
// 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
// functionLiteral adds a function literal completion item for the
// given signature.
func (c *completer) functionLiteral(sig *types.Signature, matchScore float64) {
snip := &snippet.Builder{}
snip.WriteText("func(")
seenParamNames := make(map[string]bool)
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
snip.WriteText(", ")
}
p := sig.Params().At(i)
name := p.Name()
// If the parameter has no name in the signature, we need to try
// come up with a parameter name.
if name == "" {
// Our parameter names are guesses, so they must be placeholders
// for easy correction. If placeholders are disabled, don't
// offer the completion.
if !c.opts.Placeholders {
return
}
// Try abbreviating named types. If the type isn't named, or the
// abbreviation duplicates a previous name, give up and use
// "_". The user will have to provide a name for this parameter
// in order to use it.
if named, ok := deref(p.Type()).(*types.Named); ok {
name = abbreviateCamel(named.Obj().Name())
if seenParamNames[name] {
name = "_"
} else {
seenParamNames[name] = true
}
} else {
name = "_"
}
snip.WritePlaceholder(func(b *snippet.Builder) {
b.WriteText(name)
})
} else {
snip.WriteText(name)
}
// If the following param's type is identical to this one, omit
// this param's type string. For example, emit "i, j int" instead
// of "i int, j int".
if i == sig.Params().Len()-1 || !types.Identical(p.Type(), sig.Params().At(i+1).Type()) {
snip.WriteText(" ")
typeStr := types.TypeString(p.Type(), c.qf)
if sig.Variadic() && i == sig.Params().Len()-1 {
typeStr = strings.Replace(typeStr, "[]", "...", 1)
}
snip.WriteText(typeStr)
}
}
snip.WriteText(")")
results := sig.Results()
if results.Len() > 0 {
snip.WriteText(" ")
}
resultsNeedParens := results.Len() > 1 ||
results.Len() == 1 && results.At(0).Name() != ""
if resultsNeedParens {
snip.WriteText("(")
}
for i := 0; i < results.Len(); i++ {
if i > 0 {
snip.WriteText(", ")
}
r := results.At(i)
if name := r.Name(); name != "" {
snip.WriteText(name + " ")
}
snip.WriteText(types.TypeString(r.Type(), c.qf))
}
if resultsNeedParens {
snip.WriteText(")")
}
snip.WriteText(" {")
snip.WriteFinalTabstop()
snip.WriteText("}")
c.items = append(c.items, CompletionItem{
Label: "func(...) {}",
Score: matchScore * literalCandidateScore,
Kind: protocol.VariableCompletion,
snippet: snip,
})
}
// abbreviateCamel abbreviates camel case identifiers into
// abbreviations. For example, "fooBar" is abbreviated "fb".
func abbreviateCamel(s string) string {
var (
b strings.Builder
useNextUpper bool
)
for i, r := range s {
if i == 0 {
b.WriteRune(unicode.ToLower(r))
}
if unicode.IsUpper(r) {
if useNextUpper {
b.WriteRune(unicode.ToLower(r))
useNextUpper = false
}
} else {
useNextUpper = true
}
}
return b.String()
}
// compositeLiteral adds a composite literal completion item for the given typeName.
func (c *completer) compositeLiteral(T types.Type, typeName string, matchScore float64, edits []protocol.TextEdit) {
snip := &snippet.Builder{}
snip.WriteText(typeName + "{")
// Don't put the tab stop inside the composite literal curlies "{}"
// for structs that have no accessible fields.
if strct, ok := T.(*types.Struct); !ok || fieldsAccessible(strct, c.pkg.GetTypes()) {
snip.WriteFinalTabstop()
}
snip.WriteText("}")
nonSnippet := typeName + "{}"
c.items = append(c.items, CompletionItem{
Label: nonSnippet,
InsertText: nonSnippet,
Score: matchScore * literalCandidateScore,
Kind: protocol.VariableCompletion,
AdditionalTextEdits: edits,
snippet: snip,
})
}
// basicLiteral adds a literal completion item for the given basic
// type name typeName.
func (c *completer) basicLiteral(T types.Type, typeName string, matchScore float64, edits []protocol.TextEdit) {
snip := &snippet.Builder{}
snip.WriteText(typeName + "(")
snip.WriteFinalTabstop()
snip.WriteText(")")
nonSnippet := typeName + "()"
c.items = append(c.items, CompletionItem{
Label: nonSnippet,
InsertText: nonSnippet,
Detail: T.String(),
Score: matchScore * literalCandidateScore,
Kind: protocol.VariableCompletion,
AdditionalTextEdits: edits,
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, edits []protocol.TextEdit) {
// 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: protocol.FunctionCompletion,
AdditionalTextEdits: edits,
snippet: snip,
})
}