diff options
Diffstat (limited to 'gopls/internal/lsp/source/completion/util.go')
-rw-r--r-- | gopls/internal/lsp/source/completion/util.go | 344 |
1 files changed, 344 insertions, 0 deletions
diff --git a/gopls/internal/lsp/source/completion/util.go b/gopls/internal/lsp/source/completion/util.go new file mode 100644 index 000000000..4b6ec09a0 --- /dev/null +++ b/gopls/internal/lsp/source/completion/util.go @@ -0,0 +1,344 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package completion + +import ( + "go/ast" + "go/token" + "go/types" + + "golang.org/x/tools/go/types/typeutil" + "golang.org/x/tools/gopls/internal/lsp/protocol" + "golang.org/x/tools/gopls/internal/lsp/safetoken" + "golang.org/x/tools/gopls/internal/lsp/source" + "golang.org/x/tools/internal/diff" + "golang.org/x/tools/internal/typeparams" +) + +// 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) +} + +// eachField invokes fn for each field that can be selected from a +// value of type T. +func eachField(T types.Type, fn func(*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. + + // for termination on recursive types + var seen typeutil.Map + + var visit func(T types.Type) + visit = func(T types.Type) { + if T, ok := source.Deref(T).Underlying().(*types.Struct); ok { + if seen.At(T) != nil { + return + } + + for i := 0; i < T.NumFields(); i++ { + f := T.Field(i) + fn(f) + if f.Anonymous() { + seen.Set(T, true) + visit(f.Type()) + } + } + } + } + visit(T) +} + +// typeIsValid reports whether typ doesn't contain any Invalid types. +func typeIsValid(typ types.Type) bool { + // Check named types separately, because we don't want + // to call Underlying() on them to avoid problems with recursive types. + if _, ok := typ.(*types.Named); ok { + return true + } + + switch typ := typ.Underlying().(type) { + case *types.Basic: + return typ.Kind() != types.Invalid + case *types.Array: + return typeIsValid(typ.Elem()) + case *types.Slice: + return typeIsValid(typ.Elem()) + case *types.Pointer: + return typeIsValid(typ.Elem()) + case *types.Map: + return typeIsValid(typ.Key()) && typeIsValid(typ.Elem()) + case *types.Chan: + return typeIsValid(typ.Elem()) + case *types.Signature: + return typeIsValid(typ.Params()) && typeIsValid(typ.Results()) + case *types.Tuple: + for i := 0; i < typ.Len(); i++ { + if !typeIsValid(typ.At(i).Type()) { + return false + } + } + return true + case *types.Struct, *types.Interface: + // Don't bother checking structs, interfaces for validity. + return true + default: + return false + } +} + +// 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(fset *token.FileSet, obj types.Object, node ast.Node, info *types.Info) types.Object { + 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 + default: + return true + } + }) + // Construct a fake type for the object and return a fake object with this type. + typename := source.FormatNode(fset, resultExpr) + typ := types.NewNamed(types.NewTypeName(token.NoPos, obj.Pkg(), typename, nil), types.Typ[types.Invalid], nil) + return types.NewVar(obj.Pos(), obj.Pkg(), obj.Name(), typ) +} + +func isPointer(T types.Type) bool { + _, ok := T.(*types.Pointer) + return ok +} + +func isVar(obj types.Object) bool { + _, ok := obj.(*types.Var) + return ok +} + +func isTypeName(obj types.Object) bool { + _, ok := obj.(*types.TypeName) + return ok +} + +func isFunc(obj types.Object) bool { + _, ok := obj.(*types.Func) + return ok +} + +func isEmptyInterface(T types.Type) bool { + intf, _ := T.(*types.Interface) + return intf != nil && intf.NumMethods() == 0 && typeparams.IsMethodSet(intf) +} + +func isUntyped(T types.Type) bool { + if basic, ok := T.(*types.Basic); ok { + return basic.Info()&types.IsUntyped > 0 + } + return false +} + +func isPkgName(obj types.Object) bool { + _, ok := obj.(*types.PkgName) + return ok +} + +func isASTFile(n ast.Node) bool { + _, ok := n.(*ast.File) + return ok +} + +func deslice(T types.Type) types.Type { + if slice, ok := T.Underlying().(*types.Slice); ok { + return slice.Elem() + } + return nil +} + +// isSelector returns the enclosing *ast.SelectorExpr when pos is in the +// selector. +func enclosingSelector(path []ast.Node, pos token.Pos) *ast.SelectorExpr { + if len(path) == 0 { + return nil + } + + if sel, ok := path[0].(*ast.SelectorExpr); ok { + return sel + } + + if _, ok := path[0].(*ast.Ident); ok && len(path) > 1 { + if sel, ok := path[1].(*ast.SelectorExpr); ok && pos >= sel.Sel.Pos() { + return sel + } + } + + return nil +} + +// enclosingDeclLHS returns LHS idents from containing value spec or +// assign statement. +func enclosingDeclLHS(path []ast.Node) []*ast.Ident { + for _, n := range path { + switch n := n.(type) { + case *ast.ValueSpec: + return n.Names + case *ast.AssignStmt: + ids := make([]*ast.Ident, 0, len(n.Lhs)) + for _, e := range n.Lhs { + if id, ok := e.(*ast.Ident); ok { + ids = append(ids, id) + } + } + return ids + } + } + + return nil +} + +// exprObj returns the types.Object associated with the *ast.Ident or +// *ast.SelectorExpr e. +func exprObj(info *types.Info, e ast.Expr) types.Object { + var ident *ast.Ident + switch expr := e.(type) { + case *ast.Ident: + ident = expr + case *ast.SelectorExpr: + ident = expr.Sel + default: + return nil + } + + return info.ObjectOf(ident) +} + +// typeConversion returns the type being converted to if call is a type +// conversion expression. +func typeConversion(call *ast.CallExpr, info *types.Info) types.Type { + // Type conversion (e.g. "float64(foo)"). + if fun, _ := exprObj(info, call.Fun).(*types.TypeName); fun != nil { + return fun.Type() + } + + return nil +} + +// fieldsAccessible returns whether s has at least one field accessible by p. +func fieldsAccessible(s *types.Struct, p *types.Package) bool { + for i := 0; i < s.NumFields(); i++ { + f := s.Field(i) + if f.Exported() || f.Pkg() == p { + return true + } + } + return false +} + +// prevStmt returns the statement that precedes the statement containing pos. +// For example: +// +// foo := 1 +// bar(1 + 2<>) +// +// If "<>" is pos, prevStmt returns "foo := 1" +func prevStmt(pos token.Pos, path []ast.Node) ast.Stmt { + var blockLines []ast.Stmt + for i := 0; i < len(path) && blockLines == nil; i++ { + switch n := path[i].(type) { + case *ast.BlockStmt: + blockLines = n.List + case *ast.CommClause: + blockLines = n.Body + case *ast.CaseClause: + blockLines = n.Body + } + } + + for i := len(blockLines) - 1; i >= 0; i-- { + if blockLines[i].End() < pos { + return blockLines[i] + } + } + + return nil +} + +// formatZeroValue produces Go code representing the zero value of T. It +// returns the empty string if T is invalid. +func formatZeroValue(T types.Type, qf types.Qualifier) string { + switch u := T.Underlying().(type) { + case *types.Basic: + switch { + case u.Info()&types.IsNumeric > 0: + return "0" + case u.Info()&types.IsString > 0: + return `""` + case u.Info()&types.IsBoolean > 0: + return "false" + default: + return "" + } + case *types.Pointer, *types.Interface, *types.Chan, *types.Map, *types.Slice, *types.Signature: + return "nil" + default: + return types.TypeString(T, qf) + "{}" + } +} + +// isBasicKind returns whether t is a basic type of kind k. +func isBasicKind(t types.Type, k types.BasicInfo) bool { + b, _ := t.Underlying().(*types.Basic) + return b != nil && b.Info()&k > 0 +} + +func (c *completer) editText(from, to token.Pos, newText string) ([]protocol.TextEdit, error) { + start, end, err := safetoken.Offsets(c.tokFile, from, to) + if err != nil { + return nil, err // can't happen: from/to came from c + } + return source.ToProtocolEdits(c.mapper, []diff.Edit{{ + Start: start, + End: end, + New: newText, + }}) +} + +// assignableTo is like types.AssignableTo, but returns false if +// either type is invalid. +func assignableTo(x, to types.Type) bool { + if x == types.Typ[types.Invalid] || to == types.Typ[types.Invalid] { + return false + } + + return types.AssignableTo(x, to) +} + +// convertibleTo is like types.ConvertibleTo, but returns false if +// either type is invalid. +func convertibleTo(x, to types.Type) bool { + if x == types.Typ[types.Invalid] || to == types.Typ[types.Invalid] { + return false + } + + return types.ConvertibleTo(x, to) +} |