// 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 oracle import ( "bytes" "fmt" "go/ast" "go/token" "os" "sort" "strconv" "strings" "code.google.com/p/go.tools/go/exact" "code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/oracle/serial" "code.google.com/p/go.tools/pointer" "code.google.com/p/go.tools/ssa" ) // describe describes the syntax node denoted by the query position, // including: // - its syntactic category // - the location of the definition of its referent (for identifiers) // - its type and method set (for an expression or type expression) // - its points-to set (for a pointer-like expression) // - its dynamic types (for an interface, reflect.Value, or // reflect.Type expression) and their points-to sets. // // All printed sets are sorted to ensure determinism. // func describe(o *Oracle, qpos *QueryPos) (queryResult, error) { if false { // debugging o.fprintf(os.Stderr, qpos.path[0], "you selected: %s %s", importer.NodeDescription(qpos.path[0]), pathToString2(qpos.path)) } path, action := findInterestingNode(qpos.info, qpos.path) switch action { case actionExpr: return describeValue(o, qpos, path) case actionType: return describeType(o, qpos, path) case actionPackage: return describePackage(o, qpos, path) case actionStmt: return describeStmt(o, qpos, path) case actionUnknown: return &describeUnknownResult{path[0]}, nil default: panic(action) // unreachable } } type describeUnknownResult struct { node ast.Node } func (r *describeUnknownResult) display(printf printfFunc) { // Nothing much to say about misc syntax. printf(r.node, "%s", importer.NodeDescription(r.node)) } func (r *describeUnknownResult) toSerial(res *serial.Result, fset *token.FileSet) { res.Describe = &serial.Describe{ Desc: importer.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 *importer.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: if pkginfo.ObjectOf(n.Sel) == nil { // Is this reachable? return path, actionUnknown } // Descend to .Sel child. path = append([]ast.Node{n.Sel}, path...) continue case *ast.Ident: switch obj := 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: // For f in 'interface {f()}', return the interface type, for now. if _, ok := path[1].(*ast.Field); ok { _ = path[2].(*ast.FieldList) // assertion if _, ok := path[3].(*ast.InterfaceType); ok { return path[3:], actionType } } // For reference to built-in function, return enclosing call. if _, ok := obj.Type().(*types.Builtin); ok { // Ascend to enclosing function call. path = path[1:] continue } 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: // TODO(adonovan): fix: why no package object? go/types bug? return path[1:], actionPackage default: // e.g. blank identifier (go/types bug?) // or y in "switch y := x.(type)" (go/types bug?) fmt.Printf("unknown reference %s in %T\n", n, path[1]) return path, actionUnknown } case *ast.StarExpr: if pkginfo.IsType(n) { 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 } // ---- VALUE ------------------------------------------------------------ // ssaValueForIdent returns the ssa.Value for the ast.Ident whose path // to the root of the AST is path. isAddr reports whether the // ssa.Value is the address denoted by the ast.Ident, not its value. // ssaValueForIdent may return a nil Value without an error to // indicate the pointer analysis is not appropriate. // func ssaValueForIdent(prog *ssa.Program, qinfo *importer.PackageInfo, obj types.Object, path []ast.Node) (value ssa.Value, isAddr bool, err error) { if obj, ok := obj.(*types.Var); ok { pkg := prog.Package(qinfo.Pkg) pkg.Build() if v, addr := prog.VarValue(obj, pkg, path); v != nil { // Don't run pointer analysis on a ref to a const expression. if _, ok := v.(*ssa.Const); ok { return } return v, addr, nil } return nil, false, fmt.Errorf("can't locate SSA Value for var %s", obj.Name()) } // Don't run pointer analysis on const/func objects. return } // ssaValueForExpr returns the ssa.Value of the non-ast.Ident // expression whose path to the root of the AST is path. It may // return a nil Value without an error to indicate the pointer // analysis is not appropriate. // func ssaValueForExpr(prog *ssa.Program, qinfo *importer.PackageInfo, path []ast.Node) (value ssa.Value, isAddr bool, err error) { pkg := prog.Package(qinfo.Pkg) pkg.SetDebugMode(true) pkg.Build() fn := ssa.EnclosingFunction(pkg, path) if fn == nil { return nil, false, fmt.Errorf("no SSA function built for this location (dead code?)") } if v, addr := fn.ValueForExpr(path[0].(ast.Expr)); v != nil { return v, addr, nil } return nil, false, fmt.Errorf("can't locate SSA Value for expression in %s", fn) } func describeValue(o *Oracle, 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: // Is this reachable? return nil, fmt.Errorf("unexpected AST for expr: %T", n) } typ := qpos.info.TypeOf(expr) constVal := qpos.info.ValueOf(expr) // From this point on, we cannot fail with an error. // Failure to run the pointer analysis will be reported later. // // Our disposition to pointer analysis may be one of the following: // - ok: ssa.Value was const or func. // - error: no ssa.Value for expr (e.g. trivially dead code) // - ok: ssa.Value is non-pointerlike // - error: no Pointer for ssa.Value (e.g. analytically unreachable) // - ok: Pointer has empty points-to set // - ok: Pointer has non-empty points-to set // ptaErr is non-nil only in the "error:" cases. var ptaErr error var ptrs []pointerResult // Only run pointer analysis on pointerlike expression types. if pointer.CanPoint(typ) { // Determine the ssa.Value for the expression. var value ssa.Value var isAddr bool if obj != nil { // def/ref of func/var/const object value, isAddr, ptaErr = ssaValueForIdent(o.prog, qpos.info, obj, path) } else { // any other expression if qpos.info.ValueOf(path[0].(ast.Expr)) == nil { // non-constant? value, isAddr, ptaErr = ssaValueForExpr(o.prog, qpos.info, path) } } if value != nil { ptrs, ptaErr = describePointer(o, value, isAddr) } } return &describeValueResult{ qpos: qpos, expr: expr, typ: typ, constVal: constVal, obj: obj, ptaErr: ptaErr, ptrs: ptrs, }, nil } // describePointer runs the pointer analysis of the selected SSA value. func describePointer(o *Oracle, v ssa.Value, indirect bool) (ptrs []pointerResult, err error) { buildSSA(o) // TODO(adonovan): don't run indirect pointer analysis on non-ptr-ptrlike types. o.config.Queries = map[ssa.Value]pointer.Indirect{v: pointer.Indirect(indirect)} ptares := ptrAnalysis(o) // Combine the PT sets from all contexts. pointers := ptares.Queries[v] if pointers == nil { return nil, fmt.Errorf("PTA did not encounter this expression (dead code?)") } pts := pointer.PointsToCombined(pointers) if pointer.CanHaveDynamicTypes(v.Type()) { // Show concrete types for interface/reflect.Value expression. if concs := pts.DynamicTypes(); concs.Len() > 0 { concs.Iterate(func(conc types.Type, pta interface{}) { combined := pointer.PointsToCombined(pta.([]pointer.Pointer)) labels := combined.Labels() sort.Sort(byPosAndString(labels)) // to ensure determinism ptrs = append(ptrs, pointerResult{conc, labels}) }) } } else { // Show labels for other expressions. labels := pts.Labels() sort.Sort(byPosAndString(labels)) // to ensure determinism ptrs = append(ptrs, pointerResult{v.Type(), labels}) } sort.Sort(byTypeString(ptrs)) // to ensure determinism return ptrs, nil } type pointerResult struct { typ types.Type // type of the pointer (always concrete) labels []*pointer.Label } type describeValueResult struct { qpos *QueryPos expr ast.Expr // query node typ types.Type // type of expression constVal exact.Value // value of expression, if constant obj types.Object // var/func/const object, if expr was Ident ptaErr error // reason why pointer analysis couldn't be run, or failed ptrs []pointerResult // pointer info (typ is concrete => len==1) } func (r *describeValueResult) display(printf printfFunc) { var prefix, suffix string if r.constVal != nil { suffix = fmt.Sprintf(" of constant 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.obj, suffix) } else { // referring ident printf(r.expr, "reference to %s%s%s", prefix, r.obj, suffix) if def := r.obj.Pos(); def != token.NoPos { printf(def, "defined here") } } } else { desc := importer.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.typ) } } // pointer analysis could not be run if r.ptaErr != nil { printf(r.expr, "no points-to information: %s", r.ptaErr) return } if r.ptrs == nil { return // PTA was not invoked (not an error) } // Display the results of pointer analysis. if pointer.CanHaveDynamicTypes(r.typ) { // Show concrete types for interface, reflect.Type or // reflect.Value expression. if len(r.ptrs) > 0 { printf(r.qpos, "this %s may contain these dynamic types:", r.typ) for _, ptr := range r.ptrs { var obj types.Object if nt, ok := deref(ptr.typ).(*types.Named); ok { obj = nt.Obj() } if len(ptr.labels) > 0 { printf(obj, "\t%s, may point to:", ptr.typ) printLabels(printf, ptr.labels, "\t\t") } else { printf(obj, "\t%s", ptr.typ) } } } else { printf(r.qpos, "this %s cannot contain any dynamic types.", r.typ) } } else { // Show labels for other expressions. if ptr := r.ptrs[0]; len(ptr.labels) > 0 { printf(r.qpos, "value may point to these labels:") printLabels(printf, ptr.labels, "\t") } else { printf(r.qpos, "value cannot point to anything.") } } } func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) { var value, objpos, ptaerr string if r.constVal != nil { value = r.constVal.String() } if r.obj != nil { objpos = fset.Position(r.obj.Pos()).String() } if r.ptaErr != nil { ptaerr = r.ptaErr.Error() } var pts []*serial.DescribePointer for _, ptr := range r.ptrs { var namePos string if nt, ok := deref(ptr.typ).(*types.Named); ok { namePos = fset.Position(nt.Obj().Pos()).String() } var labels []serial.DescribePTALabel for _, l := range ptr.labels { labels = append(labels, serial.DescribePTALabel{ Pos: fset.Position(l.Pos()).String(), Desc: l.String(), }) } pts = append(pts, &serial.DescribePointer{ Type: ptr.typ.String(), NamePos: namePos, Labels: labels, }) } res.Describe = &serial.Describe{ Desc: importer.NodeDescription(r.expr), Pos: fset.Position(r.expr.Pos()).String(), Detail: "value", Value: &serial.DescribeValue{ Type: r.typ.String(), Value: value, ObjPos: objpos, PTAErr: ptaerr, PTS: pts, }, } } type byTypeString []pointerResult func (a byTypeString) Len() int { return len(a) } func (a byTypeString) Less(i, j int) bool { return a[i].typ.String() < a[j].typ.String() } func (a byTypeString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } type byPosAndString []*pointer.Label func (a byPosAndString) Len() int { return len(a) } func (a byPosAndString) Less(i, j int) bool { cmp := a[i].Pos() - a[j].Pos() return cmp < 0 || (cmp == 0 && a[i].String() < a[j].String()) } func (a byPosAndString) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func printLabels(printf printfFunc, labels []*pointer.Label, prefix string) { // TODO(adonovan): due to context-sensitivity, many of these // labels may differ only by context, which isn't apparent. for _, label := range labels { printf(label, "%s%s", prefix, label) } } // ---- TYPE ------------------------------------------------------------ func describeType(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeTypeResult, error) { var description string var t types.Type switch n := path[0].(type) { case *ast.Ident: t = qpos.info.TypeOf(n) switch t := t.(type) { case *types.Basic: description = "reference to built-in type " + t.String() case *types.Named: isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above if isDef { description = "definition of type " + t.String() } else { description = "reference to type " + t.String() } } case ast.Expr: t = qpos.info.TypeOf(n) description = "type " + t.String() default: // Unreachable? return nil, fmt.Errorf("unexpected AST for type: %T", n) } return &describeTypeResult{ node: path[0], description: description, typ: t, methods: accessibleMethods(t, qpos.info.Pkg), }, nil } type describeTypeResult struct { node ast.Node description string typ types.Type methods []*types.Selection } func (r *describeTypeResult) display(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() { printf(nt.Obj(), "defined as %s", nt.Underlying()) } // Print the method set, if the type kind is capable of bearing methods. switch r.typ.(type) { case *types.Interface, *types.Struct, *types.Named: if len(r.methods) > 0 { printf(r.node, "Method set:") for _, meth := range r.methods { printf(meth.Obj(), "\t%s", meth) } } else { printf(r.node, "No methods.") } } } func (r *describeTypeResult) toSerial(res *serial.Result, fset *token.FileSet) { var namePos, nameDef string if nt, ok := r.typ.(*types.Named); ok { namePos = fset.Position(nt.Obj().Pos()).String() nameDef = nt.Underlying().String() } res.Describe = &serial.Describe{ Desc: r.description, Pos: fset.Position(r.node.Pos()).String(), Detail: "type", Type: &serial.DescribeType{ Type: r.typ.String(), NamePos: namePos, NameDef: nameDef, Methods: methodsToSerial(r.methods, fset), }, } } // ---- PACKAGE ------------------------------------------------------------ func describePackage(o *Oracle, qpos *QueryPos, path []ast.Node) (*describePackageResult, error) { var description string var pkg *types.Package switch n := path[0].(type) { case *ast.ImportSpec: // Most ImportSpecs have no .Name Ident so we can't // use ObjectOf. // We could use the types.Info.Implicits mechanism, // but it's easier just to look it up by name. description = "import of package " + n.Path.Value importPath, _ := strconv.Unquote(n.Path.Value) pkg = o.prog.ImportedPackage(importPath).Object 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).Pkg() 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{o.prog.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) display(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", meth) } } } func formatMember(obj types.Object, maxname int) string { 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", obj.Type(), obj.Val().String()) case *types.Func: fmt.Fprintf(&buf, " %s", obj.Type()) case *types.TypeName: // Abbreviate long aggregate type names. var abbrev string switch t := obj.Type().Underlying().(type) { case *types.Interface: if t.NumMethods() > 1 { abbrev = "interface{...}" } case *types.Struct: if t.NumFields() > 1 { abbrev = "struct{...}" } } if abbrev == "" { fmt.Fprintf(&buf, " %s", obj.Type().Underlying()) } else { fmt.Fprintf(&buf, " %s", abbrev) } case *types.Var: fmt.Fprintf(&buf, " %s", obj.Type()) } return buf.String() } func (r *describePackageResult) toSerial(res *serial.Result, fset *token.FileSet) { var members []*serial.DescribeMember for _, mem := range r.members { typ := mem.obj.Type() var val string switch mem := mem.obj.(type) { case *types.Const: val = mem.Val().String() case *types.TypeName: typ = typ.Underlying() } members = append(members, &serial.DescribeMember{ Name: mem.obj.Name(), Type: typ.String(), Value: val, Pos: fset.Position(mem.obj.Pos()).String(), Kind: tokenOf(mem.obj), Methods: methodsToSerial(mem.methods, fset), }) } res.Describe = &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" } panic(o) } // ---- STATEMENT ------------------------------------------------------------ func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResult, error) { var description string switch n := path[0].(type) { case *ast.Ident: if qpos.info.ObjectOf(n).Pos() == n.Pos() { description = "labelled statement" } else { description = "reference to labelled statement" } default: // Nothing much to say about statements. description = importer.NodeDescription(n) } return &describeStmtResult{o.prog.Fset, path[0], description}, nil } type describeStmtResult struct { fset *token.FileSet node ast.Node description string } func (r *describeStmtResult) display(printf printfFunc) { printf(r.node, "%s", r.description) } func (r *describeStmtResult) toSerial(res *serial.Result, fset *token.FileSet) { res.Describe = &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 pathToString2(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 ssa.IntuitiveMethodSet(t) { if isAccessibleFrom(meth.Obj(), from) { methods = append(methods, meth) } } return methods } func isAccessibleFrom(obj types.Object, pkg *types.Package) bool { return ast.IsExported(obj.Name()) || obj.Pkg() == pkg } func methodsToSerial(methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod { var jmethods []serial.DescribeMethod for _, meth := range methods { jmethods = append(jmethods, serial.DescribeMethod{ Name: meth.String(), Pos: fset.Position(meth.Obj().Pos()).String(), }) } return jmethods }