mirror of
https://github.com/golang/go
synced 2024-11-19 00:34:40 -07:00
9c8bd463e3
Add typepos field to json output of describe command. This field shows where type of current (under cursor) identifier is defined. This will help code editors implement command 'Go to type definition'. Implements [#27308](https://github.com/golang/go/issues/27308) Change-Id: I4e02ddbdc03fecec98135b8996f9562a88a9cfb8 GitHub-Last-Rev: be47e397a293a96d3d39776d6090d861e7904a24 GitHub-Pull-Request: golang/tools#50 Reviewed-on: https://go-review.googlesource.com/c/140379 Reviewed-by: Alan Donovan <adonovan@google.com>
948 lines
24 KiB
Go
948 lines
24 KiB
Go
// Copyright 2013 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 main
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/constant"
|
|
"go/token"
|
|
"go/types"
|
|
"os"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"golang.org/x/tools/cmd/guru/serial"
|
|
"golang.org/x/tools/go/ast/astutil"
|
|
"golang.org/x/tools/go/loader"
|
|
"golang.org/x/tools/go/types/typeutil"
|
|
)
|
|
|
|
// describe describes the syntax node denoted by the query position,
|
|
// including:
|
|
// - its syntactic category
|
|
// - the definition of its referent (for identifiers) [now redundant]
|
|
// - its type, fields, and methods (for an expression or type expression)
|
|
//
|
|
func describe(q *Query) error {
|
|
lconf := loader.Config{Build: q.Build}
|
|
allowErrors(&lconf)
|
|
|
|
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Load/parse/type-check the program.
|
|
lprog, err := lconf.Load()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if false { // debugging
|
|
fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
|
|
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
|
|
}
|
|
|
|
var qr QueryResult
|
|
path, action := findInterestingNode(qpos.info, qpos.path)
|
|
switch action {
|
|
case actionExpr:
|
|
qr, err = describeValue(qpos, path)
|
|
|
|
case actionType:
|
|
qr, err = describeType(qpos, path)
|
|
|
|
case actionPackage:
|
|
qr, err = describePackage(qpos, path)
|
|
|
|
case actionStmt:
|
|
qr, err = describeStmt(qpos, path)
|
|
|
|
case actionUnknown:
|
|
qr = &describeUnknownResult{path[0]}
|
|
|
|
default:
|
|
panic(action) // unreachable
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
q.Output(lprog.Fset, qr)
|
|
return nil
|
|
}
|
|
|
|
type describeUnknownResult struct {
|
|
node ast.Node
|
|
}
|
|
|
|
func (r *describeUnknownResult) PrintPlain(printf printfFunc) {
|
|
// Nothing much to say about misc syntax.
|
|
printf(r.node, "%s", astutil.NodeDescription(r.node))
|
|
}
|
|
|
|
func (r *describeUnknownResult) JSON(fset *token.FileSet) []byte {
|
|
return toJSON(&serial.Describe{
|
|
Desc: astutil.NodeDescription(r.node),
|
|
Pos: fset.Position(r.node.Pos()).String(),
|
|
})
|
|
}
|
|
|
|
type action int
|
|
|
|
const (
|
|
actionUnknown action = iota // None of the below
|
|
actionExpr // FuncDecl, true Expr or Ident(types.{Const,Var})
|
|
actionType // type Expr or Ident(types.TypeName).
|
|
actionStmt // Stmt or Ident(types.Label)
|
|
actionPackage // Ident(types.Package) or ImportSpec
|
|
)
|
|
|
|
// findInterestingNode classifies the syntax node denoted by path as one of:
|
|
// - an expression, part of an expression or a reference to a constant
|
|
// or variable;
|
|
// - a type, part of a type, or a reference to a named type;
|
|
// - a statement, part of a statement, or a label referring to a statement;
|
|
// - part of a package declaration or import spec.
|
|
// - none of the above.
|
|
// and returns the most "interesting" associated node, which may be
|
|
// the same node, an ancestor or a descendent.
|
|
//
|
|
func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.Node, action) {
|
|
// TODO(adonovan): integrate with go/types/stdlib_test.go and
|
|
// apply this to every AST node we can find to make sure it
|
|
// doesn't crash.
|
|
|
|
// TODO(adonovan): audit for ParenExpr safety, esp. since we
|
|
// traverse up and down.
|
|
|
|
// TODO(adonovan): if the users selects the "." in
|
|
// "fmt.Fprintf()", they'll get an ambiguous selection error;
|
|
// we won't even reach here. Can we do better?
|
|
|
|
// TODO(adonovan): describing a field within 'type T struct {...}'
|
|
// describes the (anonymous) struct type and concludes "no methods".
|
|
// We should ascend to the enclosing type decl, if any.
|
|
|
|
for len(path) > 0 {
|
|
switch n := path[0].(type) {
|
|
case *ast.GenDecl:
|
|
if len(n.Specs) == 1 {
|
|
// Descend to sole {Import,Type,Value}Spec child.
|
|
path = append([]ast.Node{n.Specs[0]}, path...)
|
|
continue
|
|
}
|
|
return path, actionUnknown // uninteresting
|
|
|
|
case *ast.FuncDecl:
|
|
// Descend to function name.
|
|
path = append([]ast.Node{n.Name}, path...)
|
|
continue
|
|
|
|
case *ast.ImportSpec:
|
|
return path, actionPackage
|
|
|
|
case *ast.ValueSpec:
|
|
if len(n.Names) == 1 {
|
|
// Descend to sole Ident child.
|
|
path = append([]ast.Node{n.Names[0]}, path...)
|
|
continue
|
|
}
|
|
return path, actionUnknown // uninteresting
|
|
|
|
case *ast.TypeSpec:
|
|
// Descend to type name.
|
|
path = append([]ast.Node{n.Name}, path...)
|
|
continue
|
|
|
|
case ast.Stmt:
|
|
return path, actionStmt
|
|
|
|
case *ast.ArrayType,
|
|
*ast.StructType,
|
|
*ast.FuncType,
|
|
*ast.InterfaceType,
|
|
*ast.MapType,
|
|
*ast.ChanType:
|
|
return path, actionType
|
|
|
|
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
|
|
return path, actionUnknown // uninteresting
|
|
|
|
case *ast.Ellipsis:
|
|
// Continue to enclosing node.
|
|
// e.g. [...]T in ArrayType
|
|
// f(x...) in CallExpr
|
|
// f(x...T) in FuncType
|
|
|
|
case *ast.Field:
|
|
// TODO(adonovan): this needs more thought,
|
|
// since fields can be so many things.
|
|
if len(n.Names) == 1 {
|
|
// Descend to sole Ident child.
|
|
path = append([]ast.Node{n.Names[0]}, path...)
|
|
continue
|
|
}
|
|
// Zero names (e.g. anon field in struct)
|
|
// or multiple field or param names:
|
|
// continue to enclosing field list.
|
|
|
|
case *ast.FieldList:
|
|
// Continue to enclosing node:
|
|
// {Struct,Func,Interface}Type or FuncDecl.
|
|
|
|
case *ast.BasicLit:
|
|
if _, ok := path[1].(*ast.ImportSpec); ok {
|
|
return path[1:], actionPackage
|
|
}
|
|
return path, actionExpr
|
|
|
|
case *ast.SelectorExpr:
|
|
// TODO(adonovan): use Selections info directly.
|
|
if pkginfo.Uses[n.Sel] == nil {
|
|
// TODO(adonovan): is this reachable?
|
|
return path, actionUnknown
|
|
}
|
|
// Descend to .Sel child.
|
|
path = append([]ast.Node{n.Sel}, path...)
|
|
continue
|
|
|
|
case *ast.Ident:
|
|
switch pkginfo.ObjectOf(n).(type) {
|
|
case *types.PkgName:
|
|
return path, actionPackage
|
|
|
|
case *types.Const:
|
|
return path, actionExpr
|
|
|
|
case *types.Label:
|
|
return path, actionStmt
|
|
|
|
case *types.TypeName:
|
|
return path, actionType
|
|
|
|
case *types.Var:
|
|
// For x in 'struct {x T}', return struct type, for now.
|
|
if _, ok := path[1].(*ast.Field); ok {
|
|
_ = path[2].(*ast.FieldList) // assertion
|
|
if _, ok := path[3].(*ast.StructType); ok {
|
|
return path[3:], actionType
|
|
}
|
|
}
|
|
return path, actionExpr
|
|
|
|
case *types.Func:
|
|
return path, actionExpr
|
|
|
|
case *types.Builtin:
|
|
// For reference to built-in function, return enclosing call.
|
|
path = path[1:] // ascend to enclosing function call
|
|
continue
|
|
|
|
case *types.Nil:
|
|
return path, actionExpr
|
|
}
|
|
|
|
// No object.
|
|
switch path[1].(type) {
|
|
case *ast.SelectorExpr:
|
|
// Return enclosing selector expression.
|
|
return path[1:], actionExpr
|
|
|
|
case *ast.Field:
|
|
// TODO(adonovan): test this.
|
|
// e.g. all f in:
|
|
// struct { f, g int }
|
|
// interface { f() }
|
|
// func (f T) method(f, g int) (f, g bool)
|
|
//
|
|
// switch path[3].(type) {
|
|
// case *ast.FuncDecl:
|
|
// case *ast.StructType:
|
|
// case *ast.InterfaceType:
|
|
// }
|
|
//
|
|
// return path[1:], actionExpr
|
|
//
|
|
// Unclear what to do with these.
|
|
// Struct.Fields -- field
|
|
// Interface.Methods -- field
|
|
// FuncType.{Params.Results} -- actionExpr
|
|
// FuncDecl.Recv -- actionExpr
|
|
|
|
case *ast.File:
|
|
// 'package foo'
|
|
return path, actionPackage
|
|
|
|
case *ast.ImportSpec:
|
|
return path[1:], actionPackage
|
|
|
|
default:
|
|
// e.g. blank identifier
|
|
// or y in "switch y := x.(type)"
|
|
// or code in a _test.go file that's not part of the package.
|
|
return path, actionUnknown
|
|
}
|
|
|
|
case *ast.StarExpr:
|
|
if pkginfo.Types[n].IsType() {
|
|
return path, actionType
|
|
}
|
|
return path, actionExpr
|
|
|
|
case ast.Expr:
|
|
// All Expr but {BasicLit,Ident,StarExpr} are
|
|
// "true" expressions that evaluate to a value.
|
|
return path, actionExpr
|
|
}
|
|
|
|
// Ascend to parent.
|
|
path = path[1:]
|
|
}
|
|
|
|
return nil, actionUnknown // unreachable
|
|
}
|
|
|
|
func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
|
|
var expr ast.Expr
|
|
var obj types.Object
|
|
switch n := path[0].(type) {
|
|
case *ast.ValueSpec:
|
|
// ambiguous ValueSpec containing multiple names
|
|
return nil, fmt.Errorf("multiple value specification")
|
|
case *ast.Ident:
|
|
obj = qpos.info.ObjectOf(n)
|
|
expr = n
|
|
case ast.Expr:
|
|
expr = n
|
|
default:
|
|
// TODO(adonovan): is this reachable?
|
|
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
|
}
|
|
|
|
typ := qpos.info.TypeOf(expr)
|
|
if typ == nil {
|
|
typ = types.Typ[types.Invalid]
|
|
}
|
|
constVal := qpos.info.Types[expr].Value
|
|
if c, ok := obj.(*types.Const); ok {
|
|
constVal = c.Val()
|
|
}
|
|
|
|
return &describeValueResult{
|
|
qpos: qpos,
|
|
expr: expr,
|
|
typ: typ,
|
|
names: appendNames(nil, typ),
|
|
constVal: constVal,
|
|
obj: obj,
|
|
methods: accessibleMethods(typ, qpos.info.Pkg),
|
|
fields: accessibleFields(typ, qpos.info.Pkg),
|
|
}, nil
|
|
}
|
|
|
|
// appendNames returns named types found within the Type by
|
|
// removing map, pointer, channel, slice, and array constructors.
|
|
// It does not descend into structs or interfaces.
|
|
func appendNames(names []*types.Named, typ types.Type) []*types.Named {
|
|
// elemType specifies type that has some element in it
|
|
// such as array, slice, chan, pointer
|
|
type elemType interface {
|
|
Elem() types.Type
|
|
}
|
|
|
|
switch t := typ.(type) {
|
|
case *types.Named:
|
|
names = append(names, t)
|
|
case *types.Map:
|
|
names = appendNames(names, t.Key())
|
|
names = appendNames(names, t.Elem())
|
|
case elemType:
|
|
names = appendNames(names, t.Elem())
|
|
}
|
|
|
|
return names
|
|
}
|
|
|
|
type describeValueResult struct {
|
|
qpos *queryPos
|
|
expr ast.Expr // query node
|
|
typ types.Type // type of expression
|
|
names []*types.Named // named types within typ
|
|
constVal constant.Value // value of expression, if constant
|
|
obj types.Object // var/func/const object, if expr was Ident
|
|
methods []*types.Selection
|
|
fields []describeField
|
|
}
|
|
|
|
func (r *describeValueResult) PrintPlain(printf printfFunc) {
|
|
var prefix, suffix string
|
|
if r.constVal != nil {
|
|
suffix = fmt.Sprintf(" of value %s", r.constVal)
|
|
}
|
|
switch obj := r.obj.(type) {
|
|
case *types.Func:
|
|
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
|
|
if _, ok := recv.Type().Underlying().(*types.Interface); ok {
|
|
prefix = "interface method "
|
|
} else {
|
|
prefix = "method "
|
|
}
|
|
}
|
|
}
|
|
|
|
// Describe the expression.
|
|
if r.obj != nil {
|
|
if r.obj.Pos() == r.expr.Pos() {
|
|
// defining ident
|
|
printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
|
} else {
|
|
// referring ident
|
|
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
|
if def := r.obj.Pos(); def != token.NoPos {
|
|
printf(def, "defined here")
|
|
}
|
|
}
|
|
} else {
|
|
desc := astutil.NodeDescription(r.expr)
|
|
if suffix != "" {
|
|
// constant expression
|
|
printf(r.expr, "%s%s", desc, suffix)
|
|
} else {
|
|
// non-constant expression
|
|
printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
|
|
}
|
|
}
|
|
|
|
printMethods(printf, r.expr, r.methods)
|
|
printFields(printf, r.expr, r.fields)
|
|
printNamedTypes(printf, r.expr, r.names)
|
|
}
|
|
|
|
func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
|
|
var value, objpos string
|
|
if r.constVal != nil {
|
|
value = r.constVal.String()
|
|
}
|
|
if r.obj != nil {
|
|
objpos = fset.Position(r.obj.Pos()).String()
|
|
}
|
|
|
|
typesPos := make([]serial.Definition, len(r.names))
|
|
for i, t := range r.names {
|
|
typesPos[i] = serial.Definition{
|
|
ObjPos: fset.Position(t.Obj().Pos()).String(),
|
|
Desc: r.qpos.typeString(t),
|
|
}
|
|
}
|
|
|
|
return toJSON(&serial.Describe{
|
|
Desc: astutil.NodeDescription(r.expr),
|
|
Pos: fset.Position(r.expr.Pos()).String(),
|
|
Detail: "value",
|
|
Value: &serial.DescribeValue{
|
|
Type: r.qpos.typeString(r.typ),
|
|
TypesPos: typesPos,
|
|
Value: value,
|
|
ObjPos: objpos,
|
|
},
|
|
})
|
|
}
|
|
|
|
// ---- TYPE ------------------------------------------------------------
|
|
|
|
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
|
|
var description string
|
|
var typ types.Type
|
|
switch n := path[0].(type) {
|
|
case *ast.Ident:
|
|
obj := qpos.info.ObjectOf(n).(*types.TypeName)
|
|
typ = obj.Type()
|
|
if isAlias(obj) {
|
|
description = "alias of "
|
|
} else if obj.Pos() == n.Pos() {
|
|
description = "definition of " // (Named type)
|
|
} else if _, ok := typ.(*types.Basic); ok {
|
|
description = "reference to built-in "
|
|
} else {
|
|
description = "reference to " // (Named type)
|
|
}
|
|
|
|
case ast.Expr:
|
|
typ = qpos.info.TypeOf(n)
|
|
|
|
default:
|
|
// Unreachable?
|
|
return nil, fmt.Errorf("unexpected AST for type: %T", n)
|
|
}
|
|
|
|
description = description + "type " + qpos.typeString(typ)
|
|
|
|
// Show sizes for structs and named types (it's fairly obvious for others).
|
|
switch typ.(type) {
|
|
case *types.Named, *types.Struct:
|
|
szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
|
|
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
|
szs.Sizeof(typ), szs.Alignof(typ))
|
|
}
|
|
|
|
return &describeTypeResult{
|
|
qpos: qpos,
|
|
node: path[0],
|
|
description: description,
|
|
typ: typ,
|
|
methods: accessibleMethods(typ, qpos.info.Pkg),
|
|
fields: accessibleFields(typ, qpos.info.Pkg),
|
|
}, nil
|
|
}
|
|
|
|
type describeTypeResult struct {
|
|
qpos *queryPos
|
|
node ast.Node
|
|
description string
|
|
typ types.Type
|
|
methods []*types.Selection
|
|
fields []describeField
|
|
}
|
|
|
|
type describeField struct {
|
|
implicits []*types.Named
|
|
field *types.Var
|
|
}
|
|
|
|
func printMethods(printf printfFunc, node ast.Node, methods []*types.Selection) {
|
|
if len(methods) > 0 {
|
|
printf(node, "Methods:")
|
|
}
|
|
for _, meth := range methods {
|
|
// Print the method type relative to the package
|
|
// in which it was defined, not the query package,
|
|
printf(meth.Obj(), "\t%s",
|
|
types.SelectionString(meth, types.RelativeTo(meth.Obj().Pkg())))
|
|
}
|
|
}
|
|
|
|
func printFields(printf printfFunc, node ast.Node, fields []describeField) {
|
|
if len(fields) > 0 {
|
|
printf(node, "Fields:")
|
|
}
|
|
|
|
// Align the names and the types (requires two passes).
|
|
var width int
|
|
var names []string
|
|
for _, f := range fields {
|
|
var buf bytes.Buffer
|
|
for _, fld := range f.implicits {
|
|
buf.WriteString(fld.Obj().Name())
|
|
buf.WriteByte('.')
|
|
}
|
|
buf.WriteString(f.field.Name())
|
|
name := buf.String()
|
|
if n := utf8.RuneCountInString(name); n > width {
|
|
width = n
|
|
}
|
|
names = append(names, name)
|
|
}
|
|
|
|
for i, f := range fields {
|
|
// Print the field type relative to the package
|
|
// in which it was defined, not the query package,
|
|
printf(f.field, "\t%*s %s", -width, names[i],
|
|
types.TypeString(f.field.Type(), types.RelativeTo(f.field.Pkg())))
|
|
}
|
|
}
|
|
|
|
func printNamedTypes(printf printfFunc, node ast.Node, names []*types.Named) {
|
|
if len(names) > 0 {
|
|
printf(node, "Named types:")
|
|
}
|
|
|
|
for _, t := range names {
|
|
// Print the type relative to the package
|
|
// in which it was defined, not the query package,
|
|
printf(t.Obj(), "\ttype %s defined here",
|
|
types.TypeString(t.Obj().Type(), types.RelativeTo(t.Obj().Pkg())))
|
|
}
|
|
}
|
|
|
|
func (r *describeTypeResult) PrintPlain(printf printfFunc) {
|
|
printf(r.node, "%s", r.description)
|
|
|
|
// Show the underlying type for a reference to a named type.
|
|
if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
|
|
// TODO(adonovan): improve display of complex struct/interface types.
|
|
printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying()))
|
|
}
|
|
|
|
printMethods(printf, r.node, r.methods)
|
|
if len(r.methods) == 0 {
|
|
// Only report null result for type kinds
|
|
// capable of bearing methods.
|
|
switch r.typ.(type) {
|
|
case *types.Interface, *types.Struct, *types.Named:
|
|
printf(r.node, "No methods.")
|
|
}
|
|
}
|
|
|
|
printFields(printf, r.node, r.fields)
|
|
}
|
|
|
|
func (r *describeTypeResult) JSON(fset *token.FileSet) []byte {
|
|
var namePos, nameDef string
|
|
if nt, ok := r.typ.(*types.Named); ok {
|
|
namePos = fset.Position(nt.Obj().Pos()).String()
|
|
nameDef = nt.Underlying().String()
|
|
}
|
|
return toJSON(&serial.Describe{
|
|
Desc: r.description,
|
|
Pos: fset.Position(r.node.Pos()).String(),
|
|
Detail: "type",
|
|
Type: &serial.DescribeType{
|
|
Type: r.qpos.typeString(r.typ),
|
|
NamePos: namePos,
|
|
NameDef: nameDef,
|
|
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
|
|
},
|
|
})
|
|
}
|
|
|
|
// ---- PACKAGE ------------------------------------------------------------
|
|
|
|
func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
|
|
var description string
|
|
var pkg *types.Package
|
|
switch n := path[0].(type) {
|
|
case *ast.ImportSpec:
|
|
var obj types.Object
|
|
if n.Name != nil {
|
|
obj = qpos.info.Defs[n.Name]
|
|
} else {
|
|
obj = qpos.info.Implicits[n]
|
|
}
|
|
pkgname, _ := obj.(*types.PkgName)
|
|
if pkgname == nil {
|
|
return nil, fmt.Errorf("can't import package %s", n.Path.Value)
|
|
}
|
|
pkg = pkgname.Imported()
|
|
description = fmt.Sprintf("import of package %q", pkg.Path())
|
|
|
|
case *ast.Ident:
|
|
if _, isDef := path[1].(*ast.File); isDef {
|
|
// e.g. package id
|
|
pkg = qpos.info.Pkg
|
|
description = fmt.Sprintf("definition of package %q", pkg.Path())
|
|
} else {
|
|
// e.g. import id "..."
|
|
// or id.F()
|
|
pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported()
|
|
description = fmt.Sprintf("reference to package %q", pkg.Path())
|
|
}
|
|
|
|
default:
|
|
// Unreachable?
|
|
return nil, fmt.Errorf("unexpected AST for package: %T", n)
|
|
}
|
|
|
|
var members []*describeMember
|
|
// NB: "unsafe" has no types.Package
|
|
if pkg != nil {
|
|
// Enumerate the accessible package members
|
|
// in lexicographic order.
|
|
for _, name := range pkg.Scope().Names() {
|
|
if pkg == qpos.info.Pkg || ast.IsExported(name) {
|
|
mem := pkg.Scope().Lookup(name)
|
|
var methods []*types.Selection
|
|
if mem, ok := mem.(*types.TypeName); ok {
|
|
methods = accessibleMethods(mem.Type(), qpos.info.Pkg)
|
|
}
|
|
members = append(members, &describeMember{
|
|
mem,
|
|
methods,
|
|
})
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
|
|
}
|
|
|
|
type describePackageResult struct {
|
|
fset *token.FileSet
|
|
node ast.Node
|
|
description string
|
|
pkg *types.Package
|
|
members []*describeMember // in lexicographic name order
|
|
}
|
|
|
|
type describeMember struct {
|
|
obj types.Object
|
|
methods []*types.Selection // in types.MethodSet order
|
|
}
|
|
|
|
func (r *describePackageResult) PrintPlain(printf printfFunc) {
|
|
printf(r.node, "%s", r.description)
|
|
|
|
// Compute max width of name "column".
|
|
maxname := 0
|
|
for _, mem := range r.members {
|
|
if l := len(mem.obj.Name()); l > maxname {
|
|
maxname = l
|
|
}
|
|
}
|
|
|
|
for _, mem := range r.members {
|
|
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
|
|
for _, meth := range mem.methods {
|
|
printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg)))
|
|
}
|
|
}
|
|
}
|
|
|
|
func formatMember(obj types.Object, maxname int) string {
|
|
qualifier := types.RelativeTo(obj.Pkg())
|
|
var buf bytes.Buffer
|
|
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
|
|
switch obj := obj.(type) {
|
|
case *types.Const:
|
|
fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), obj.Val())
|
|
|
|
case *types.Func:
|
|
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
|
|
|
case *types.TypeName:
|
|
typ := obj.Type()
|
|
if isAlias(obj) {
|
|
buf.WriteString(" = ")
|
|
} else {
|
|
buf.WriteByte(' ')
|
|
typ = typ.Underlying()
|
|
}
|
|
var typestr string
|
|
// Abbreviate long aggregate type names.
|
|
switch typ := typ.(type) {
|
|
case *types.Interface:
|
|
if typ.NumMethods() > 1 {
|
|
typestr = "interface{...}"
|
|
}
|
|
case *types.Struct:
|
|
if typ.NumFields() > 1 {
|
|
typestr = "struct{...}"
|
|
}
|
|
}
|
|
if typestr == "" {
|
|
typestr = types.TypeString(typ, qualifier)
|
|
}
|
|
buf.WriteString(typestr)
|
|
|
|
case *types.Var:
|
|
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
func (r *describePackageResult) JSON(fset *token.FileSet) []byte {
|
|
var members []*serial.DescribeMember
|
|
for _, mem := range r.members {
|
|
obj := mem.obj
|
|
typ := obj.Type()
|
|
var val string
|
|
var alias string
|
|
switch obj := obj.(type) {
|
|
case *types.Const:
|
|
val = obj.Val().String()
|
|
case *types.TypeName:
|
|
if isAlias(obj) {
|
|
alias = "= " // kludgy
|
|
} else {
|
|
typ = typ.Underlying()
|
|
}
|
|
}
|
|
members = append(members, &serial.DescribeMember{
|
|
Name: obj.Name(),
|
|
Type: alias + typ.String(),
|
|
Value: val,
|
|
Pos: fset.Position(obj.Pos()).String(),
|
|
Kind: tokenOf(obj),
|
|
Methods: methodsToSerial(r.pkg, mem.methods, fset),
|
|
})
|
|
}
|
|
return toJSON(&serial.Describe{
|
|
Desc: r.description,
|
|
Pos: fset.Position(r.node.Pos()).String(),
|
|
Detail: "package",
|
|
Package: &serial.DescribePackage{
|
|
Path: r.pkg.Path(),
|
|
Members: members,
|
|
},
|
|
})
|
|
}
|
|
|
|
func tokenOf(o types.Object) string {
|
|
switch o.(type) {
|
|
case *types.Func:
|
|
return "func"
|
|
case *types.Var:
|
|
return "var"
|
|
case *types.TypeName:
|
|
return "type"
|
|
case *types.Const:
|
|
return "const"
|
|
case *types.PkgName:
|
|
return "package"
|
|
case *types.Builtin:
|
|
return "builtin" // e.g. when describing package "unsafe"
|
|
case *types.Nil:
|
|
return "nil"
|
|
case *types.Label:
|
|
return "label"
|
|
}
|
|
panic(o)
|
|
}
|
|
|
|
// ---- STATEMENT ------------------------------------------------------------
|
|
|
|
func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
|
|
var description string
|
|
switch n := path[0].(type) {
|
|
case *ast.Ident:
|
|
if qpos.info.Defs[n] != nil {
|
|
description = "labelled statement"
|
|
} else {
|
|
description = "reference to labelled statement"
|
|
}
|
|
|
|
default:
|
|
// Nothing much to say about statements.
|
|
description = astutil.NodeDescription(n)
|
|
}
|
|
return &describeStmtResult{qpos.fset, path[0], description}, nil
|
|
}
|
|
|
|
type describeStmtResult struct {
|
|
fset *token.FileSet
|
|
node ast.Node
|
|
description string
|
|
}
|
|
|
|
func (r *describeStmtResult) PrintPlain(printf printfFunc) {
|
|
printf(r.node, "%s", r.description)
|
|
}
|
|
|
|
func (r *describeStmtResult) JSON(fset *token.FileSet) []byte {
|
|
return toJSON(&serial.Describe{
|
|
Desc: r.description,
|
|
Pos: fset.Position(r.node.Pos()).String(),
|
|
Detail: "unknown",
|
|
})
|
|
}
|
|
|
|
// ------------------- Utilities -------------------
|
|
|
|
// pathToString returns a string containing the concrete types of the
|
|
// nodes in path.
|
|
func pathToString(path []ast.Node) string {
|
|
var buf bytes.Buffer
|
|
fmt.Fprint(&buf, "[")
|
|
for i, n := range path {
|
|
if i > 0 {
|
|
fmt.Fprint(&buf, " ")
|
|
}
|
|
fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
|
|
}
|
|
fmt.Fprint(&buf, "]")
|
|
return buf.String()
|
|
}
|
|
|
|
func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
|
|
var methods []*types.Selection
|
|
for _, meth := range typeutil.IntuitiveMethodSet(t, nil) {
|
|
if isAccessibleFrom(meth.Obj(), from) {
|
|
methods = append(methods, meth)
|
|
}
|
|
}
|
|
return methods
|
|
}
|
|
|
|
// accessibleFields returns the set of accessible
|
|
// field selections on a value of type recv.
|
|
func accessibleFields(recv types.Type, from *types.Package) []describeField {
|
|
wantField := func(f *types.Var) bool {
|
|
if !isAccessibleFrom(f, from) {
|
|
return false
|
|
}
|
|
// Check that the field is not shadowed.
|
|
obj, _, _ := types.LookupFieldOrMethod(recv, true, f.Pkg(), f.Name())
|
|
return obj == f
|
|
}
|
|
|
|
var fields []describeField
|
|
var visit func(t types.Type, stack []*types.Named)
|
|
visit = func(t types.Type, stack []*types.Named) {
|
|
tStruct, ok := deref(t).Underlying().(*types.Struct)
|
|
if !ok {
|
|
return
|
|
}
|
|
fieldloop:
|
|
for i := 0; i < tStruct.NumFields(); i++ {
|
|
f := tStruct.Field(i)
|
|
|
|
// Handle recursion through anonymous fields.
|
|
if f.Anonymous() {
|
|
tf := f.Type()
|
|
if ptr, ok := tf.(*types.Pointer); ok {
|
|
tf = ptr.Elem()
|
|
}
|
|
if named, ok := tf.(*types.Named); ok { // (be defensive)
|
|
// If we've already visited this named type
|
|
// on this path, break the cycle.
|
|
for _, x := range stack {
|
|
if x == named {
|
|
continue fieldloop
|
|
}
|
|
}
|
|
visit(f.Type(), append(stack, named))
|
|
}
|
|
}
|
|
|
|
// Save accessible fields.
|
|
if wantField(f) {
|
|
fields = append(fields, describeField{
|
|
implicits: append([]*types.Named(nil), stack...),
|
|
field: f,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
visit(recv, nil)
|
|
|
|
return fields
|
|
}
|
|
|
|
func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
|
|
return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
|
|
}
|
|
|
|
func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
|
|
qualifier := types.RelativeTo(this)
|
|
var jmethods []serial.DescribeMethod
|
|
for _, meth := range methods {
|
|
var ser serial.DescribeMethod
|
|
if meth != nil { // may contain nils when called by implements (on a method)
|
|
ser = serial.DescribeMethod{
|
|
Name: types.SelectionString(meth, qualifier),
|
|
Pos: fset.Position(meth.Obj().Pos()).String(),
|
|
}
|
|
}
|
|
jmethods = append(jmethods, ser)
|
|
}
|
|
return jmethods
|
|
}
|