| // 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, |
| } |
| } |