900 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			900 lines
		
	
	
		
			23 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,
 | 
						|
		constVal: constVal,
 | 
						|
		obj:      obj,
 | 
						|
		methods:  accessibleMethods(typ, qpos.info.Pkg),
 | 
						|
		fields:   accessibleFields(typ, qpos.info.Pkg),
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
type describeValueResult struct {
 | 
						|
	qpos     *queryPos
 | 
						|
	expr     ast.Expr       // query node
 | 
						|
	typ      types.Type     // type of expression
 | 
						|
	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)
 | 
						|
}
 | 
						|
 | 
						|
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()
 | 
						|
	}
 | 
						|
 | 
						|
	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),
 | 
						|
			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 (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
 | 
						|
}
 |