mirror of
https://github.com/golang/go
synced 2024-11-19 01:54:39 -07:00
f59e586bb3
This CL ensures that a "." inside a string literal will return an empty completion list. Fixes golang/go#30477 Change-Id: I1442d0acab4c12a829047805f745c4729d69c208 Reviewed-on: https://go-review.googlesource.com/c/tools/+/167857 Reviewed-by: Rebecca Stambler <rstambler@golang.org> Run-TryBot: Rebecca Stambler <rstambler@golang.org>
791 lines
22 KiB
Go
791 lines
22 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())
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip completion inside comment blocks or string literals.
|
|
switch lit := path[0].(type) {
|
|
case *ast.File, *ast.BlockStmt:
|
|
if inComment(pos, file.Comments) {
|
|
return items, prefix, nil
|
|
}
|
|
case *ast.BasicLit:
|
|
if lit.Kind == token.STRING {
|
|
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 {
|
|
for _, c := range g.List {
|
|
if c.Pos() <= pos && pos <= c.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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
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
|
|
}
|
|
|
|
// 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 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{}",
|
|
},
|
|
}
|