From c26e340d2f6d4e95b683e6fe994653436ca30337 Mon Sep 17 00:00:00 2001 From: Rebecca Stambler Date: Wed, 7 Nov 2018 20:57:08 -0500 Subject: [PATCH] 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 Run-TryBot: Rebecca Stambler TryBot-Result: Gobot Gobot --- internal/lsp/completion.go | 746 ++---------------------------- internal/lsp/server.go | 10 +- internal/lsp/source/completion.go | 731 +++++++++++++++++++++++++++++ 3 files changed, 775 insertions(+), 712 deletions(-) create mode 100644 internal/lsp/source/completion.go diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go index c068ecd1..878793b4 100644 --- a/internal/lsp/completion.go +++ b/internal/lsp/completion.go @@ -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) 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{}", - }, } diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 9ee806ae..0818195b 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -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 } diff --git a/internal/lsp/source/completion.go b/internal/lsp/source/completion.go new file mode 100644 index 00000000..5d1e29ae --- /dev/null +++ b/internal/lsp/source/completion.go @@ -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) 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{}", + }, +}