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>
diff --git a/internal/lsp/lsp_test.go b/internal/lsp/lsp_test.go
index 2f6d8f1..84b4e6e 100644
--- a/internal/lsp/lsp_test.go
+++ b/internal/lsp/lsp_test.go
@@ -165,7 +165,6 @@
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
diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go
index d9879f5..52b5944 100644
--- a/internal/lsp/source/completion.go
+++ b/internal/lsp/source/completion.go
@@ -1,13 +1,15 @@
+// Copyright 2018 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"
"context"
"fmt"
"go/ast"
"go/token"
"go/types"
- "strings"
"golang.org/x/tools/go/ast/astutil"
)
@@ -34,19 +36,79 @@
PackageCompletionItem
)
-// stdScore is the base score value set for all completion items.
-const stdScore float64 = 1.0
+// Scoring constants are used for weighting the relevance of different candidates.
+const (
+ // stdScore is the base score for all completion items.
+ stdScore float64 = 1.0
-// finder is a function used to record a completion candidate item in a list of
-// completion items.
-type finder func(types.Object, float64, []CompletionItem) []CompletionItem
+ // highScore indicates a very relevant completion item.
+ highScore float64 = 10.0
+
+ // lowScore indicates an irrelevant or not useful completion item.
+ lowScore float64 = 0.01
+)
+
+// completer contains the necessary information for a single completion request.
+type completer struct {
+ // Package-specific fields.
+ types *types.Package
+ info *types.Info
+ qf types.Qualifier
+
+ // pos is the position at which the request was triggered.
+ pos token.Pos
+
+ // path is the path of AST nodes enclosing the position.
+ path []ast.Node
+
+ // seen is the map that ensures we do not return duplicate results.
+ seen map[types.Object]bool
+
+ // items is the list of completion items returned.
+ items []CompletionItem
+
+ // prefix is the already-typed portion of the completion candidates.
+ prefix string
+
+ // expectedType is the type we expect the completion candidate to be.
+ // It may not be set.
+ expectedType types.Type
+
+ // enclosingFunction is the function declaration enclosing the position.
+ enclosingFunction *types.Signature
+
+ // preferTypeNames is true if we are completing at a position that expects a type,
+ // not a value.
+ preferTypeNames bool
+}
+
+// found adds a candidate completion.
+//
+// Only the first candidate of a given name is considered.
+func (c *completer) found(obj types.Object, weight float64) {
+ if obj.Pkg() != nil && obj.Pkg() != c.types && !obj.Exported() {
+ return // inaccessible
+ }
+ if c.seen[obj] {
+ return
+ }
+ c.seen[obj] = true
+ if c.matchingType(obj.Type()) {
+ weight *= highScore
+ }
+ if _, ok := obj.(*types.TypeName); !ok && c.preferTypeNames {
+ weight *= lowScore
+ }
+ c.items = append(c.items, c.item(obj, weight))
+}
// Completion returns a list of possible candidates for completion, given a
-// a file and a position. The prefix is computed based on the preceding
-// identifier and can be used by the client to score the quality of the
-// completion. For instance, some clients may tolerate imperfect matches as
-// valid completion results, since users may make typos.
-func Completion(ctx context.Context, f File, pos token.Pos) (items []CompletionItem, prefix string, err error) {
+// a file and a position.
+//
+// The prefix is computed based on the preceding identifier and can be used by
+// the client to score the quality of the completion. For instance, some clients
+// may tolerate imperfect matches as valid completion results, since users may make typos.
+func Completion(ctx context.Context, f File, pos token.Pos) ([]CompletionItem, string, error) {
file := f.GetAST(ctx)
pkg := f.GetPackage(ctx)
if pkg.IsIllTyped() {
@@ -54,68 +116,58 @@
}
// Completion is based on what precedes the cursor.
- // To understand what we are completing, find the path to the
- // position before pos.
+ // Find the path to the position before pos.
path, _ := astutil.PathEnclosingInterval(file, pos-1, pos-1)
if path == nil {
return nil, "", fmt.Errorf("cannot find node enclosing position")
}
-
// Skip completion inside comments.
- if inComment(pos, file.Comments) {
- return items, prefix, nil
+ for _, g := range file.Comments {
+ if g.Pos() <= pos && pos <= g.End() {
+ return nil, "", nil
+ }
}
-
// Skip completion inside any kind of literal.
if _, ok := path[0].(*ast.BasicLit); ok {
- return items, prefix, nil
+ return nil, "", nil
}
- // Save certain facts about the query position, including the expected type
- // of the completion result, the signature of the function enclosing the
- // position.
- typ := expectedType(path, pos, pkg.GetTypesInfo())
- sig := enclosingFunction(path, pos, pkg.GetTypesInfo())
- pkgStringer := qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
- preferTypeNames := wantTypeNames(pos, path)
+ c := &completer{
+ types: pkg.GetTypes(),
+ info: pkg.GetTypesInfo(),
+ qf: qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo()),
+ path: path,
+ pos: pos,
+ seen: make(map[types.Object]bool),
+ expectedType: expectedType(path, pos, pkg.GetTypesInfo()),
+ enclosingFunction: enclosingFunction(path, pos, pkg.GetTypesInfo()),
+ preferTypeNames: preferTypeNames(path, pos),
+ }
- seen := make(map[types.Object]bool)
- // found adds a candidate completion.
- // Only the first candidate of a given name is considered.
- found := func(obj types.Object, weight float64, items []CompletionItem) []CompletionItem {
- if obj.Pkg() != nil && obj.Pkg() != pkg.GetTypes() && !obj.Exported() {
- return items // inaccessible
- }
+ // Composite literals are handled entirely separately.
+ if lit, kv, ok := c.enclosingCompositeLiteral(); lit != nil {
+ c.expectedType = c.expectedCompositeLiteralType(lit, kv)
- if !seen[obj] {
- seen[obj] = true
- if typ != nil && matchingTypes(typ, obj.Type()) {
- weight *= 10.0
+ // ok means that we should return composite literal completions for this position.
+ if ok {
+ if err := c.compositeLiteral(lit, kv); err != nil {
+ return nil, "", err
}
- if _, ok := obj.(*types.TypeName); !ok && preferTypeNames {
- weight *= 0.01
- }
- item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
- return isParameter(sig, v)
- })
- items = append(items, item)
+ return c.items, c.prefix, nil
}
- return items
}
- // The position is within a composite literal.
- if items, prefix, ok := complit(path, pos, pkg.GetTypes(), pkg.GetTypesInfo(), found); ok {
- return items, prefix, nil
- }
switch n := path[0].(type) {
case *ast.Ident:
// Set the filter prefix.
- prefix = n.Name[:pos-n.Pos()]
+ c.prefix = n.Name[:pos-n.Pos()]
// Is this the Sel part of a selector?
if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == n {
- items, err = selector(sel, pos, pkg.GetTypesInfo(), found)
- return items, prefix, err
+ if err := c.selector(sel); err != nil {
+ return nil, "", err
+ }
+ return c.items, c.prefix, nil
}
// reject defining identifiers
if obj, ok := pkg.GetTypesInfo().Defs[n]; ok {
@@ -130,114 +182,86 @@
return nil, "", fmt.Errorf("this is a definition%s", of)
}
}
-
- items = append(items, lexical(path, pos, pkg.GetTypes(), pkg.GetTypesInfo(), found)...)
+ if err := c.lexical(); err != nil {
+ return nil, "", err
+ }
// The function name hasn't been typed yet, but the parens are there:
// recv.‸(arg)
case *ast.TypeAssertExpr:
// Create a fake selector expression.
- items, err = selector(&ast.SelectorExpr{X: n.X}, pos, pkg.GetTypesInfo(), found)
- return items, prefix, err
+ if err := c.selector(&ast.SelectorExpr{X: n.X}); err != nil {
+ return nil, "", err
+ }
case *ast.SelectorExpr:
- items, err = selector(n, pos, pkg.GetTypesInfo(), found)
- return items, prefix, err
+ if err := c.selector(n); err != nil {
+ return nil, "", err
+ }
default:
// fallback to lexical completions
- return lexical(path, pos, pkg.GetTypes(), pkg.GetTypesInfo(), found), "", nil
+ if err := c.lexical(); err != nil {
+ return nil, "", err
+ }
}
- return items, prefix, nil
+ return c.items, c.prefix, nil
}
-// selector finds completions for
-// the specified selector expression.
-// TODO(rstambler): Set the prefix filter correctly for selectors.
-func selector(sel *ast.SelectorExpr, pos token.Pos, info *types.Info, found finder) (items []CompletionItem, err error) {
+// selector finds completions for the specified selector expression.
+func (c *completer) selector(sel *ast.SelectorExpr) error {
// Is sel a qualified identifier?
if id, ok := sel.X.(*ast.Ident); ok {
- if pkgname, ok := info.Uses[id].(*types.PkgName); ok {
+ if pkgname, ok := c.info.Uses[id].(*types.PkgName); ok {
// Enumerate package members.
- // TODO(adonovan): can Imported() be nil?
scope := pkgname.Imported().Scope()
- // TODO testcase: bad import
for _, name := range scope.Names() {
- items = found(scope.Lookup(name), stdScore, items)
+ c.found(scope.Lookup(name), stdScore)
}
- return items, nil
+ return nil
}
}
- // Inv: sel is a true selector.
- tv, ok := info.Types[sel.X]
+ // Invariant: sel is a true selector.
+ tv, ok := c.info.Types[sel.X]
if !ok {
- return nil, fmt.Errorf("cannot resolve %s", sel.X)
+ return fmt.Errorf("cannot resolve %s", sel.X)
}
- // methods of T
+ // Add methods of T.
mset := types.NewMethodSet(tv.Type)
for i := 0; i < mset.Len(); i++ {
- items = found(mset.At(i).Obj(), stdScore, items)
+ c.found(mset.At(i).Obj(), stdScore)
}
- // methods of *T
+ // Add methods of *T.
if tv.Addressable() && !types.IsInterface(tv.Type) && !isPointer(tv.Type) {
mset := types.NewMethodSet(types.NewPointer(tv.Type))
for i := 0; i < mset.Len(); i++ {
- items = found(mset.At(i).Obj(), stdScore, items)
+ c.found(mset.At(i).Obj(), stdScore)
}
}
- // fields of T
+ // Add fields of T.
for _, f := range fieldSelections(tv.Type) {
- items = found(f, stdScore, items)
+ c.found(f, stdScore)
}
-
- return items, nil
-}
-
-// wantTypeNames checks if given token position is inside func receiver, type params
-// or type results (e.g func (<>) foo(<>) (<>) {} ).
-func wantTypeNames(pos token.Pos, path []ast.Node) bool {
- for _, p := range path {
- switch n := p.(type) {
- case *ast.FuncDecl:
- recv := n.Recv
- if recv != nil && recv.Pos() <= pos && pos <= recv.End() {
- return true
- }
-
- if n.Type != nil {
- params := n.Type.Params
- if params != nil && params.Pos() <= pos && pos <= params.End() {
- return true
- }
-
- results := n.Type.Results
- if results != nil && results.Pos() <= pos && pos <= results.End() {
- return true
- }
- }
- return false
- }
- }
- return false
+ return nil
}
// lexical finds completions in the lexical environment.
-func lexical(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Info, found finder) (items []CompletionItem) {
+func (c *completer) lexical() error {
var scopes []*types.Scope // scopes[i], where i<len(path), is the possibly nil Scope of path[i].
- for _, n := range path {
+ for _, n := range c.path {
switch node := n.(type) {
case *ast.FuncDecl:
n = node.Type
case *ast.FuncLit:
n = node.Type
}
- scopes = append(scopes, info.Scopes[n])
+ scopes = append(scopes, c.info.Scopes[n])
}
- scopes = append(scopes, pkg.Scope(), types.Universe)
+ scopes = append(scopes, c.types.Scope(), types.Universe)
// Track seen variables to avoid showing completions for shadowed variables.
// This works since we look at scopes from innermost to outermost.
@@ -249,7 +273,7 @@
continue
}
for _, name := range scope.Names() {
- declScope, obj := scope.LookupParent(name, pos)
+ declScope, obj := scope.LookupParent(name, c.pos)
if declScope != scope {
continue // Name was declared in some enclosing scope, or not at all.
}
@@ -259,13 +283,13 @@
// Match the scope to its ast.Node. If the scope is the package scope,
// use the *ast.File as the starting node.
var node ast.Node
- if i < len(path) {
- node = path[i]
- } else if i == len(path) { // use the *ast.File for package scope
- node = path[i-1]
+ if i < len(c.path) {
+ node = c.path[i]
+ } else if i == len(c.path) { // use the *ast.File for package scope
+ node = c.path[i-1]
}
if node != nil {
- if resolved := resolveInvalid(obj, node, info); resolved != nil {
+ if resolved := resolveInvalid(obj, node, c.info); resolved != nil {
obj = resolved
}
}
@@ -279,102 +303,32 @@
// If we haven't already added a candidate for an object with this name.
if _, ok := seen[obj.Name()]; !ok {
seen[obj.Name()] = struct{}{}
- items = found(obj, score, items)
+ c.found(obj, score)
}
}
}
- return items
+ return nil
}
-// inComment checks if given token position is inside ast.Comment node.
-func inComment(pos token.Pos, commentGroups []*ast.CommentGroup) bool {
- for _, g := range commentGroups {
- if g.Pos() <= pos && pos <= g.End() {
- return true
- }
- }
- return false
-}
-
-// complit finds completions for field names inside a composite literal.
-// It reports whether the node was handled as part of a composite literal.
-func complit(path []ast.Node, pos token.Pos, pkg *types.Package, info *types.Info, found finder) (items []CompletionItem, prefix string, ok bool) {
- var lit *ast.CompositeLit
-
- // First, determine if the pos is within a composite literal.
- switch n := path[0].(type) {
- case *ast.CompositeLit:
- // The enclosing node will be a composite literal if the user has just
- // opened the curly brace (e.g. &x{<>) or the completion request is triggered
- // from an already completed composite literal expression (e.g. &x{foo: 1, <>})
- //
- // If the cursor position is within a key-value expression inside the composite
- // literal, we try to determine if it is before or after the colon. If it is before
- // the colon, we return field completions. If the cursor does not belong to any
- // expression within the composite literal, we show composite literal completions.
- var expr ast.Expr
- for _, e := range n.Elts {
- if e.Pos() <= pos && pos < e.End() {
- expr = e
- break
- }
- }
- lit = n
- // If the position belongs to a key-value expression and is after the colon,
- // don't show composite literal completions.
- if kv, ok := expr.(*ast.KeyValueExpr); ok && pos > kv.Colon {
- lit = nil
- }
- case *ast.KeyValueExpr:
- // If the enclosing node is a key-value expression (e.g. &x{foo: <>}),
- // we show composite literal completions if the cursor position is before the colon.
- if len(path) > 1 && pos < n.Colon {
- if l, ok := path[1].(*ast.CompositeLit); ok {
- lit = l
- }
- }
+// compositeLiteral finds completions for field names inside a composite literal.
+func (c *completer) compositeLiteral(lit *ast.CompositeLit, kv *ast.KeyValueExpr) error {
+ switch n := c.path[0].(type) {
case *ast.Ident:
- prefix = n.Name[:pos-n.Pos()]
-
- // If the enclosing node is an identifier, it can either be an identifier that is
- // part of a composite literal (e.g. &x{fo<>}), or it can be an identifier that is
- // part of a key-value expression, which is part of a composite literal (e.g. &x{foo: ba<>).
- // We handle both of these cases, showing composite literal completions only if
- // the cursor position for the key-value expression is before the colon.
- if len(path) > 1 {
- if l, ok := path[1].(*ast.CompositeLit); ok {
- lit = l
- } else if len(path) > 2 {
- if l, ok := path[2].(*ast.CompositeLit); ok {
- // Confirm that cursor position is inside curly braces.
- if l.Lbrace <= pos && pos <= l.Rbrace {
- lit = l
- if kv, ok := path[1].(*ast.KeyValueExpr); ok {
- if pos > kv.Colon {
- lit = nil
- }
- }
- }
- }
- }
- }
- }
- // We are not in a composite literal.
- if lit == nil {
- return nil, prefix, false
+ c.prefix = n.Name[:c.pos-n.Pos()]
}
// Mark fields of the composite literal that have already been set,
// except for the current field.
- hasKeys := false // true if the composite literal already has key-value pairs
+ hasKeys := kv != nil // true if the composite literal already has key-value pairs
addedFields := make(map[*types.Var]bool)
for _, el := range lit.Elts {
- if kv, ok := el.(*ast.KeyValueExpr); ok {
- hasKeys = true
- if kv.Pos() <= pos && pos <= kv.End() {
+ if kvExpr, ok := el.(*ast.KeyValueExpr); ok {
+ if kv == kvExpr {
continue
}
- if key, ok := kv.Key.(*ast.Ident); ok {
- if used, ok := info.Uses[key]; ok {
+
+ hasKeys = true
+ if key, ok := kvExpr.Key.(*ast.Ident); ok {
+ if used, ok := c.info.Uses[key]; ok {
if usedVar, ok := used.(*types.Var); ok {
addedFields[usedVar] = true
}
@@ -384,52 +338,67 @@
}
// If the underlying type of the composite literal is a struct,
// collect completions for the fields of this struct.
- if tv, ok := info.Types[lit]; ok {
- var structPkg *types.Package // package containing the struct type declaration
- if s, ok := tv.Type.Underlying().(*types.Struct); ok {
- for i := 0; i < s.NumFields(); i++ {
- field := s.Field(i)
+ if tv, ok := c.info.Types[lit]; ok {
+ switch t := tv.Type.Underlying().(type) {
+ case *types.Struct:
+ var structPkg *types.Package // package that struct is declared in
+ for i := 0; i < t.NumFields(); i++ {
+ field := t.Field(i)
if i == 0 {
structPkg = field.Pkg()
}
if !addedFields[field] {
- items = found(field, 10.0, items)
+ c.found(field, highScore)
}
}
// Add lexical completions if the user hasn't typed a key value expression
// and if the struct fields are defined in the same package as the user is in.
- if !hasKeys && structPkg == pkg {
- items = append(items, lexical(path, pos, pkg, info, found)...)
+ if !hasKeys && structPkg == c.types {
+ return c.lexical()
}
- return items, prefix, true
+ default:
+ return c.lexical()
}
}
- return items, prefix, false
+ return nil
}
-// enclosingCompLit returns the composite literal and key value expression, if
-// any, enclosing the given position.
-func enclosingCompLit(pos token.Pos, path []ast.Node) (*ast.CompositeLit, *ast.KeyValueExpr) {
- var keyVal *ast.KeyValueExpr
-
- for _, n := range path {
+func (c *completer) enclosingCompositeLiteral() (lit *ast.CompositeLit, kv *ast.KeyValueExpr, ok bool) {
+ for _, n := range c.path {
switch n := n.(type) {
case *ast.CompositeLit:
- // pos isn't part of the composite literal unless it falls within the curly
- // braces (e.g. "foo.Foo<>Struct{}").
- if n.Lbrace <= pos && pos <= n.Rbrace {
- if keyVal == nil {
- if i := exprAtPos(pos, n.Elts); i < len(n.Elts) {
- keyVal, _ = n.Elts[i].(*ast.KeyValueExpr)
- }
+ // The enclosing node will be a composite literal if the user has just
+ // opened the curly brace (e.g. &x{<>) or the completion request is triggered
+ // from an already completed composite literal expression (e.g. &x{foo: 1, <>})
+ //
+ // The position is not part of the composite literal unless it falls within the
+ // curly braces (e.g. "foo.Foo<>Struct{}").
+ if n.Lbrace <= c.pos && c.pos <= n.Rbrace {
+ lit = n
+
+ // If the cursor position is within a key-value expression inside the composite
+ // literal, we try to determine if it is before or after the colon. If it is before
+ // the colon, we return field completions. If the cursor does not belong to any
+ // expression within the composite literal, we show composite literal completions.
+ if expr, isKeyValue := exprAtPos(c.pos, n.Elts).(*ast.KeyValueExpr); kv == nil && isKeyValue {
+ kv = expr
+
+ // If the position belongs to a key-value expression and is after the colon,
+ // don't show composite literal completions.
+ ok = c.pos <= kv.Colon
+ } else if kv == nil {
+ ok = true
}
-
- return n, keyVal
}
-
- return nil, nil
+ return lit, kv, ok
case *ast.KeyValueExpr:
- keyVal = n
+ if kv == nil {
+ kv = n
+
+ // If the position belongs to a key-value expression and is after the colon,
+ // don't show composite literal completions.
+ ok = c.pos <= kv.Colon
+ }
case *ast.FuncType, *ast.CallExpr, *ast.TypeAssertExpr:
// These node types break the type link between the leaf node and
// the composite literal. The type of the leaf node becomes unrelated
@@ -437,164 +406,13 @@
// inappropriate completions. For example, "Foo{Bar: x.Baz(<>)}"
// should complete as a function argument to Baz, not part of the Foo
// composite literal.
- return nil, nil
+ return nil, nil, false
}
}
-
- return nil, nil
+ return lit, kv, ok
}
-// formatCompletion creates a completion item for a given types.Object.
-func formatCompletion(obj types.Object, qualifier types.Qualifier, score float64, isParam func(*types.Var) bool) CompletionItem {
- label := obj.Name()
- detail := types.TypeString(obj.Type(), qualifier)
- var kind CompletionItemKind
-
- switch o := obj.(type) {
- case *types.TypeName:
- detail, kind = formatType(o.Type(), qualifier)
- 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 isParam(o) {
- kind = ParameterCompletionItem
- } else {
- kind = VariableCompletionItem
- }
- case *types.Func:
- if sig, ok := o.Type().(*types.Signature); ok {
- label += formatParams(sig.Params(), sig.Variadic(), qualifier)
- detail = strings.Trim(types.TypeString(sig.Results(), qualifier), "()")
- 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,
- }
-}
-
-// formatType returns the detail and kind for an object of type *types.TypeName.
-func formatType(typ types.Type, qualifier 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(), qualifier)
- } else {
- detail = types.TypeString(typ, qualifier)
- kind = TypeCompletionItem
- }
- return detail, kind
-}
-
-// formatParams correctly format the parameters of a function.
-func formatParams(t *types.Tuple, variadic bool, qualifier types.Qualifier) string {
- var b bytes.Buffer
- b.WriteByte('(')
- for i := 0; i < t.Len(); i++ {
- if i > 0 {
- b.WriteString(", ")
- }
- el := t.At(i)
- typ := types.TypeString(el.Type(), qualifier)
- // Handle a variadic parameter (can only be the final parameter).
- if variadic && i == t.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()
-}
-
-// isParameter returns true if the given *types.Var is a parameter to the given
-// *types.Signature.
-func isParameter(sig *types.Signature, v *types.Var) bool {
- if sig == nil {
- return false
- }
- for i := 0; i < sig.Params().Len(); i++ {
- if sig.Params().At(i) == v {
- return true
- }
- }
- return false
-}
-
-// 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()
- }
-}
-
-// enclosingFunction returns the signature of the function enclosing the given
-// position.
+// enclosingFunction returns the signature of the function enclosing the given position.
func enclosingFunction(path []ast.Node, pos token.Pos, info *types.Info) *types.Signature {
for _, node := range path {
switch t := node.(type) {
@@ -611,77 +429,57 @@
return nil
}
-func expectedCompLitType(cl *ast.CompositeLit, kv *ast.KeyValueExpr, pos token.Pos, info *types.Info) types.Type {
- // Get the type of the *ast.CompositeLit we belong to.
- clType, ok := info.Types[cl]
+func (c *completer) expectedCompositeLiteralType(cl *ast.CompositeLit, kv *ast.KeyValueExpr) types.Type {
+ clType, ok := c.info.Types[cl]
if !ok {
return nil
}
-
switch t := clType.Type.Underlying().(type) {
case *types.Slice:
return t.Elem()
case *types.Array:
return t.Elem()
case *types.Map:
- // If pos isn't in a key/value expression or it is on the left side
- // of a key/value colon, a key must be entered next.
- if kv == nil || pos <= kv.Colon {
+ if kv == nil || c.pos <= kv.Colon {
return t.Key()
}
-
return t.Elem()
case *types.Struct:
- // pos is in a key/value expression
+ // If we are in a key-value expression.
if kv != nil {
- // If pos is to left of the colon, it is a struct field name,
- // so there is no expected type.
- if pos <= kv.Colon {
+ // There is no expected type for a struct field name.
+ if c.pos <= kv.Colon {
return nil
}
-
- if keyIdent, ok := kv.Key.(*ast.Ident); ok {
- // Find the type of the struct field whose name matches the key.
+ // Find the type of the struct field whose name matches the key.
+ if key, ok := kv.Key.(*ast.Ident); ok {
for i := 0; i < t.NumFields(); i++ {
- if field := t.Field(i); field.Name() == keyIdent.Name {
+ if field := t.Field(i); field.Name() == key.Name {
return field.Type()
}
}
}
-
return nil
}
-
- hasKeys := false // true if the composite literal has any key/value pairs
+ // We are in a struct literal, but not a specific key-value pair.
+ // If the struct literal doesn't have explicit field names,
+ // we may still be able to suggest an expected type.
for _, el := range cl.Elts {
if _, ok := el.(*ast.KeyValueExpr); ok {
- hasKeys = true
- break
+ return nil
}
}
-
- // The struct literal is using field names, but pos is not in a key/value
- // pair. A field name must be entered next, so there is no expected type.
- if hasKeys {
- return nil
- }
-
// The order of the literal fields must match the order in the struct definition.
- // Find the element pos falls in and use the corresponding field's type.
- if i := exprAtPos(pos, cl.Elts); i < t.NumFields() {
+ // Find the element that the position belongs to and suggest that field's type.
+ if i := indexExprAtPos(c.pos, cl.Elts); i < t.NumFields() {
return t.Field(i).Type()
}
}
-
return nil
}
// expectedType returns the expected type for an expression at the query position.
func expectedType(path []ast.Node, pos token.Pos, info *types.Info) types.Type {
- if compLit, keyVal := enclosingCompLit(pos, path); compLit != nil {
- return expectedCompLitType(compLit, keyVal, pos, info)
- }
-
for i, node := range path {
if i == 2 {
break
@@ -701,7 +499,7 @@
if pos <= expr.TokPos {
break
}
- i := exprAtPos(pos, expr.Rhs)
+ i := indexExprAtPos(pos, expr.Rhs)
if i >= len(expr.Lhs) {
i = len(expr.Lhs) - 1
}
@@ -714,7 +512,7 @@
if sig.Params().Len() == 0 {
return nil
}
- i := exprAtPos(pos, expr.Args)
+ i := indexExprAtPos(pos, expr.Args)
// Make sure not to run past the end of expected parameters.
if i >= sig.Params().Len() {
i = sig.Params().Len() - 1
@@ -727,170 +525,43 @@
return nil
}
+// preferTypeNames checks if given token position is inside func receiver,
+// type params, or type results. For example:
+//
+// func (<>) foo(<>) (<>) {}
+//
+func preferTypeNames(path []ast.Node, pos token.Pos) bool {
+ for _, p := range path {
+ switch n := p.(type) {
+ case *ast.FuncDecl:
+ if r := n.Recv; r != nil && r.Pos() <= pos && pos <= r.End() {
+ return true
+ }
+ if t := n.Type; t != nil {
+ if p := t.Params; p != nil && p.Pos() <= pos && pos <= p.End() {
+ return true
+ }
+ if r := t.Results; r != nil && r.Pos() <= pos && pos <= r.End() {
+ return true
+ }
+ }
+ return false
+ }
+ }
+ return false
+}
+
// matchingTypes reports whether actual is a good candidate type
// for a completion in a context of the expected type.
-func matchingTypes(expected, actual types.Type) bool {
+func (c *completer) matchingType(actual types.Type) bool {
+ if c.expectedType == nil {
+ return false
+ }
// Use a function's return type as its type.
if sig, ok := actual.(*types.Signature); ok {
if sig.Results().Len() == 1 {
actual = sig.Results().At(0).Type()
}
}
- return types.Identical(types.Default(expected), types.Default(actual))
-}
-
-// exprAtPos returns the index of the expression containing pos.
-func exprAtPos(pos token.Pos, args []ast.Expr) int {
- for i, expr := range args {
- if expr.Pos() <= pos && pos <= expr.End() {
- return i
- }
- }
- return len(args)
-}
-
-// 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
-}
-
-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
-}
-
-// 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)
-}
-
-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{}",
- },
+ return types.Identical(types.Default(c.expectedType), types.Default(actual))
}
diff --git a/internal/lsp/source/completion_format.go b/internal/lsp/source/completion_format.go
new file mode 100644
index 0000000..dd5fab9
--- /dev/null
+++ b/internal/lsp/source/completion_format.go
@@ -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{}",
+ },
+}
diff --git a/internal/lsp/source/signature_help.go b/internal/lsp/source/signature_help.go
index bcb4091..54433f6 100644
--- a/internal/lsp/source/signature_help.go
+++ b/internal/lsp/source/signature_help.go
@@ -59,11 +59,11 @@
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 @@
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, "()")
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
new file mode 100644
index 0000000..a4d3745
--- /dev/null
+++ b/internal/lsp/source/util.go
@@ -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
+}