1
0
mirror of https://github.com/golang/go synced 2024-11-18 08:54:45 -07:00

internal/lsp: refactor completion and move into source directory

The completion function belongs in internal/lsp/source, so move it
there. Some small refactoring of completion, by moving each type of
completion into helper functions that append to the list of results.

Change-Id: I8599092906609591d499183657fe2d21d1f74df1
Reviewed-on: https://go-review.googlesource.com/c/148397
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Rebecca Stambler 2018-11-07 20:57:08 -05:00
parent a28dfb48e0
commit c26e340d2f
3 changed files with 775 additions and 712 deletions

View File

@ -1,723 +1,49 @@
package lsp
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"go/types"
"strings"
"sort"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/internal/lsp/protocol"
"golang.org/x/tools/internal/lsp/source"
)
func completion(v *source.View, uri protocol.DocumentURI, pos protocol.Position) (items []protocol.CompletionItem, err error) {
f := v.GetFile(source.URI(uri))
if err != nil {
return nil, err
}
tok, err := f.GetToken()
if err != nil {
return nil, err
}
p := fromProtocolPosition(tok, pos)
file, err := f.GetAST() // Use p to prune the AST?
if err != nil {
return nil, err
}
pkg, err := f.GetPackage()
if err != nil {
return nil, err
}
items, _, err = completions(v.Config.Fset, file, p, pkg.Types, pkg.TypesInfo)
return items, err
}
// Completions returns the map of possible candidates for completion,
// given a position, a file AST, and type information. 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 completions(fset *token.FileSet, file *ast.File, pos token.Pos, pkg *types.Package, info *types.Info) (completions []protocol.CompletionItem, prefix string, err error) {
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
if path == nil {
return nil, "", fmt.Errorf("cannot find node enclosing position")
}
// If the position is not an identifier but immediately follows
// an identifier or selector period (as is common when
// requesting a completion), use the path to the preceding node.
if _, ok := path[0].(*ast.Ident); !ok {
if p, _ := astutil.PathEnclosingInterval(file, pos-1, pos-1); p != nil {
switch p[0].(type) {
case *ast.Ident, *ast.SelectorExpr:
path = p // use preceding ident/selector
}
}
}
expectedTyp := expectedType(path, pos, info)
enclosing := enclosingFunc(path, pos, info)
pkgStringer := qualifier(file, pkg, info)
seen := make(map[types.Object]bool)
const stdWeight = 1 // default rank for a completion result
// found adds a candidate completion.
// Only the first candidate of a given name is considered.
found := func(obj types.Object, weight float32) {
if obj.Pkg() != nil && obj.Pkg() != pkg && !obj.Exported() {
return // inaccessible
}
if !seen[obj] {
seen[obj] = true
if expectedTyp != nil && matchingTypes(expectedTyp, obj.Type()) {
weight *= 10
}
item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
return isParam(enclosing, v)
})
completions = append(completions, item)
}
}
// selector finds completions for
// the specified selector expression.
// TODO(rstambler): Set the prefix filter correctly for selectors.
selector := func(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 {
// Enumerate package members.
// TODO(adonovan): can Imported() be nil?
scope := pkgname.Imported().Scope()
// TODO testcase: bad import
for _, name := range scope.Names() {
found(scope.Lookup(name), stdWeight)
}
return nil
}
}
// Inv: sel is a true selector.
tv, ok := info.Types[sel.X]
if !ok {
var buf bytes.Buffer
format.Node(&buf, fset, sel.X) // TODO check for error
return fmt.Errorf("cannot resolve %s", &buf)
}
// methods of T
mset := types.NewMethodSet(tv.Type)
for i := 0; i < mset.Len(); i++ {
found(mset.At(i).Obj(), stdWeight)
}
// 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++ {
found(mset.At(i).Obj(), stdWeight)
}
}
// fields of T
for _, f := range fieldSelections(tv.Type) {
found(f, stdWeight)
}
return nil
}
// lexical finds completions in the lexical environment.
lexical := func(path []ast.Node) {
var scopes []*types.Scope // scopes[i], where i<len(path), is the possibly nil Scope of path[i].
for _, n := range 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, pkg.Scope(), types.Universe)
// Process scopes innermost first.
for i, scope := range scopes {
if scope == nil {
continue
}
for _, name := range scope.Names() {
declScope, obj := scope.LookupParent(name, pos)
if declScope != scope {
continue // Name was declared in some enclosing scope, or not at all.
}
// If obj's type is invalid, find the AST node that defines the lexical block
// containing the declaration of obj. Don't resolve types for packages.
if _, ok := obj.(*types.PkgName); !ok && obj.Type() == types.Typ[types.Invalid] {
// 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 node != nil {
if resolved := resolveInvalid(obj, node, info); resolved != nil {
obj = resolved
}
}
}
score := float32(stdWeight)
// Rank builtins significantly lower than other results.
if scope == types.Universe {
score *= 0.1
}
found(obj, score)
}
}
}
// complit finds completions for field names inside a composite literal.
// It reports whether the node was handled as part of a composite literal.
complit := func(node ast.Node) bool {
var lit *ast.CompositeLit
switch n := node.(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
}
}
case *ast.Ident:
// 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
}
}
}
}
}
}
}
if lit == nil {
return false
}
// Mark fields that have already been set, apart from the current field.
hasKeys := false // 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() {
continue
}
if key, ok := kv.Key.(*ast.Ident); ok {
if used, ok := info.Uses[key]; ok {
if usedVar, ok := used.(*types.Var); ok {
addedFields[usedVar] = true
}
}
}
}
}
// If the underlying type of the composite literal is a struct,
// we show 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 i == 0 {
structPkg = field.Pkg()
}
if !addedFields[field] {
found(field, stdWeight*10)
}
}
// 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 {
lexical(path)
}
return true
}
}
return false
}
if complit(path[0]) {
return completions, prefix, nil
}
switch n := path[0].(type) {
case *ast.Ident:
// Set the filter prefix.
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 {
if err := selector(sel); err != nil {
return nil, prefix, err
}
} else {
// reject defining identifiers
if obj, ok := info.Defs[n]; ok {
if v, ok := obj.(*types.Var); ok && v.IsField() {
// An anonymous field is also a reference to a type.
} else {
of := ""
if obj != nil {
qual := types.RelativeTo(pkg)
of += ", of " + types.ObjectString(obj, qual)
}
return nil, "", fmt.Errorf("this is a definition%s", of)
}
}
lexical(path)
}
// Support completions when no letters of the function name have been
// typed yet, but the parens are there:
// recv.‸(arg)
case *ast.TypeAssertExpr:
// Create a fake selector expression.
if err := selector(&ast.SelectorExpr{X: n.X}); err != nil {
return nil, prefix, err
}
case *ast.SelectorExpr:
if err := selector(n); err != nil {
return nil, prefix, err
}
default:
// TODO(adonovan): a lexical query may not be what the
// user expects when completing after the period of a
// type assertion.
lexical(path)
}
return completions, prefix, nil
}
// qualifier returns a function that appropriately formats a types.PkgName appearing in q.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(pkg *types.Package) string {
if pkg == pkg {
return ""
}
if name, ok := imports[pkg]; ok {
return name
}
return pkg.Name()
}
}
// enclosingFunc returns the signature of the function enclosing the position.
func enclosingFunc(path []ast.Node, pos token.Pos, info *types.Info) *types.Signature {
for _, node := range path {
switch t := node.(type) {
case *ast.FuncDecl:
if obj, ok := info.Defs[t.Name]; ok {
return obj.Type().(*types.Signature)
}
case *ast.FuncLit:
if typ, ok := info.Types[t]; ok {
return typ.Type.(*types.Signature)
}
}
}
return nil
}
// formatCompletion returns the label, details, and kind for a types.Object,
// fitting the format of a LSP completion item.
func formatCompletion(obj types.Object, qualifier types.Qualifier, score float32, isParam func(*types.Var) bool) protocol.CompletionItem {
label := obj.Name()
detail := types.TypeString(obj.Type(), qualifier)
var kind protocol.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 = protocol.ConstantCompletion
case *types.Var:
if _, ok := o.Type().(*types.Struct); ok {
detail = "struct{...}" // for anonymous structs
}
if o.IsField() {
kind = protocol.FieldCompletion
} else if isParam(o) {
kind = protocol.TypeParameterCompletion
} else {
kind = protocol.VariableCompletion
}
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 = protocol.FunctionCompletion
if sig.Recv() != nil {
kind = protocol.MethodCompletion
}
}
case *types.Builtin:
item, ok := builtinDetails[obj.Name()]
if !ok {
break
}
label, detail = item.label, item.detail
kind = protocol.FunctionCompletion
case *types.PkgName:
kind = protocol.ModuleCompletion // package??
detail = fmt.Sprintf("\"%s\"", o.Imported().Path())
case *types.Nil:
kind = protocol.VariableCompletion
detail = ""
}
detail = strings.TrimPrefix(detail, "untyped ")
return protocol.CompletionItem{
Label: label,
Detail: detail,
Kind: float64(kind),
}
}
// formatType returns the detail and kind for an object of type *types.TypeName.
func formatType(typ types.Type, qualifier types.Qualifier) (detail string, kind protocol.CompletionItemKind) {
if types.IsInterface(typ) {
detail = "interface{...}"
kind = protocol.InterfaceCompletion
} else if _, ok := typ.(*types.Struct); ok {
detail = "struct{...}"
kind = protocol.StructCompletion
} else if typ != typ.Underlying() {
detail, kind = formatType(typ.Underlying(), qualifier)
} else {
detail = types.TypeString(typ, qualifier)
kind = protocol.TypeParameterCompletion // ???
}
return detail, kind
}
func formatParams(t *types.Tuple, variadic bool, qualifier types.Qualifier) string {
var b strings.Builder
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)
}
fmt.Fprintf(&b, "%v %v", el.Name(), typ)
}
b.WriteByte(')')
return b.String()
}
func isParam(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
}
// 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 {
for i, node := range path {
if i == 2 {
break
}
switch expr := node.(type) {
case *ast.BinaryExpr:
// Determine if query position comes from left or right of op.
e := expr.X
if pos < expr.OpPos {
e = expr.Y
}
if tv, ok := info.Types[e]; ok {
return tv.Type
}
case *ast.AssignStmt:
// Only rank completions if you are on the right side of the token.
if pos <= expr.TokPos {
break
}
i := exprAtPos(pos, expr.Rhs)
if i >= len(expr.Lhs) {
i = len(expr.Lhs) - 1
}
if tv, ok := info.Types[expr.Lhs[i]]; ok {
return tv.Type
}
case *ast.CallExpr:
if tv, ok := info.Types[expr.Fun]; ok {
if sig, ok := tv.Type.(*types.Signature); ok {
if sig.Params().Len() == 0 {
return nil
}
i := exprAtPos(pos, expr.Args)
// Make sure not to run past the end of expected parameters.
if i >= sig.Params().Len() {
i = sig.Params().Len() - 1
}
return sig.Params().At(i).Type()
}
}
}
}
return nil
}
// 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 {
// 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
}
func toProtocolCompletionItems(items []source.CompletionItem) []protocol.CompletionItem {
var results []protocol.CompletionItem
sort.Slice(items, func(i, j int) bool {
return items[i].Score > items[j].Score
})
return formatResult(resultExpr)
for _, item := range items {
results = append(results, protocol.CompletionItem{
Label: item.Label,
Detail: item.Detail,
Kind: float64(toProtocolCompletionItemKind(item.Kind)),
})
}
return results
}
type itemDetails struct {
label, detail string
}
func toProtocolCompletionItemKind(kind source.CompletionItemKind) protocol.CompletionItemKind {
switch kind {
case source.InterfaceCompletionItem:
return protocol.InterfaceCompletion
case source.StructCompletionItem:
return protocol.StructCompletion
case source.TypeCompletionItem:
return protocol.TypeParameterCompletion // ??
case source.ConstantCompletionItem:
return protocol.ConstantCompletion
case source.FieldCompletionItem:
return protocol.FieldCompletion
case source.ParameterCompletionItem, source.VariableCompletionItem:
return protocol.VariableCompletion
case source.FunctionCompletionItem:
return protocol.FunctionCompletion
case source.MethodCompletionItem:
return protocol.MethodCompletion
case source.PackageCompletionItem:
return protocol.ModuleCompletion // ??
default:
return protocol.TextCompletion
}
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, imag float64)",
detail: "complex128",
},
"copy": { // copy(dst, src []T) int
label: "copy(dst, 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{}",
},
}

View File

@ -149,13 +149,19 @@ func (s *server) DidClose(ctx context.Context, params *protocol.DidCloseTextDocu
}
func (s *server) Completion(ctx context.Context, params *protocol.CompletionParams) (*protocol.CompletionList, error) {
items, err := completion(s.view, params.TextDocument.URI, params.Position)
f := s.view.GetFile(source.URI(params.TextDocument.URI))
tok, err := f.GetToken()
if err != nil {
return nil, err
}
pos := fromProtocolPosition(tok, params.Position)
items, err := source.Completion(ctx, f, pos)
if err != nil {
return nil, err
}
return &protocol.CompletionList{
IsIncomplete: false,
Items: items,
Items: toProtocolCompletionItems(items),
}, nil
}

View File

@ -0,0 +1,731 @@
package source
import (
"context"
"fmt"
"go/ast"
"go/token"
"go/types"
"strings"
"golang.org/x/tools/go/ast/astutil"
)
type CompletionItem struct {
Label, Detail string
Kind CompletionItemKind
Score int
}
type CompletionItemKind int
const (
Unknown CompletionItemKind = iota
InterfaceCompletionItem
StructCompletionItem
TypeCompletionItem
ConstantCompletionItem
FieldCompletionItem
ParameterCompletionItem
VariableCompletionItem
FunctionCompletionItem
MethodCompletionItem
PackageCompletionItem
)
func Completion(ctx context.Context, f *File, pos token.Pos) ([]CompletionItem, error) {
file, err := f.GetAST()
if err != nil {
return nil, err
}
pkg, err := f.GetPackage()
if err != nil {
return nil, err
}
items, _, err := completions(file, pos, pkg.Fset, pkg.Types, pkg.TypesInfo)
return items, err
}
type finder func(types.Object, float64, []CompletionItem) []CompletionItem
// completions returns the map of possible candidates for completion, given a
// position, a file AST, and type information. 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 completions(file *ast.File, pos token.Pos, fset *token.FileSet, pkg *types.Package, info *types.Info) (items []CompletionItem, prefix string, err error) {
path, _ := astutil.PathEnclosingInterval(file, pos, pos)
if path == nil {
return nil, "", fmt.Errorf("cannot find node enclosing position")
}
// If the position is not an identifier but immediately follows
// an identifier or selector period (as is common when
// requesting a completion), use the path to the preceding node.
if _, ok := path[0].(*ast.Ident); !ok {
if p, _ := astutil.PathEnclosingInterval(file, pos-1, pos-1); p != nil {
switch p[0].(type) {
case *ast.Ident, *ast.SelectorExpr:
path = p // use preceding ident/selector
}
}
}
// 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, info)
sig := enclosingFunction(path, pos, info)
pkgStringer := qualifier(file, pkg, info)
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 && !obj.Exported() {
return items // inaccessible
}
if !seen[obj] {
seen[obj] = true
if typ != nil && matchingTypes(typ, obj.Type()) {
weight *= 10
}
item := formatCompletion(obj, pkgStringer, weight, func(v *types.Var) bool {
return isParameter(sig, v)
})
items = append(items, item)
}
return items
}
// The position is within a composite literal.
if items, ok := complit(path, pos, pkg, info, found); ok {
return items, "", nil
}
switch n := path[0].(type) {
case *ast.Ident:
// Set the filter prefix.
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 {
return selector(sel, info, found)
}
// reject defining identifiers
if obj, ok := info.Defs[n]; ok {
if v, ok := obj.(*types.Var); ok && v.IsField() {
// An anonymous field is also a reference to a type.
} else {
of := ""
if obj != nil {
qual := types.RelativeTo(pkg)
of += ", of " + types.ObjectString(obj, qual)
}
return nil, "", fmt.Errorf("this is a definition%s", of)
}
}
items = append(items, lexical(path, pos, pkg, info, found)...)
// The function name hasn't been typed yet, but the parens are there:
// recv.‸(arg)
case *ast.TypeAssertExpr:
// Create a fake selector expression.
return selector(&ast.SelectorExpr{X: n.X}, info, found)
case *ast.SelectorExpr:
return selector(n, info, found)
default:
// fallback to lexical completions
return lexical(path, pos, pkg, info, found), "", nil
}
return items, prefix, nil
}
// selector finds completions for
// the specified selector expression.
// TODO(rstambler): Set the prefix filter correctly for selectors.
func selector(sel *ast.SelectorExpr, info *types.Info, found finder) (items []CompletionItem, prefix string, err error) {
// Is sel a qualified identifier?
if id, ok := sel.X.(*ast.Ident); ok {
if pkgname, ok := 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), 1, items)
}
return items, prefix, nil
}
}
// Inv: sel is a true selector.
tv, ok := info.Types[sel.X]
if !ok {
return nil, "", fmt.Errorf("cannot resolve %s", sel.X)
}
// methods of T
mset := types.NewMethodSet(tv.Type)
for i := 0; i < mset.Len(); i++ {
items = found(mset.At(i).Obj(), 1, items)
}
// 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(), 1, items)
}
}
// fields of T
for _, f := range fieldSelections(tv.Type) {
items = found(f, 1, items)
}
return items, prefix, 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) {
var scopes []*types.Scope // scopes[i], where i<len(path), is the possibly nil Scope of path[i].
for _, n := range 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, pkg.Scope(), types.Universe)
// Process scopes innermost first.
for i, scope := range scopes {
if scope == nil {
continue
}
for _, name := range scope.Names() {
declScope, obj := scope.LookupParent(name, pos)
if declScope != scope {
continue // Name was declared in some enclosing scope, or not at all.
}
// If obj's type is invalid, find the AST node that defines the lexical block
// containing the declaration of obj. Don't resolve types for packages.
if _, ok := obj.(*types.PkgName); !ok && obj.Type() == types.Typ[types.Invalid] {
// 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 node != nil {
if resolved := resolveInvalid(obj, node, info); resolved != nil {
obj = resolved
}
}
}
score := 1.0
// Rank builtins significantly lower than other results.
if scope == types.Universe {
score *= 0.1
}
items = found(obj, score, items)
}
}
return items
}
// 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, 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
}
}
case *ast.Ident:
// 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, false
}
// 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
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() {
continue
}
if key, ok := kv.Key.(*ast.Ident); ok {
if used, ok := info.Uses[key]; ok {
if usedVar, ok := used.(*types.Var); ok {
addedFields[usedVar] = true
}
}
}
}
}
// 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 i == 0 {
structPkg = field.Pkg()
}
if !addedFields[field] {
items = found(field, 10, items)
}
}
// 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)...)
}
return items, true
}
}
return items, false
}
// 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,
}
}
// 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 strings.Builder
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)
}
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(pkg *types.Package) string {
if pkg == pkg {
return ""
}
if name, ok := imports[pkg]; ok {
return name
}
return pkg.Name()
}
}
// 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) {
case *ast.FuncDecl:
if obj, ok := info.Defs[t.Name]; ok {
return obj.Type().(*types.Signature)
}
case *ast.FuncLit:
if typ, ok := info.Types[t]; ok {
return typ.Type.(*types.Signature)
}
}
}
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 {
for i, node := range path {
if i == 2 {
break
}
switch expr := node.(type) {
case *ast.BinaryExpr:
// Determine if query position comes from left or right of op.
e := expr.X
if pos < expr.OpPos {
e = expr.Y
}
if tv, ok := info.Types[e]; ok {
return tv.Type
}
case *ast.AssignStmt:
// Only rank completions if you are on the right side of the token.
if pos <= expr.TokPos {
break
}
i := exprAtPos(pos, expr.Rhs)
if i >= len(expr.Lhs) {
i = len(expr.Lhs) - 1
}
if tv, ok := info.Types[expr.Lhs[i]]; ok {
return tv.Type
}
case *ast.CallExpr:
if tv, ok := info.Types[expr.Fun]; ok {
if sig, ok := tv.Type.(*types.Signature); ok {
if sig.Params().Len() == 0 {
return nil
}
i := exprAtPos(pos, expr.Args)
// Make sure not to run past the end of expected parameters.
if i >= sig.Params().Len() {
i = sig.Params().Len() - 1
}
return sig.Params().At(i).Type()
}
}
}
}
return nil
}
// 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 {
// 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, imag float64)",
detail: "complex128",
},
"copy": { // copy(dst, src []T) int
label: "copy(dst, 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{}",
},
}