mirror of
https://github.com/golang/go
synced 2024-11-05 11:46:12 -07:00
7c0525f229
When a signature doesn't name its params, we make up param names when generating a corresponding func literal: var f func(myType) // no param name f = fun<> // completes to "func(mt myType) {}" Previously we would abbreviate named types and fall back to "_" for builtins or repeated names. We would require placeholders to be enabled when using "_" so the user could name the param easily. That left users that don't use placeholders with no completion at all in this case. I made the following improvements: - Generate a name for all params. For builtin types we use the first letter, e.g. "i" for "int", "s" for "[]string". If a name is repeated, we append incrementing numeric suffixes. For example, "func(int, int32)" becomes "func(i1 int, i2 int32"). - No longer require placeholders to be enabled in any case. - Fix handling of alias types so the param name and type name are based on the alias, not the aliasee. I also tweaked formatVarType to qualify packages using a types.Qualifier. I needed it to respect a qualifier that doesn't qualify anything so I could format e.g. "http.Response" as just "Response" to come up with param names. Fixes golang/go#38416. Change-Id: I0ce8a0a4e2485dda41a0aa696d9fd48bea595869 Reviewed-on: https://go-review.googlesource.com/c/tools/+/246262 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
439 lines
13 KiB
Go
439 lines
13 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 (
|
|
"context"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/token"
|
|
"go/types"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"golang.org/x/tools/internal/event"
|
|
"golang.org/x/tools/internal/lsp/diff"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
"golang.org/x/tools/internal/lsp/snippet"
|
|
)
|
|
|
|
// literal generates composite literal, function literal, and make()
|
|
// completion items.
|
|
func (c *completer) literal(ctx context.Context, literalType types.Type, imp *importInfo) {
|
|
if !c.opts.literal {
|
|
return
|
|
}
|
|
|
|
expType := c.inference.objType
|
|
|
|
if c.inference.matchesVariadic(literalType) {
|
|
// Don't offer literal slice candidates for variadic arguments.
|
|
// For example, don't offer "[]interface{}{}" in "fmt.Print(<>)".
|
|
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 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),
|
|
}
|
|
|
|
switch literalType.Underlying().(type) {
|
|
// These literal types are addressable (e.g. "&[]int{}"), others are
|
|
// not (e.g. can't do "&(func(){})").
|
|
case *types.Struct, *types.Array, *types.Slice, *types.Map:
|
|
cand.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(ctx, imp)
|
|
if err != nil {
|
|
event.Error(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 := prependEdit(c.snapshot.FileSet(), c.mapper, sel, "&")
|
|
if err != nil {
|
|
event.Error(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(ctx, t, float64(score))
|
|
}
|
|
}
|
|
}
|
|
|
|
// prependEdit produces text edits that preprend the specified prefix
|
|
// to the specified node.
|
|
func prependEdit(fset *token.FileSet, m *protocol.ColumnMapper, node ast.Node, prefix string) ([]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: prefix,
|
|
}})
|
|
}
|
|
|
|
// 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(ctx context.Context, sig *types.Signature, matchScore float64) {
|
|
snip := &snippet.Builder{}
|
|
snip.WriteText("func(")
|
|
|
|
// First we generate names for each param and keep a seen count so
|
|
// we know if we need to uniquify param names. For example,
|
|
// "func(int)" will become "func(i int)", but "func(int, int64)"
|
|
// will become "func(i1 int, i2 int64)".
|
|
var (
|
|
paramNames = make([]string, sig.Params().Len())
|
|
paramNameCount = make(map[string]int)
|
|
)
|
|
for i := 0; i < sig.Params().Len(); i++ {
|
|
var (
|
|
p = sig.Params().At(i)
|
|
name = p.Name()
|
|
)
|
|
if name == "" {
|
|
// If the param has no name in the signature, guess a name based
|
|
// on the type. Use an empty qualifier to ignore the package.
|
|
// For example, we want to name "http.Request" "r", not "hr".
|
|
name = formatVarType(ctx, c.snapshot, c.pkg, c.file, p, func(p *types.Package) string {
|
|
return ""
|
|
})
|
|
name = abbreviateTypeName(name)
|
|
}
|
|
paramNames[i] = name
|
|
if name != "_" {
|
|
paramNameCount[name]++
|
|
}
|
|
}
|
|
|
|
for n, c := range paramNameCount {
|
|
// Any names we saw more than once will need a unique suffix added
|
|
// on. Reset the count to 1 to act as the suffix for the first
|
|
// name.
|
|
if c >= 2 {
|
|
paramNameCount[n] = 1
|
|
} else {
|
|
delete(paramNameCount, n)
|
|
}
|
|
}
|
|
|
|
for i := 0; i < sig.Params().Len(); i++ {
|
|
if i > 0 {
|
|
snip.WriteText(", ")
|
|
}
|
|
|
|
var (
|
|
p = sig.Params().At(i)
|
|
name = paramNames[i]
|
|
)
|
|
|
|
// Uniquify names by adding on an incrementing numeric suffix.
|
|
if idx, found := paramNameCount[name]; found {
|
|
paramNameCount[name]++
|
|
name = fmt.Sprintf("%s%d", name, idx)
|
|
}
|
|
|
|
if name != p.Name() && c.opts.placeholders {
|
|
// If we didn't use the signature's param name verbatim then we
|
|
// may have chosen a poor name. Give the user a placeholder so
|
|
// they can easily fix the 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 := formatVarType(ctx, c.snapshot, c.pkg, c.file, p, 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(formatVarType(ctx, c.snapshot, c.pkg, c.file, r, 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,
|
|
})
|
|
}
|
|
|
|
// abbreviateTypeName abbreviates type names into acronyms. For
|
|
// example, "fooBar" is abbreviated "fb". Care is taken to ignore
|
|
// non-identifier runes. For example, "[]int" becomes "i", and
|
|
// "struct { i int }" becomes "s".
|
|
func abbreviateTypeName(s string) string {
|
|
var (
|
|
b strings.Builder
|
|
useNextUpper bool
|
|
)
|
|
|
|
// Trim off leading non-letters. We trim everything between "[" and
|
|
// "]" to handle array types like "[someConst]int".
|
|
var inBracket bool
|
|
s = strings.TrimFunc(s, func(r rune) bool {
|
|
if inBracket {
|
|
inBracket = r != ']'
|
|
return true
|
|
}
|
|
|
|
if r == '[' {
|
|
inBracket = true
|
|
}
|
|
|
|
return !unicode.IsLetter(r)
|
|
})
|
|
|
|
for i, r := range s {
|
|
// Stop if we encounter a non-identifier rune.
|
|
if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
|
|
break
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|