mirror of
https://github.com/golang/go
synced 2024-11-18 18:14:43 -07:00
internal/lsp/source: refactor completion
This change separates the completion formatting functions from the completion logic. It also simplifies the completion logic by necessary values per-request into a struct that is used throughout. Change-Id: Ieb6b09b7076ecf89c8b76ec12c1f1c9b10618cfe Reviewed-on: https://go-review.googlesource.com/c/tools/+/173779 Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
parent
ad9eeb8003
commit
550556f78a
@ -165,7 +165,6 @@ func (r *runner) Completion(t *testing.T, data tests.Completions, items tests.Co
|
||||
t.Errorf("%s: %s", src, diff)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we don't crash completing the first position in file set.
|
||||
firstFile := r.data.Config.Fset.Position(1).Filename
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
224
internal/lsp/source/completion_format.go
Normal file
224
internal/lsp/source/completion_format.go
Normal file
@ -0,0 +1,224 @@
|
||||
// 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// formatCompletion creates a completion item for a given types.Object.
|
||||
func (c *completer) item(obj types.Object, score float64) CompletionItem {
|
||||
label := obj.Name()
|
||||
detail := types.TypeString(obj.Type(), c.qf)
|
||||
var kind CompletionItemKind
|
||||
|
||||
switch o := obj.(type) {
|
||||
case *types.TypeName:
|
||||
detail, kind = formatType(o.Type(), c.qf)
|
||||
if obj.Parent() == types.Universe {
|
||||
detail = ""
|
||||
}
|
||||
case *types.Const:
|
||||
if obj.Parent() == types.Universe {
|
||||
detail = ""
|
||||
} else {
|
||||
val := o.Val().ExactString()
|
||||
if !strings.Contains(val, "\\n") { // skip any multiline constants
|
||||
label += " = " + o.Val().ExactString()
|
||||
}
|
||||
}
|
||||
kind = ConstantCompletionItem
|
||||
case *types.Var:
|
||||
if _, ok := o.Type().(*types.Struct); ok {
|
||||
detail = "struct{...}" // for anonymous structs
|
||||
}
|
||||
if o.IsField() {
|
||||
kind = FieldCompletionItem
|
||||
} else if c.isParameter(o) {
|
||||
kind = ParameterCompletionItem
|
||||
} else {
|
||||
kind = VariableCompletionItem
|
||||
}
|
||||
case *types.Func:
|
||||
if sig, ok := o.Type().(*types.Signature); ok {
|
||||
label += formatParams(sig, c.qf)
|
||||
detail = strings.Trim(types.TypeString(sig.Results(), c.qf), "()")
|
||||
kind = FunctionCompletionItem
|
||||
if sig.Recv() != nil {
|
||||
kind = MethodCompletionItem
|
||||
}
|
||||
}
|
||||
case *types.Builtin:
|
||||
item, ok := builtinDetails[obj.Name()]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
label, detail = item.label, item.detail
|
||||
kind = FunctionCompletionItem
|
||||
case *types.PkgName:
|
||||
kind = PackageCompletionItem
|
||||
detail = fmt.Sprintf("\"%s\"", o.Imported().Path())
|
||||
case *types.Nil:
|
||||
kind = VariableCompletionItem
|
||||
detail = ""
|
||||
}
|
||||
detail = strings.TrimPrefix(detail, "untyped ")
|
||||
|
||||
return CompletionItem{
|
||||
Label: label,
|
||||
Detail: detail,
|
||||
Kind: kind,
|
||||
Score: score,
|
||||
}
|
||||
}
|
||||
|
||||
// isParameter returns true if the given *types.Var is a parameter
|
||||
// of the enclosingFunction.
|
||||
func (c *completer) isParameter(v *types.Var) bool {
|
||||
if c.enclosingFunction == nil {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < c.enclosingFunction.Params().Len(); i++ {
|
||||
if c.enclosingFunction.Params().At(i) == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// formatType returns the detail and kind for an object of type *types.TypeName.
|
||||
func formatType(typ types.Type, qf types.Qualifier) (detail string, kind CompletionItemKind) {
|
||||
if types.IsInterface(typ) {
|
||||
detail = "interface{...}"
|
||||
kind = InterfaceCompletionItem
|
||||
} else if _, ok := typ.(*types.Struct); ok {
|
||||
detail = "struct{...}"
|
||||
kind = StructCompletionItem
|
||||
} else if typ != typ.Underlying() {
|
||||
detail, kind = formatType(typ.Underlying(), qf)
|
||||
} else {
|
||||
detail = types.TypeString(typ, qf)
|
||||
kind = TypeCompletionItem
|
||||
}
|
||||
return detail, kind
|
||||
}
|
||||
|
||||
// formatParams correctly format the parameters of a function.
|
||||
func formatParams(sig *types.Signature, qf types.Qualifier) string {
|
||||
var b bytes.Buffer
|
||||
b.WriteByte('(')
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
el := sig.Params().At(i)
|
||||
typ := types.TypeString(el.Type(), qf)
|
||||
// Handle a variadic parameter (can only be the final parameter).
|
||||
if sig.Variadic() && i == sig.Params().Len()-1 {
|
||||
typ = strings.Replace(typ, "[]", "...", 1)
|
||||
}
|
||||
if el.Name() == "" {
|
||||
fmt.Fprintf(&b, "%v", typ)
|
||||
} else {
|
||||
fmt.Fprintf(&b, "%v %v", el.Name(), typ)
|
||||
}
|
||||
}
|
||||
b.WriteByte(')')
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// qualifier returns a function that appropriately formats a types.PkgName
|
||||
// appearing in a *ast.File.
|
||||
func qualifier(f *ast.File, pkg *types.Package, info *types.Info) types.Qualifier {
|
||||
// Construct mapping of import paths to their defined or implicit names.
|
||||
imports := make(map[*types.Package]string)
|
||||
for _, imp := range f.Imports {
|
||||
var obj types.Object
|
||||
if imp.Name != nil {
|
||||
obj = info.Defs[imp.Name]
|
||||
} else {
|
||||
obj = info.Implicits[imp]
|
||||
}
|
||||
if pkgname, ok := obj.(*types.PkgName); ok {
|
||||
imports[pkgname.Imported()] = pkgname.Name()
|
||||
}
|
||||
}
|
||||
// Define qualifier to replace full package paths with names of the imports.
|
||||
return func(p *types.Package) string {
|
||||
if p == pkg {
|
||||
return ""
|
||||
}
|
||||
if name, ok := imports[p]; ok {
|
||||
return name
|
||||
}
|
||||
return p.Name()
|
||||
}
|
||||
}
|
||||
|
||||
type itemDetails struct {
|
||||
label, detail string
|
||||
}
|
||||
|
||||
var builtinDetails = map[string]itemDetails{
|
||||
"append": { // append(slice []T, elems ...T)
|
||||
label: "append(slice []T, elems ...T)",
|
||||
detail: "[]T",
|
||||
},
|
||||
"cap": { // cap(v []T) int
|
||||
label: "cap(v []T)",
|
||||
detail: "int",
|
||||
},
|
||||
"close": { // close(c chan<- T)
|
||||
label: "close(c chan<- T)",
|
||||
},
|
||||
"complex": { // complex(r, i float64) complex128
|
||||
label: "complex(real float64, imag float64)",
|
||||
detail: "complex128",
|
||||
},
|
||||
"copy": { // copy(dst, src []T) int
|
||||
label: "copy(dst []T, src []T)",
|
||||
detail: "int",
|
||||
},
|
||||
"delete": { // delete(m map[T]T1, key T)
|
||||
label: "delete(m map[K]V, key K)",
|
||||
},
|
||||
"imag": { // imag(c complex128) float64
|
||||
label: "imag(complex128)",
|
||||
detail: "float64",
|
||||
},
|
||||
"len": { // len(v T) int
|
||||
label: "len(T)",
|
||||
detail: "int",
|
||||
},
|
||||
"make": { // make(t T, size ...int) T
|
||||
label: "make(t T, size ...int)",
|
||||
detail: "T",
|
||||
},
|
||||
"new": { // new(T) *T
|
||||
label: "new(T)",
|
||||
detail: "*T",
|
||||
},
|
||||
"panic": { // panic(v interface{})
|
||||
label: "panic(interface{})",
|
||||
},
|
||||
"print": { // print(args ...T)
|
||||
label: "print(args ...T)",
|
||||
},
|
||||
"println": { // println(args ...T)
|
||||
label: "println(args ...T)",
|
||||
},
|
||||
"real": { // real(c complex128) float64
|
||||
label: "real(complex128)",
|
||||
detail: "float64",
|
||||
},
|
||||
"recover": { // recover() interface{}
|
||||
label: "recover()",
|
||||
detail: "interface{}",
|
||||
},
|
||||
}
|
@ -59,11 +59,11 @@ func SignatureHelp(ctx context.Context, f File, pos token.Pos) (*SignatureInform
|
||||
return nil, fmt.Errorf("cannot find signature for Fun %[1]T (%[1]v)", callExpr.Fun)
|
||||
}
|
||||
|
||||
pkgStringer := qualifier(fAST, pkg.GetTypes(), pkg.GetTypesInfo())
|
||||
qf := qualifier(fAST, pkg.GetTypes(), pkg.GetTypesInfo())
|
||||
var paramInfo []ParameterInformation
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
param := sig.Params().At(i)
|
||||
label := types.TypeString(param.Type(), pkgStringer)
|
||||
label := types.TypeString(param.Type(), qf)
|
||||
if sig.Variadic() && i == sig.Params().Len()-1 {
|
||||
label = strings.Replace(label, "[]", "...", 1)
|
||||
}
|
||||
@ -112,9 +112,9 @@ func SignatureHelp(ctx context.Context, f File, pos token.Pos) (*SignatureInform
|
||||
label = "func"
|
||||
}
|
||||
|
||||
label += formatParams(sig.Params(), sig.Variadic(), pkgStringer)
|
||||
label += formatParams(sig, qf)
|
||||
if sig.Results().Len() > 0 {
|
||||
results := types.TypeString(sig.Results(), pkgStringer)
|
||||
results := types.TypeString(sig.Results(), qf)
|
||||
if sig.Results().Len() == 1 && sig.Results().At(0).Name() == "" {
|
||||
// Trim off leading/trailing parens to avoid results like "foo(a int) (int)".
|
||||
results = strings.Trim(results, "()")
|
||||
|
111
internal/lsp/source/util.go
Normal file
111
internal/lsp/source/util.go
Normal file
@ -0,0 +1,111 @@
|
||||
package source
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
)
|
||||
|
||||
// indexExprAtPos returns the index of the expression containing pos.
|
||||
func indexExprAtPos(pos token.Pos, args []ast.Expr) int {
|
||||
for i, expr := range args {
|
||||
if expr.Pos() <= pos && pos <= expr.End() {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return len(args)
|
||||
}
|
||||
|
||||
func exprAtPos(pos token.Pos, args []ast.Expr) ast.Expr {
|
||||
for _, expr := range args {
|
||||
if expr.Pos() <= pos && pos <= expr.End() {
|
||||
return expr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fieldSelections returns the set of fields that can
|
||||
// be selected from a value of type T.
|
||||
func fieldSelections(T types.Type) (fields []*types.Var) {
|
||||
// TODO(adonovan): this algorithm doesn't exclude ambiguous
|
||||
// selections that match more than one field/method.
|
||||
// types.NewSelectionSet should do that for us.
|
||||
|
||||
seen := make(map[types.Type]bool) // for termination on recursive types
|
||||
var visit func(T types.Type)
|
||||
visit = func(T types.Type) {
|
||||
if !seen[T] {
|
||||
seen[T] = true
|
||||
if T, ok := deref(T).Underlying().(*types.Struct); ok {
|
||||
for i := 0; i < T.NumFields(); i++ {
|
||||
f := T.Field(i)
|
||||
fields = append(fields, f)
|
||||
if f.Anonymous() {
|
||||
visit(f.Type())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(T)
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
// resolveInvalid traverses the node of the AST that defines the scope
|
||||
// containing the declaration of obj, and attempts to find a user-friendly
|
||||
// name for its invalid type. The resulting Object and its Type are fake.
|
||||
func resolveInvalid(obj types.Object, node ast.Node, info *types.Info) types.Object {
|
||||
// Construct a fake type for the object and return a fake object with this type.
|
||||
formatResult := func(expr ast.Expr) types.Object {
|
||||
var typename string
|
||||
switch t := expr.(type) {
|
||||
case *ast.SelectorExpr:
|
||||
typename = fmt.Sprintf("%s.%s", t.X, t.Sel)
|
||||
case *ast.Ident:
|
||||
typename = t.String()
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), nil, nil)
|
||||
return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ)
|
||||
}
|
||||
var resultExpr ast.Expr
|
||||
ast.Inspect(node, func(node ast.Node) bool {
|
||||
switch n := node.(type) {
|
||||
case *ast.ValueSpec:
|
||||
for _, name := range n.Names {
|
||||
if info.Defs[name] == obj {
|
||||
resultExpr = n.Type
|
||||
}
|
||||
}
|
||||
return false
|
||||
case *ast.Field: // This case handles parameters and results of a FuncDecl or FuncLit.
|
||||
for _, name := range n.Names {
|
||||
if info.Defs[name] == obj {
|
||||
resultExpr = n.Type
|
||||
}
|
||||
}
|
||||
return false
|
||||
// TODO(rstambler): Handle range statements.
|
||||
default:
|
||||
return true
|
||||
}
|
||||
})
|
||||
return formatResult(resultExpr)
|
||||
}
|
||||
|
||||
func isPointer(T types.Type) bool {
|
||||
_, ok := T.(*types.Pointer)
|
||||
return ok
|
||||
}
|
||||
|
||||
// deref returns a pointer's element type; otherwise it returns typ.
|
||||
func deref(typ types.Type) types.Type {
|
||||
if p, ok := typ.Underlying().(*types.Pointer); ok {
|
||||
return p.Elem()
|
||||
}
|
||||
return typ
|
||||
}
|
Loading…
Reference in New Issue
Block a user