From d15da3015c832ff48d9eede2723e5d2272a59adf Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 3 Nov 2016 16:40:09 -0400 Subject: [PATCH] cmd/guru: describe Go 1.8 aliases Also: - always display the value of a constant expr, whether query expr is a definition, a reference, or an alias. - eliminate some go1.5 portability code. - remove go1.8 fork of referrers; no changes are necessary since I decided not to treat aliases specially. - add tests. Tested with Go 1.6, Go 1.7, and tip (Go 1.8). Change-Id: I94624cff82f4d8c0dcbf12d11c8ce16e8168a7fe Reviewed-on: https://go-review.googlesource.com/32730 Reviewed-by: Robert Griesemer --- cmd/guru/callees18.go | 12 +- cmd/guru/definition18.go | 4 + cmd/guru/describe.go | 21 +- cmd/guru/describe18.go | 70 +-- cmd/guru/guru_test.go | 5 + cmd/guru/referrers.go | 8 +- cmd/guru/referrers18.go | 524 --------------------- cmd/guru/testdata/src/alias/main.go | 25 + cmd/guru/testdata/src/alias/main.golden | 54 +++ cmd/guru/testdata/src/aliaslib/aliaslib.go | 11 + cmd/guru/testdata/src/describe/main.golden | 18 +- cmd/guru/testdata/src/imports/main.golden | 2 +- 12 files changed, 173 insertions(+), 581 deletions(-) delete mode 100644 cmd/guru/referrers18.go create mode 100644 cmd/guru/testdata/src/alias/main.go create mode 100644 cmd/guru/testdata/src/alias/main.golden create mode 100644 cmd/guru/testdata/src/aliaslib/aliaslib.go diff --git a/cmd/guru/callees18.go b/cmd/guru/callees18.go index 6e269640..0afac903 100644 --- a/cmd/guru/callees18.go +++ b/cmd/guru/callees18.go @@ -64,7 +64,7 @@ func callees(q *Query) error { // e.g. f := func(){}; f(). switch funexpr := unparen(e.Fun).(type) { case *ast.Ident: - switch obj := qpos.info.Uses[funexpr].(type) { + switch obj := original(qpos.info.Uses[funexpr]).(type) { case *types.Builtin: // Reject calls to built-ins. return fmt.Errorf("this is a call to the built-in '%s' operator", obj.Name()) @@ -82,7 +82,7 @@ func callees(q *Query) error { // qualified identifier. // May refer to top level function variable // or to top level function. - callee := qpos.info.Uses[funexpr.Sel] + callee := original(qpos.info.Uses[funexpr.Sel]) if obj, ok := callee.(*types.Func); ok { q.Output(lprog.Fset, &calleesTypesResult{ site: e, @@ -257,3 +257,11 @@ type byFuncPos []*ssa.Function func (a byFuncPos) Len() int { return len(a) } func (a byFuncPos) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() } func (a byFuncPos) Swap(i, j int) { a[i], a[j] = a[j], a[i] } + +// TODO(adonovan): use types.Original when available. +func original(obj types.Object) types.Object { + if alias, ok := obj.(*types.Alias); ok { + return alias.Orig() + } + return obj +} diff --git a/cmd/guru/definition18.go b/cmd/guru/definition18.go index c267f531..b6f75884 100644 --- a/cmd/guru/definition18.go +++ b/cmd/guru/definition18.go @@ -177,6 +177,10 @@ func findPackageMember(ctxt *build.Context, fset *token.FileSet, srcdir, pkg, me if spec.Name.Name == member { return token.TYPE, spec.Name.Pos(), nil } + case *ast.AliasSpec: + if spec.Name.Name == member { + return decl.Tok, spec.Name.Pos(), nil + } } } case *ast.FuncDecl: diff --git a/cmd/guru/describe.go b/cmd/guru/describe.go index 8e06e4c6..6493e903 100644 --- a/cmd/guru/describe.go +++ b/cmd/guru/describe.go @@ -334,6 +334,9 @@ func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error t = types.Typ[types.Invalid] } constVal := qpos.info.Types[expr].Value + if c, ok := obj.(*types.Const); ok { + constVal = c.Val() + } return &describeValueResult{ qpos: qpos, @@ -359,7 +362,7 @@ type describeValueResult struct { func (r *describeValueResult) PrintPlain(printf printfFunc) { var prefix, suffix string if r.constVal != nil { - suffix = fmt.Sprintf(" of constant value %s", constValString(r.constVal)) + suffix = fmt.Sprintf(" of value %s", r.constVal) } switch obj := r.obj.(type) { case *types.Func: @@ -659,25 +662,13 @@ func (r *describePackageResult) PrintPlain(printf printfFunc) { } } -// Helper function to adjust go1.5 numeric go/constant formatting. -// Can be removed once we give up compatibility with go1.5. -func constValString(v exact.Value) string { - if v.Kind() == exact.Float { - // In go1.5, go/constant floating-point values are printed - // as fractions. Make them appear as floating-point numbers. - f, _ := exact.Float64Val(v) - return fmt.Sprintf("%g", f) - } - return v.String() -} - 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), constValString(obj.Val())) + fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), obj.Val()) case *types.Func: fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier)) @@ -714,7 +705,7 @@ func (r *describePackageResult) JSON(fset *token.FileSet) []byte { var val string switch mem := mem.obj.(type) { case *types.Const: - val = constValString(mem.Val()) + val = mem.Val().String() case *types.TypeName: typ = typ.Underlying() } diff --git a/cmd/guru/describe18.go b/cmd/guru/describe18.go index 6ec38fd6..3bc91a01 100644 --- a/cmd/guru/describe18.go +++ b/cmd/guru/describe18.go @@ -159,6 +159,11 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No } return path, actionUnknown // uninteresting + case *ast.AliasSpec: + // Descend to alias name. + path = append([]ast.Node{n.Name}, path...) + continue + case *ast.TypeSpec: // Descend to type name. path = append([]ast.Node{n.Name}, path...) @@ -217,7 +222,7 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No continue case *ast.Ident: - switch pkginfo.ObjectOf(n).(type) { + switch original(pkginfo.ObjectOf(n)).(type) { case *types.PkgName: return path, actionPackage @@ -334,6 +339,9 @@ func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error t = types.Typ[types.Invalid] } constVal := qpos.info.Types[expr].Value + if c, ok := original(obj).(*types.Const); ok { + constVal = c.Val() + } return &describeValueResult{ qpos: qpos, @@ -351,7 +359,7 @@ type describeValueResult struct { 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 + obj types.Object // var/func/const object, if expr was Ident, or alias to same methods []*types.Selection fields []describeField } @@ -359,21 +367,24 @@ type describeValueResult struct { func (r *describeValueResult) PrintPlain(printf printfFunc) { var prefix, suffix string if r.constVal != nil { - suffix = fmt.Sprintf(" of constant value %s", constValString(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 " - } - } + suffix = fmt.Sprintf(" of value %s", r.constVal) } // Describe the expression. if r.obj != nil { + 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 " + } + } + case *types.Alias: + prefix = tokenOf(obj.Orig()) + " " + } + if r.obj.Pos() == r.expr.Pos() { // defining ident printf(r.expr, "definition of %s%s%s", prefix, r.qpos.objectString(r.obj), suffix) @@ -436,6 +447,8 @@ func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) isDef := t.Obj().Pos() == n.Pos() // see caveats at isDef above if isDef { description = "definition of " + } else if _, ok := qpos.info.ObjectOf(n).(*types.Alias); ok { + description = "alias of " } else { description = "reference to " } @@ -659,25 +672,21 @@ func (r *describePackageResult) PrintPlain(printf printfFunc) { } } -// Helper function to adjust go1.5 numeric go/constant formatting. -// Can be removed once we give up compatibility with go1.5. -func constValString(v exact.Value) string { - if v.Kind() == exact.Float { - // In go1.5, go/constant floating-point values are printed - // as fractions. Make them appear as floating-point numbers. - f, _ := exact.Float64Val(v) - return fmt.Sprintf("%g", f) - } - return v.String() -} - 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.Alias: + buf.WriteString(" => ") + if orig := obj.Orig(); orig != nil { + fmt.Fprintf(&buf, "%s.%s", orig.Pkg().Name(), orig.Name()) + } else { + buf.WriteByte('?') + } + case *types.Const: - fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), constValString(obj.Val())) + fmt.Fprintf(&buf, " %s = %s", types.TypeString(obj.Type(), qualifier), obj.Val()) case *types.Func: fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier)) @@ -714,7 +723,7 @@ func (r *describePackageResult) JSON(fset *token.FileSet) []byte { var val string switch mem := mem.obj.(type) { case *types.Const: - val = constValString(mem.Val()) + val = mem.Val().String() case *types.TypeName: typ = typ.Underlying() } @@ -739,7 +748,7 @@ func (r *describePackageResult) JSON(fset *token.FileSet) []byte { } func tokenOf(o types.Object) string { - switch o.(type) { + switch o := o.(type) { case *types.Func: return "func" case *types.Var: @@ -756,6 +765,11 @@ func tokenOf(o types.Object) string { return "nil" case *types.Label: return "label" + case *types.Alias: + if o.Orig() == nil { + return "alias" + } + return tokenOf(o.Orig()) } panic(o) } diff --git a/cmd/guru/guru_test.go b/cmd/guru/guru_test.go index 62dcd6d3..2eb12475 100644 --- a/cmd/guru/guru_test.go +++ b/cmd/guru/guru_test.go @@ -230,6 +230,7 @@ func TestGuru(t *testing.T) { "testdata/src/reflection/main.go", "testdata/src/what/main.go", "testdata/src/whicherrs/main.go", + "testdata/src/alias/main.go", // Go 1.8 only // JSON: // TODO(adonovan): most of these are very similar; combine them. "testdata/src/calls-json/main.go", @@ -247,6 +248,10 @@ func TestGuru(t *testing.T) { // wording for a "no such file or directory" error. continue } + if filename == "testdata/src/alias/main.go" && + !strings.Contains(fmt.Sprint(build.Default.ReleaseTags), "go1.8") { + continue + } json := strings.Contains(filename, "-json/") queries := parseQueries(t, filename) diff --git a/cmd/guru/referrers.go b/cmd/guru/referrers.go index e80b0a67..a5164a3c 100644 --- a/cmd/guru/referrers.go +++ b/cmd/guru/referrers.go @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build !go1.8 - package main import ( @@ -27,6 +25,12 @@ import ( // Referrers reports all identifiers that resolve to the same object // as the queried identifier, within any package in the workspace. +// +// Go 1.8 aliases are not treated specially. A referrers query on an +// object will report declarations of aliases of that object, but not +// uses of those aliases; for that, a second query is needed. +// Similarly, a query on an alias will report all uses of the alias but +// not of the original object. func referrers(q *Query) error { fset := token.NewFileSet() lconf := loader.Config{Fset: fset, Build: q.Build} diff --git a/cmd/guru/referrers18.go b/cmd/guru/referrers18.go deleted file mode 100644 index 277f57a5..00000000 --- a/cmd/guru/referrers18.go +++ /dev/null @@ -1,524 +0,0 @@ -// 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. - -// +build go1.8 - -package main - -import ( - "bytes" - "fmt" - "go/ast" - "go/build" - "go/token" - "go/types" - "io" - "log" - "sort" - "strings" - "sync" - - "golang.org/x/tools/cmd/guru/serial" - "golang.org/x/tools/go/buildutil" - "golang.org/x/tools/go/loader" - "golang.org/x/tools/refactor/importgraph" -) - -// Referrers reports all identifiers that resolve to the same object -// as the queried identifier, within any package in the workspace. -func referrers(q *Query) error { - fset := token.NewFileSet() - lconf := loader.Config{Fset: fset, Build: q.Build} - allowErrors(&lconf) - - if _, err := importQueryPackage(q.Pos, &lconf); err != nil { - return err - } - - // Load/parse/type-check the query package. - lprog, err := lconf.Load() - if err != nil { - return err - } - - qpos, err := parseQueryPos(lprog, q.Pos, false) - if err != nil { - return err - } - - id, _ := qpos.path[0].(*ast.Ident) - if id == nil { - return fmt.Errorf("no identifier here") - } - - obj := qpos.info.ObjectOf(id) - if obj == nil { - // Happens for y in "switch y := x.(type)", - // the package declaration, - // and unresolved identifiers. - if _, ok := qpos.path[1].(*ast.File); ok { // package decl? - return packageReferrers(q, qpos.info.Pkg.Path()) - } - return fmt.Errorf("no object for identifier: %T", qpos.path[1]) - } - - // Imported package name? - if pkgname, ok := obj.(*types.PkgName); ok { - return packageReferrers(q, pkgname.Imported().Path()) - } - - if obj.Pkg() == nil { - return fmt.Errorf("references to predeclared %q are everywhere!", obj.Name()) - } - - // For a globally accessible object defined in package P, we - // must load packages that depend on P. Specifically, for a - // package-level object, we need load only direct importers - // of P, but for a field or interface method, we must load - // any package that transitively imports P. - if global, pkglevel := classify(obj); global { - // We'll use the the object's position to identify it in the larger program. - objposn := fset.Position(obj.Pos()) - defpkg := obj.Pkg().Path() // defining package - return globalReferrers(q, qpos.info.Pkg.Path(), defpkg, objposn, pkglevel) - } - - q.Output(fset, &referrersInitialResult{ - qinfo: qpos.info, - obj: obj, - }) - - outputUses(q, fset, usesOf(obj, qpos.info), obj.Pkg()) - - return nil // success -} - -// classify classifies objects by how far -// we have to look to find references to them. -func classify(obj types.Object) (global, pkglevel bool) { - if obj.Exported() { - if obj.Parent() == nil { - // selectable object (field or method) - return true, false - } - if obj.Parent() == obj.Pkg().Scope() { - // lexical object (package-level var/const/func/type) - return true, true - } - } - // object with unexported named or defined in local scope - return false, false -} - -// packageReferrers reports all references to the specified package -// throughout the workspace. -func packageReferrers(q *Query, path string) error { - // Scan the workspace and build the import graph. - // Ignore broken packages. - _, rev, _ := importgraph.Build(q.Build) - - // Find the set of packages that directly import the query package. - // Only those packages need typechecking of function bodies. - users := rev[path] - - // Load the larger program. - fset := token.NewFileSet() - lconf := loader.Config{ - Fset: fset, - Build: q.Build, - TypeCheckFuncBodies: func(p string) bool { - return users[strings.TrimSuffix(p, "_test")] - }, - } - allowErrors(&lconf) - - // The importgraph doesn't treat external test packages - // as separate nodes, so we must use ImportWithTests. - for path := range users { - lconf.ImportWithTests(path) - } - - // Subtle! AfterTypeCheck needs no mutex for qpkg because the - // topological import order gives us the necessary happens-before edges. - // TODO(adonovan): what about import cycles? - var qpkg *types.Package - - // For efficiency, we scan each package for references - // just after it has been type-checked. The loader calls - // AfterTypeCheck (concurrently), providing us with a stream of - // packages. - lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { - // AfterTypeCheck may be called twice for the same package due to augmentation. - - if info.Pkg.Path() == path && qpkg == nil { - // Found the package of interest. - qpkg = info.Pkg - fakepkgname := types.NewPkgName(token.NoPos, qpkg, qpkg.Name(), qpkg) - q.Output(fset, &referrersInitialResult{ - qinfo: info, - obj: fakepkgname, // bogus - }) - } - - // Only inspect packages that directly import the - // declaring package (and thus were type-checked). - if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { - // Find PkgNames that refer to qpkg. - // TODO(adonovan): perhaps more useful would be to show imports - // of the package instead of qualified identifiers. - var refs []*ast.Ident - for id, obj := range info.Uses { - if obj, ok := obj.(*types.PkgName); ok && obj.Imported() == qpkg { - refs = append(refs, id) - } - } - outputUses(q, fset, refs, info.Pkg) - } - - clearInfoFields(info) // save memory - } - - lconf.Load() // ignore error - - if qpkg == nil { - log.Fatalf("query package %q not found during reloading", path) - } - - return nil -} - -func usesOf(queryObj types.Object, info *loader.PackageInfo) []*ast.Ident { - var refs []*ast.Ident - for id, obj := range info.Uses { - if sameObj(queryObj, obj) { - refs = append(refs, id) - } - } - return refs -} - -// outputUses outputs a result describing refs, which appear in the package denoted by info. -func outputUses(q *Query, fset *token.FileSet, refs []*ast.Ident, pkg *types.Package) { - if len(refs) > 0 { - sort.Sort(byNamePos{fset, refs}) - q.Output(fset, &referrersPackageResult{ - pkg: pkg, - build: q.Build, - fset: fset, - refs: refs, - }) - } -} - -// globalReferrers reports references throughout the entire workspace to the -// object at the specified source position. Its defining package is defpkg, -// and the query package is qpkg. isPkgLevel indicates whether the object -// is defined at package-level. -func globalReferrers(q *Query, qpkg, defpkg string, objposn token.Position, isPkgLevel bool) error { - // Scan the workspace and build the import graph. - // Ignore broken packages. - _, rev, _ := importgraph.Build(q.Build) - - // Find the set of packages that depend on defpkg. - // Only function bodies in those packages need type-checking. - var users map[string]bool - if isPkgLevel { - users = rev[defpkg] // direct importers - if users == nil { - users = make(map[string]bool) - } - users[defpkg] = true // plus the defining package itself - } else { - users = rev.Search(defpkg) // transitive importers - } - - // Prepare to load the larger program. - fset := token.NewFileSet() - lconf := loader.Config{ - Fset: fset, - Build: q.Build, - TypeCheckFuncBodies: func(p string) bool { - return users[strings.TrimSuffix(p, "_test")] - }, - } - allowErrors(&lconf) - - // The importgraph doesn't treat external test packages - // as separate nodes, so we must use ImportWithTests. - for path := range users { - lconf.ImportWithTests(path) - } - - // The remainder of this function is somewhat tricky because it - // operates on the concurrent stream of packages observed by the - // loader's AfterTypeCheck hook. Most of guru's helper - // functions assume the entire program has already been loaded, - // so we can't use them here. - // TODO(adonovan): smooth things out once the other changes have landed. - - // Results are reported concurrently from within the - // AfterTypeCheck hook. The program may provide a useful stream - // of information even if the user doesn't let the program run - // to completion. - - var ( - mu sync.Mutex - qobj types.Object - qinfo *loader.PackageInfo // info for qpkg - ) - - // For efficiency, we scan each package for references - // just after it has been type-checked. The loader calls - // AfterTypeCheck (concurrently), providing us with a stream of - // packages. - lconf.AfterTypeCheck = func(info *loader.PackageInfo, files []*ast.File) { - // AfterTypeCheck may be called twice for the same package due to augmentation. - - // Only inspect packages that depend on the declaring package - // (and thus were type-checked). - if lconf.TypeCheckFuncBodies(info.Pkg.Path()) { - // Record the query object and its package when we see it. - mu.Lock() - if qobj == nil && info.Pkg.Path() == defpkg { - // Find the object by its position (slightly ugly). - qobj = findObject(fset, &info.Info, objposn) - if qobj == nil { - // It really ought to be there; - // we found it once already. - log.Fatalf("object at %s not found in package %s", - objposn, defpkg) - } - - // Object found. - qinfo = info - q.Output(fset, &referrersInitialResult{ - qinfo: qinfo, - obj: qobj, - }) - } - obj := qobj - mu.Unlock() - - // Look for references to the query object. - if obj != nil { - outputUses(q, fset, usesOf(obj, info), info.Pkg) - } - } - - clearInfoFields(info) // save memory - } - - lconf.Load() // ignore error - - if qobj == nil { - log.Fatal("query object not found during reloading") - } - - return nil // success -} - -// findObject returns the object defined at the specified position. -func findObject(fset *token.FileSet, info *types.Info, objposn token.Position) types.Object { - good := func(obj types.Object) bool { - if obj == nil { - return false - } - posn := fset.Position(obj.Pos()) - return posn.Filename == objposn.Filename && posn.Offset == objposn.Offset - } - for _, obj := range info.Defs { - if good(obj) { - return obj - } - } - for _, obj := range info.Implicits { - if good(obj) { - return obj - } - } - return nil -} - -// same reports whether x and y are identical, or both are PkgNames -// that import the same Package. -// -func sameObj(x, y types.Object) bool { - if x == y { - return true - } - if x, ok := x.(*types.PkgName); ok { - if y, ok := y.(*types.PkgName); ok { - return x.Imported() == y.Imported() - } - } - return false -} - -func clearInfoFields(info *loader.PackageInfo) { - // TODO(adonovan): opt: save memory by eliminating unneeded scopes/objects. - // (Requires go/types change for Go 1.7.) - // info.Pkg.Scope().ClearChildren() - - // Discard the file ASTs and their accumulated type - // information to save memory. - info.Files = nil - info.Defs = make(map[*ast.Ident]types.Object) - info.Uses = make(map[*ast.Ident]types.Object) - info.Implicits = make(map[ast.Node]types.Object) - - // Also, disable future collection of wholly unneeded - // type information for the package in case there is - // more type-checking to do (augmentation). - info.Types = nil - info.Scopes = nil - info.Selections = nil -} - -// -------- utils -------- - -// An deterministic ordering for token.Pos that doesn't -// depend on the order in which packages were loaded. -func lessPos(fset *token.FileSet, x, y token.Pos) bool { - fx := fset.File(x) - fy := fset.File(y) - if fx != fy { - return fx.Name() < fy.Name() - } - return x < y -} - -type byNamePos struct { - fset *token.FileSet - ids []*ast.Ident -} - -func (p byNamePos) Len() int { return len(p.ids) } -func (p byNamePos) Swap(i, j int) { p.ids[i], p.ids[j] = p.ids[j], p.ids[i] } -func (p byNamePos) Less(i, j int) bool { - return lessPos(p.fset, p.ids[i].NamePos, p.ids[j].NamePos) -} - -// referrersInitialResult is the initial result of a "referrers" query. -type referrersInitialResult struct { - qinfo *loader.PackageInfo - obj types.Object // object it denotes -} - -func (r *referrersInitialResult) PrintPlain(printf printfFunc) { - printf(r.obj, "references to %s", - types.ObjectString(r.obj, types.RelativeTo(r.qinfo.Pkg))) -} - -func (r *referrersInitialResult) JSON(fset *token.FileSet) []byte { - var objpos string - if pos := r.obj.Pos(); pos.IsValid() { - objpos = fset.Position(pos).String() - } - return toJSON(&serial.ReferrersInitial{ - Desc: r.obj.String(), - ObjPos: objpos, - }) -} - -// referrersPackageResult is the streaming result for one package of a "referrers" query. -type referrersPackageResult struct { - pkg *types.Package - build *build.Context - fset *token.FileSet - refs []*ast.Ident // set of all other references to it -} - -// forEachRef calls f(id, text) for id in r.refs, in order. -// Text is the text of the line on which id appears. -func (r *referrersPackageResult) foreachRef(f func(id *ast.Ident, text string)) { - // Show referring lines, like grep. - type fileinfo struct { - refs []*ast.Ident - linenums []int // line number of refs[i] - data chan interface{} // file contents or error - } - var fileinfos []*fileinfo - fileinfosByName := make(map[string]*fileinfo) - - // First pass: start the file reads concurrently. - sema := make(chan struct{}, 20) // counting semaphore to limit I/O concurrency - for _, ref := range r.refs { - posn := r.fset.Position(ref.Pos()) - fi := fileinfosByName[posn.Filename] - if fi == nil { - fi = &fileinfo{data: make(chan interface{})} - fileinfosByName[posn.Filename] = fi - fileinfos = append(fileinfos, fi) - - // First request for this file: - // start asynchronous read. - go func() { - sema <- struct{}{} // acquire token - content, err := readFile(r.build, posn.Filename) - <-sema // release token - if err != nil { - fi.data <- err - } else { - fi.data <- content - } - }() - } - fi.refs = append(fi.refs, ref) - fi.linenums = append(fi.linenums, posn.Line) - } - - // Second pass: print refs in original order. - // One line may have several refs at different columns. - for _, fi := range fileinfos { - v := <-fi.data // wait for I/O completion - - // Print one item for all refs in a file that could not - // be loaded (perhaps due to //line directives). - if err, ok := v.(error); ok { - var suffix string - if more := len(fi.refs) - 1; more > 0 { - suffix = fmt.Sprintf(" (+ %d more refs in this file)", more) - } - f(fi.refs[0], err.Error()+suffix) - continue - } - - lines := bytes.Split(v.([]byte), []byte("\n")) - for i, ref := range fi.refs { - f(ref, string(lines[fi.linenums[i]-1])) - } - } -} - -// readFile is like ioutil.ReadFile, but -// it goes through the virtualized build.Context. -func readFile(ctxt *build.Context, filename string) ([]byte, error) { - rc, err := buildutil.OpenFile(ctxt, filename) - if err != nil { - return nil, err - } - defer rc.Close() - var buf bytes.Buffer - if _, err := io.Copy(&buf, rc); err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func (r *referrersPackageResult) PrintPlain(printf printfFunc) { - r.foreachRef(func(id *ast.Ident, text string) { - printf(id, "%s", text) - }) -} - -func (r *referrersPackageResult) JSON(fset *token.FileSet) []byte { - refs := serial.ReferrersPackage{Package: r.pkg.Path()} - r.foreachRef(func(id *ast.Ident, text string) { - refs.Refs = append(refs.Refs, serial.Ref{ - Pos: fset.Position(id.NamePos).String(), - Text: text, - }) - }) - return toJSON(refs) -} diff --git a/cmd/guru/testdata/src/alias/main.go b/cmd/guru/testdata/src/alias/main.go new file mode 100644 index 00000000..164d1312 --- /dev/null +++ b/cmd/guru/testdata/src/alias/main.go @@ -0,0 +1,25 @@ +package alias // @describe pkg "alias" + +// +build go1.8 + +// Test describe queries on Go 1.8 aliases. +// See go.tools/guru/guru_test.go for explanation. +// See alias.golden for expected query results. + +import ( + "aliaslib" + "nosuchpkg" +) + +var bad1 => nopkg.NoVar// @describe bad1 "bad1" +var bad2 => nosuchpkg.NoVar// @describe bad2 "bad2" + +var v_ => aliaslib.V // @describe v "v_" +type t_ => aliaslib.T // @describe t "t_" +const c_ => aliaslib.C // @describe c "c_" +func f_ => aliaslib.F // @describe f "f_" + +type S1 struct { aliaslib.T } // @describe s1-field "T" +type S2 struct { t_ } // @describe s2-field "t_" + +var x t_ // @describe var-x "t_" diff --git a/cmd/guru/testdata/src/alias/main.golden b/cmd/guru/testdata/src/alias/main.golden new file mode 100644 index 00000000..fcd7c39e --- /dev/null +++ b/cmd/guru/testdata/src/alias/main.golden @@ -0,0 +1,54 @@ +-------- @describe pkg -------- +definition of package "alias" + type S1 struct{aliaslib.T} + method (S1) Method(x *int) *int + type S2 struct{aliaslib.T} + method (S2) Method(x *int) *int + alias bad1 => ? + alias bad2 => ? + const c_ => aliaslib.C + func f_ => aliaslib.F + type t_ => aliaslib.T + var v_ => aliaslib.V + var x aliaslib.T + +-------- @describe bad1 -------- +identifier + +-------- @describe bad2 -------- +identifier + +-------- @describe v -------- +definition of var alias v_ int + +-------- @describe t -------- +alias of type aliaslib.T (size 8, align 8) +defined as int +Methods: + method (T) Method(x *int) *int + +-------- @describe c -------- +definition of const alias c_ untyped int of value 3 + +-------- @describe f -------- +definition of func alias f_ func() + +-------- @describe s1-field -------- +reference to field T aliaslib.T +defined here +Methods: + method (T) Method(x *int) *int + +-------- @describe s2-field -------- +type struct{aliaslib.T} (size 8, align 8) +Methods: + method (struct{T}) Method(x *int) *int +Fields: + t_ aliaslib.T + +-------- @describe var-x -------- +alias of type aliaslib.T (size 8, align 8) +defined as int +Methods: + method (T) Method(x *int) *int + diff --git a/cmd/guru/testdata/src/aliaslib/aliaslib.go b/cmd/guru/testdata/src/aliaslib/aliaslib.go new file mode 100644 index 00000000..4f07a427 --- /dev/null +++ b/cmd/guru/testdata/src/aliaslib/aliaslib.go @@ -0,0 +1,11 @@ +package aliaslib + +type T int + +func (T) Method(x *int) *int + +func F() + +const C = 3 + +var V = 0 diff --git a/cmd/guru/testdata/src/describe/main.golden b/cmd/guru/testdata/src/describe/main.golden index 4ddb680c..940ef574 100644 --- a/cmd/guru/testdata/src/describe/main.golden +++ b/cmd/guru/testdata/src/describe/main.golden @@ -30,16 +30,16 @@ import of package "unsafe" reference to built-in type float64 -------- @describe const-ref-iota -------- -reference to const iota untyped int of constant value 0 +reference to const iota untyped int of value 0 -------- @describe const-def-pi -------- -definition of const pi untyped float +definition of const pi untyped float of value 3.141 -------- @describe const-def-pie -------- -definition of const pie cake +definition of const pie cake of value 3.141 -------- @describe const-ref-pi -------- -reference to const pi untyped float of constant value 3.141 +reference to const pi untyped float of value 3.141 defined here -------- @describe func-def-main -------- @@ -143,13 +143,13 @@ Methods: method (I) f() -------- @describe const-local-pi -------- -definition of const localpi untyped float +definition of const localpi untyped float of value 3.141 -------- @describe const-local-pie -------- -definition of const localpie cake +definition of const localpie cake of value 3.141 -------- @describe const-ref-localpi -------- -reference to const localpi untyped float of constant value 3.141 +reference to const localpi untyped float of value 3.141 defined here -------- @describe type-def-T -------- @@ -162,10 +162,10 @@ defined as int No methods. -------- @describe const-expr -------- -binary * operation of constant value 6 +binary * operation of value 6 -------- @describe const-expr2 -------- -binary - operation of constant value -2 +binary - operation of value -2 -------- @describe map-lookup,ok -------- index expression of type (*int, bool) diff --git a/cmd/guru/testdata/src/imports/main.golden b/cmd/guru/testdata/src/imports/main.golden index 80355156..89cfb2b6 100644 --- a/cmd/guru/testdata/src/imports/main.golden +++ b/cmd/guru/testdata/src/imports/main.golden @@ -19,7 +19,7 @@ import of package "lib" var Var int -------- @describe ref-const -------- -reference to const lib.Const untyped int +reference to const lib.Const untyped int of value 3 defined here -------- @describe ref-func --------