mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
internal/lsp: don't qualify literal candidates in *ast.SelectorExpr
Previously we unconditionally qualified literal candidate types with their package. For example: var buf *bytes.Buffer buf = &bytes.Bu<> would complete to: buf = &bytes.bytes.Buffer{} Now we don't qualify the type if the cursor position is in the selector of an *ast.SelectorExpr. We only generate literal candidates for type names, so if we are in a selector then we can assume it is a package qualified type (as opposed to an object field). We also handle the insertion of "&" for literal pointers better. If you are in the selector of an *ast.SelectorExpr, we prepend the "&" to the beginning of the expression rather than the selector. For example, you will end up with "&bytes.Buffer{}" instead of "bytes.&Buffer{}". Updates golang/go#34872. Change-Id: I812aa809cd4e649a429853386789f80033412814 Reviewed-on: https://go-review.googlesource.com/c/tools/+/201200 Run-TryBot: Rebecca Stambler <rstambler@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
This commit is contained in:
parent
5bac78f585
commit
02335f11d5
@ -5,12 +5,17 @@
|
|||||||
package source
|
package source
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/diff"
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/lsp/snippet"
|
"golang.org/x/tools/internal/lsp/snippet"
|
||||||
|
"golang.org/x/tools/internal/span"
|
||||||
|
"golang.org/x/tools/internal/telemetry/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// literal generates composite literal, function literal, and make()
|
// literal generates composite literal, function literal, and make()
|
||||||
@ -64,7 +69,18 @@ func (c *completer) literal(literalType types.Type) {
|
|||||||
literalType = ptr.Elem()
|
literalType = ptr.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
typeName := types.TypeString(literalType, c.qf)
|
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
|
// 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
|
// since "[" isn't a valid identifier prefix. Here we strip off the
|
||||||
@ -72,26 +88,41 @@ func (c *completer) literal(literalType types.Type) {
|
|||||||
matchName := typeName
|
matchName := typeName
|
||||||
switch t := literalType.(type) {
|
switch t := literalType.(type) {
|
||||||
case *types.Slice:
|
case *types.Slice:
|
||||||
matchName = types.TypeString(t.Elem(), c.qf)
|
matchName = types.TypeString(t.Elem(), qf)
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
matchName = types.TypeString(t.Elem(), c.qf)
|
matchName = types.TypeString(t.Elem(), qf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If prefix matches the type name, client may want a composite literal.
|
// If prefix matches the type name, client may want a composite literal.
|
||||||
if score := c.matcher.Score(matchName); score >= 0 {
|
if score := c.matcher.Score(matchName); score >= 0 {
|
||||||
|
var protocolEdits []protocol.TextEdit
|
||||||
|
|
||||||
if isPointer {
|
if isPointer {
|
||||||
typeName = "&" + typeName
|
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.view.Session().Cache().FileSet(), c.mapper, sel)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(c.ctx, "error making edit for literal pointer completion", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
protocolEdits = append(protocolEdits, edits...)
|
||||||
|
} else {
|
||||||
|
// Otherwise we can stick the "&" directly before the type name.
|
||||||
|
typeName = "&" + typeName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := literalType.Underlying().(type) {
|
switch t := literalType.Underlying().(type) {
|
||||||
case *types.Struct, *types.Array, *types.Slice, *types.Map:
|
case *types.Struct, *types.Array, *types.Slice, *types.Map:
|
||||||
c.compositeLiteral(t, typeName, float64(score))
|
c.compositeLiteral(t, typeName, float64(score), protocolEdits)
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
// Add a literal completion for basic types that implement our
|
// Add a literal completion for basic types that implement our
|
||||||
// expected interface (e.g. named string type http.Dir
|
// expected interface (e.g. named string type http.Dir
|
||||||
// implements http.FileSystem).
|
// implements http.FileSystem).
|
||||||
if isInterface(c.expectedType.objType) {
|
if isInterface(c.expectedType.objType) {
|
||||||
c.basicLiteral(t, typeName, float64(score))
|
c.basicLiteral(t, typeName, float64(score), protocolEdits)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,6 +151,24 @@ func (c *completer) literal(literalType types.Type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 := span.Range{
|
||||||
|
FileSet: fset,
|
||||||
|
Start: node.Pos(),
|
||||||
|
End: 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.
|
// literalCandidateScore is the base score for literal candidates.
|
||||||
// Literal candidates match the expected type so they should be high
|
// Literal candidates match the expected type so they should be high
|
||||||
// scoring, but we want them ranked below lexical objects of the
|
// scoring, but we want them ranked below lexical objects of the
|
||||||
@ -248,7 +297,7 @@ func abbreviateCamel(s string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// compositeLiteral adds a composite literal completion item for the given typeName.
|
// compositeLiteral adds a composite literal completion item for the given typeName.
|
||||||
func (c *completer) compositeLiteral(T types.Type, typeName string, matchScore float64) {
|
func (c *completer) compositeLiteral(T types.Type, typeName string, matchScore float64, edits []protocol.TextEdit) {
|
||||||
snip := &snippet.Builder{}
|
snip := &snippet.Builder{}
|
||||||
snip.WriteText(typeName + "{")
|
snip.WriteText(typeName + "{")
|
||||||
// Don't put the tab stop inside the composite literal curlies "{}"
|
// Don't put the tab stop inside the composite literal curlies "{}"
|
||||||
@ -261,17 +310,18 @@ func (c *completer) compositeLiteral(T types.Type, typeName string, matchScore f
|
|||||||
nonSnippet := typeName + "{}"
|
nonSnippet := typeName + "{}"
|
||||||
|
|
||||||
c.items = append(c.items, CompletionItem{
|
c.items = append(c.items, CompletionItem{
|
||||||
Label: nonSnippet,
|
Label: nonSnippet,
|
||||||
InsertText: nonSnippet,
|
InsertText: nonSnippet,
|
||||||
Score: matchScore * literalCandidateScore,
|
Score: matchScore * literalCandidateScore,
|
||||||
Kind: protocol.VariableCompletion,
|
Kind: protocol.VariableCompletion,
|
||||||
snippet: snip,
|
AdditionalTextEdits: edits,
|
||||||
|
snippet: snip,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// basicLiteral adds a literal completion item for the given basic
|
// basicLiteral adds a literal completion item for the given basic
|
||||||
// type name typeName.
|
// type name typeName.
|
||||||
func (c *completer) basicLiteral(T types.Type, typeName string, matchScore float64) {
|
func (c *completer) basicLiteral(T types.Type, typeName string, matchScore float64, edits []protocol.TextEdit) {
|
||||||
snip := &snippet.Builder{}
|
snip := &snippet.Builder{}
|
||||||
snip.WriteText(typeName + "(")
|
snip.WriteText(typeName + "(")
|
||||||
snip.WriteFinalTabstop()
|
snip.WriteFinalTabstop()
|
||||||
@ -280,12 +330,13 @@ func (c *completer) basicLiteral(T types.Type, typeName string, matchScore float
|
|||||||
nonSnippet := typeName + "()"
|
nonSnippet := typeName + "()"
|
||||||
|
|
||||||
c.items = append(c.items, CompletionItem{
|
c.items = append(c.items, CompletionItem{
|
||||||
Label: nonSnippet,
|
Label: nonSnippet,
|
||||||
InsertText: nonSnippet,
|
InsertText: nonSnippet,
|
||||||
Detail: T.String(),
|
Detail: T.String(),
|
||||||
Score: matchScore * literalCandidateScore,
|
Score: matchScore * literalCandidateScore,
|
||||||
Kind: protocol.VariableCompletion,
|
Kind: protocol.VariableCompletion,
|
||||||
snippet: snip,
|
AdditionalTextEdits: edits,
|
||||||
|
snippet: snip,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,6 +340,26 @@ func isEmptyInterface(T types.Type) bool {
|
|||||||
return intf != nil && intf.NumMethods() == 0
|
return intf != nil && intf.NumMethods() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isSelector returns the enclosing *ast.SelectorExpr when pos is in the
|
||||||
|
// selector.
|
||||||
|
func enclosingSelector(path []ast.Node, pos token.Pos) *ast.SelectorExpr {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sel, ok := path[0].(*ast.SelectorExpr); ok {
|
||||||
|
return sel
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := path[0].(*ast.Ident); ok && len(path) > 1 {
|
||||||
|
if sel, ok := path[1].(*ast.SelectorExpr); ok && pos >= sel.Sel.Pos() {
|
||||||
|
return sel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// typeConversion returns the type being converted to if call is a type
|
// typeConversion returns the type being converted to if call is a type
|
||||||
// conversion expression.
|
// conversion expression.
|
||||||
func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
|
func typeConversion(call *ast.CallExpr, info *types.Info) types.Type {
|
||||||
|
@ -3,6 +3,8 @@ package snippets
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/lsp/foo"
|
||||||
)
|
)
|
||||||
|
|
||||||
func _() {
|
func _() {
|
||||||
@ -129,3 +131,15 @@ func _() {
|
|||||||
var duplicateParams func(myImpl, int, myImpl)
|
var duplicateParams func(myImpl, int, myImpl)
|
||||||
duplicateParams = f //@snippet(" //", litFunc, "", "func(${1:mi} myImpl, ${2:_} int, ${3:_} myImpl) {$0\\}")
|
duplicateParams = f //@snippet(" //", litFunc, "", "func(${1:mi} myImpl, ${2:_} int, ${3:_} myImpl) {$0\\}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
StructFoo{} //@item(litStructFoo, "StructFoo{}", "struct{...}", "struct")
|
||||||
|
|
||||||
|
var sfp *foo.StructFoo
|
||||||
|
// Don't insert the "&" before "StructFoo{}".
|
||||||
|
sfp = foo.Str //@snippet(" //", litStructFoo, "StructFoo{$0\\}", "StructFoo{$0\\}")
|
||||||
|
|
||||||
|
var sf foo.StructFoo
|
||||||
|
sf = foo.Str //@snippet(" //", litStructFoo, "StructFoo{$0\\}", "StructFoo{$0\\}")
|
||||||
|
sf = foo. //@snippet(" //", litStructFoo, "StructFoo{$0\\}", "StructFoo{$0\\}")
|
||||||
|
}
|
2
internal/lsp/testdata/summary.txt.golden
vendored
2
internal/lsp/testdata/summary.txt.golden
vendored
@ -1,6 +1,6 @@
|
|||||||
-- summary --
|
-- summary --
|
||||||
CompletionsCount = 178
|
CompletionsCount = 178
|
||||||
CompletionSnippetCount = 36
|
CompletionSnippetCount = 39
|
||||||
UnimportedCompletionsCount = 1
|
UnimportedCompletionsCount = 1
|
||||||
DeepCompletionsCount = 5
|
DeepCompletionsCount = 5
|
||||||
FuzzyCompletionsCount = 6
|
FuzzyCompletionsCount = 6
|
||||||
|
Loading…
x
Reference in New Issue
Block a user