// 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 source import ( "bytes" "context" "fmt" "go/ast" "go/doc" "go/printer" "go/token" "go/types" "strings" "golang.org/x/tools/internal/event" "golang.org/x/tools/internal/lsp/debug/tag" "golang.org/x/tools/internal/lsp/protocol" ) // FormatType returns the detail and kind for a types.Type. func FormatType(typ types.Type, qf 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(), qf) } else { detail = types.TypeString(typ, qf) kind = protocol.ClassCompletion } return detail, kind } type signature struct { name, doc string params, results []string variadic bool needResultParens bool } func (s *signature) Format() string { var b strings.Builder b.WriteByte('(') for i, p := range s.params { if i > 0 { b.WriteString(", ") } b.WriteString(p) } b.WriteByte(')') // Add space between parameters and results. if len(s.results) > 0 { b.WriteByte(' ') } if s.needResultParens { b.WriteByte('(') } for i, r := range s.results { if i > 0 { b.WriteString(", ") } b.WriteString(r) } if s.needResultParens { b.WriteByte(')') } return b.String() } func (s *signature) Params() []string { return s.params } // NewBuiltinSignature returns signature for the builtin object with a given // name, if a builtin object with the name exists. func NewBuiltinSignature(ctx context.Context, s Snapshot, name string) (*signature, error) { builtin, err := s.BuiltinPackage(ctx) if err != nil { return nil, err } obj := builtin.Package.Scope.Lookup(name) if obj == nil { return nil, fmt.Errorf("no builtin object for %s", name) } decl, ok := obj.Decl.(*ast.FuncDecl) if !ok { return nil, fmt.Errorf("no function declaration for builtin: %s", name) } if decl.Type == nil { return nil, fmt.Errorf("no type for builtin decl %s", decl.Name) } var variadic bool if decl.Type.Params.List != nil { numParams := len(decl.Type.Params.List) lastParam := decl.Type.Params.List[numParams-1] if _, ok := lastParam.Type.(*ast.Ellipsis); ok { variadic = true } } params, _ := formatFieldList(ctx, s, decl.Type.Params, variadic) results, needResultParens := formatFieldList(ctx, s, decl.Type.Results, false) d := decl.Doc.Text() switch s.View().Options().HoverKind { case SynopsisDocumentation: d = doc.Synopsis(d) case NoDocumentation: d = "" } return &signature{ doc: d, name: name, needResultParens: needResultParens, params: params, results: results, variadic: variadic, }, nil } var replacer = strings.NewReplacer( `ComplexType`, `complex128`, `FloatType`, `float64`, `IntegerType`, `int`, ) func formatFieldList(ctx context.Context, snapshot Snapshot, list *ast.FieldList, variadic bool) ([]string, bool) { if list == nil { return nil, false } var writeResultParens bool var result []string for i := 0; i < len(list.List); i++ { if i >= 1 { writeResultParens = true } p := list.List[i] cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 4} b := &bytes.Buffer{} if err := cfg.Fprint(b, snapshot.FileSet(), p.Type); err != nil { event.Error(ctx, "unable to print type", nil, tag.Type.Of(p.Type)) continue } typ := replacer.Replace(b.String()) if len(p.Names) == 0 { result = append(result, typ) } for _, name := range p.Names { if name.Name != "" { if i == 0 { writeResultParens = true } result = append(result, fmt.Sprintf("%s %s", name.Name, typ)) } else { result = append(result, typ) } } } if variadic { result[len(result)-1] = strings.Replace(result[len(result)-1], "[]", "...", 1) } return result, writeResultParens } // NewSignature returns formatted signature for a types.Signature struct. func NewSignature(ctx context.Context, s Snapshot, pkg Package, sig *types.Signature, comment *ast.CommentGroup, qf types.Qualifier) *signature { params := make([]string, 0, sig.Params().Len()) for i := 0; i < sig.Params().Len(); i++ { el := sig.Params().At(i) typ := FormatVarType(ctx, s, pkg, el, qf) p := typ if el.Name() != "" { p = el.Name() + " " + typ } params = append(params, p) } var needResultParens bool results := make([]string, 0, sig.Results().Len()) for i := 0; i < sig.Results().Len(); i++ { if i >= 1 { needResultParens = true } el := sig.Results().At(i) typ := FormatVarType(ctx, s, pkg, el, qf) if el.Name() == "" { results = append(results, typ) } else { if i == 0 { needResultParens = true } results = append(results, el.Name()+" "+typ) } } var d string if comment != nil { d = comment.Text() } switch s.View().Options().HoverKind { case SynopsisDocumentation: d = doc.Synopsis(d) case NoDocumentation: d = "" } return &signature{ doc: d, params: params, results: results, variadic: sig.Variadic(), needResultParens: needResultParens, } } // FormatVarType formats a *types.Var, accounting for type aliases. // To do this, it looks in the AST of the file in which the object is declared. // On any errors, it always fallbacks back to types.TypeString. func FormatVarType(ctx context.Context, snapshot Snapshot, srcpkg Package, obj *types.Var, qf types.Qualifier) string { pgf, pkg, err := FindPosInPackage(snapshot, srcpkg, obj.Pos()) if err != nil { return types.TypeString(obj.Type(), qf) } expr, err := varType(ctx, snapshot, pgf, obj) if err != nil { return types.TypeString(obj.Type(), qf) } // The type names in the AST may not be correctly qualified. // Determine the package name to use based on the package that originated // the query and the package in which the type is declared. // We then qualify the value by cloning the AST node and editing it. clonedInfo := make(map[token.Pos]*types.PkgName) qualified := cloneExpr(expr, pkg.GetTypesInfo(), clonedInfo) // If the request came from a different package than the one in which the // types are defined, we may need to modify the qualifiers. qualified = qualifyExpr(qualified, srcpkg, pkg, clonedInfo, qf) fmted := FormatNode(snapshot.FileSet(), qualified) return fmted } // varType returns the type expression for a *types.Var. func varType(ctx context.Context, snapshot Snapshot, pgf *ParsedGoFile, obj *types.Var) (ast.Expr, error) { posToField, err := snapshot.PosToField(ctx, pgf) if err != nil { return nil, err } field := posToField[obj.Pos()] if field == nil { return nil, fmt.Errorf("no declaration for object %s", obj.Name()) } typ, ok := field.Type.(ast.Expr) if !ok { return nil, fmt.Errorf("unexpected type for node (%T)", field.Type) } return typ, nil } // qualifyExpr applies the "pkgName." prefix to any *ast.Ident in the expr. func qualifyExpr(expr ast.Expr, srcpkg, pkg Package, clonedInfo map[token.Pos]*types.PkgName, qf types.Qualifier) ast.Expr { ast.Inspect(expr, func(n ast.Node) bool { switch n := n.(type) { case *ast.ArrayType, *ast.ChanType, *ast.Ellipsis, *ast.FuncType, *ast.MapType, *ast.ParenExpr, *ast.StarExpr, *ast.StructType: // These are the only types that are cloned by cloneExpr below, // so these are the only types that we can traverse and potentially // modify. This is not an ideal approach, but it works for now. return true case *ast.SelectorExpr: // We may need to change any selectors in which the X is a package // name and the Sel is exported. x, ok := n.X.(*ast.Ident) if !ok { return false } obj, ok := clonedInfo[x.Pos()] if !ok { return false } pkgName := qf(obj.Imported()) if pkgName != "" { x.Name = pkgName } return false case *ast.Ident: if srcpkg == pkg { return false } // Only add the qualifier if the identifier is exported. if ast.IsExported(n.Name) { pkgName := qf(pkg.GetTypes()) n.Name = pkgName + "." + n.Name } } return false }) return expr } // cloneExpr only clones expressions that appear in the parameters or return // values of a function declaration. The original expression may be returned // to the caller in 2 cases: // (1) The expression has no pointer fields. // (2) The expression cannot appear in an *ast.FuncType, making it // unnecessary to clone. // This function also keeps track of selector expressions in which the X is a // package name and marks them in a map along with their type information, so // that this information can be used when rewriting the expression. // // NOTE: This function is tailored to the use case of qualifyExpr, and should // be used with caution. func cloneExpr(expr ast.Expr, info *types.Info, clonedInfo map[token.Pos]*types.PkgName) ast.Expr { switch expr := expr.(type) { case *ast.ArrayType: return &ast.ArrayType{ Lbrack: expr.Lbrack, Elt: cloneExpr(expr.Elt, info, clonedInfo), Len: expr.Len, } case *ast.ChanType: return &ast.ChanType{ Arrow: expr.Arrow, Begin: expr.Begin, Dir: expr.Dir, Value: cloneExpr(expr.Value, info, clonedInfo), } case *ast.Ellipsis: return &ast.Ellipsis{ Ellipsis: expr.Ellipsis, Elt: cloneExpr(expr.Elt, info, clonedInfo), } case *ast.FuncType: return &ast.FuncType{ Func: expr.Func, Params: cloneFieldList(expr.Params, info, clonedInfo), Results: cloneFieldList(expr.Results, info, clonedInfo), } case *ast.Ident: return cloneIdent(expr) case *ast.MapType: return &ast.MapType{ Map: expr.Map, Key: cloneExpr(expr.Key, info, clonedInfo), Value: cloneExpr(expr.Value, info, clonedInfo), } case *ast.ParenExpr: return &ast.ParenExpr{ Lparen: expr.Lparen, Rparen: expr.Rparen, X: cloneExpr(expr.X, info, clonedInfo), } case *ast.SelectorExpr: s := &ast.SelectorExpr{ Sel: cloneIdent(expr.Sel), X: cloneExpr(expr.X, info, clonedInfo), } if x, ok := expr.X.(*ast.Ident); ok && ast.IsExported(expr.Sel.Name) { if obj, ok := info.ObjectOf(x).(*types.PkgName); ok { clonedInfo[s.X.Pos()] = obj } } return s case *ast.StarExpr: return &ast.StarExpr{ Star: expr.Star, X: cloneExpr(expr.X, info, clonedInfo), } case *ast.StructType: return &ast.StructType{ Struct: expr.Struct, Fields: cloneFieldList(expr.Fields, info, clonedInfo), Incomplete: expr.Incomplete, } default: return expr } } func cloneFieldList(fl *ast.FieldList, info *types.Info, clonedInfo map[token.Pos]*types.PkgName) *ast.FieldList { if fl == nil { return nil } if fl.List == nil { return &ast.FieldList{ Closing: fl.Closing, Opening: fl.Opening, } } list := make([]*ast.Field, 0, len(fl.List)) for _, f := range fl.List { var names []*ast.Ident for _, n := range f.Names { names = append(names, cloneIdent(n)) } list = append(list, &ast.Field{ Comment: f.Comment, Doc: f.Doc, Names: names, Tag: f.Tag, Type: cloneExpr(f.Type, info, clonedInfo), }) } return &ast.FieldList{ Closing: fl.Closing, Opening: fl.Opening, List: list, } } func cloneIdent(ident *ast.Ident) *ast.Ident { return &ast.Ident{ NamePos: ident.NamePos, Name: ident.Name, Obj: ident.Obj, } }