1
0
mirror of https://github.com/golang/go synced 2024-11-19 02:44:44 -07:00
go/internal/lsp/source/completion.go
Muir Manders 3e93b52866 internal/lsp: suppress more completions in comments and literals
Completion suppression in comments wasn't working for comments in
switch case statements, select case statements, and decl statements.
Rather than adding those to the list of leaf ast.Node types to look
for, we now always check if the position is in a comment. This fix
broke some completion tests that were using re"$" since "$" matches
after the comment "//" characters.

We now also don't complete within any literal values. Previously we
only excluded string literals.

Change-Id: If02f39f79fe2cd7417e39dbac2c6f84a484391ec
GitHub-Last-Rev: 7ab3f526b6752a8f74413dcd268382d359e1beba
GitHub-Pull-Request: golang/tools#88
Reviewed-on: https://go-review.googlesource.com/c/tools/+/173518
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
2019-04-24 20:52:08 +00:00

889 lines
25 KiB
Go

package source
import (
"bytes"
"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 float64
}
type CompletionItemKind int
const (
Unknown CompletionItemKind = iota
InterfaceCompletionItem
StructCompletionItem
TypeCompletionItem
ConstantCompletionItem
FieldCompletionItem
ParameterCompletionItem
VariableCompletionItem
FunctionCompletionItem
MethodCompletionItem
PackageCompletionItem
)
// stdScore is the base score value set for all completion items.
const 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
// 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) {
file := f.GetAST(ctx)
pkg := f.GetPackage(ctx)
if pkg.IsIllTyped() {
return nil, "", fmt.Errorf("package for %s is ill typed", f.URI())
}
// Completion is based on what precedes the cursor.
// To understand what we are completing, 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
}
// Skip completion inside any kind of literal.
if _, ok := path[0].(*ast.BasicLit); ok {
return items, prefix, 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)
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
}
if !seen[obj] {
seen[obj] = true
if typ != nil && matchingTypes(typ, obj.Type()) {
weight *= 10.0
}
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 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()]
// 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
}
// reject defining identifiers
if obj, ok := pkg.GetTypesInfo().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.GetTypes())
of += ", of " + types.ObjectString(obj, qual)
}
return nil, "", fmt.Errorf("this is a definition%s", of)
}
}
items = append(items, lexical(path, pos, pkg.GetTypes(), pkg.GetTypesInfo(), found)...)
// 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
case *ast.SelectorExpr:
items, err = selector(n, pos, pkg.GetTypesInfo(), found)
return items, prefix, err
default:
// fallback to lexical completions
return lexical(path, pos, pkg.GetTypes(), pkg.GetTypesInfo(), 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, pos token.Pos, info *types.Info, found finder) (items []CompletionItem, 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), stdScore, items)
}
return items, 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(), stdScore, 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(), stdScore, items)
}
}
// fields of T
for _, f := range fieldSelections(tv.Type) {
items = found(f, stdScore, items)
}
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
}
// 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 := stdScore
// Rank builtins significantly lower than other results.
if scope == types.Universe {
score *= 0.1
}
items = found(obj, score, items)
}
}
return items
}
// 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
}
}
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
}
// 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.0, 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, prefix, true
}
}
return items, prefix, false
}
// 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 {
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)
}
}
return n, keyVal
}
return nil, nil
case *ast.KeyValueExpr:
keyVal = n
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
// to the type of the composite literal, so we return nil to avoid
// 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
}
// 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.
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
}
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]
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 {
return t.Key()
}
return t.Elem()
case *types.Struct:
// pos is 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 {
return nil
}
if keyIdent, ok := kv.Key.(*ast.Ident); ok {
// Find the type of the struct field whose name matches the key.
for i := 0; i < t.NumFields(); i++ {
if field := t.Field(i); field.Name() == keyIdent.Name {
return field.Type()
}
}
}
return nil
}
hasKeys := false // true if the composite literal has any key/value pairs
for _, el := range cl.Elts {
if _, ok := el.(*ast.KeyValueExpr); ok {
hasKeys = true
break
}
}
// 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() {
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
}
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 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{}",
},
}