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 <gri@golang.org>
diff --git a/cmd/guru/callees18.go b/cmd/guru/callees18.go
index 6e26964..0afac90 100644
--- a/cmd/guru/callees18.go
+++ b/cmd/guru/callees18.go
@@ -64,7 +64,7 @@
// 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 @@
// 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 @@
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 c267f53..b6f7588 100644
--- a/cmd/guru/definition18.go
+++ b/cmd/guru/definition18.go
@@ -177,6 +177,10 @@
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 8e06e4c..6493e90 100644
--- a/cmd/guru/describe.go
+++ b/cmd/guru/describe.go
@@ -334,6 +334,9 @@
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 @@
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 @@
}
}
-// 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 @@
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 6ec38fd..3bc91a0 100644
--- a/cmd/guru/describe18.go
+++ b/cmd/guru/describe18.go
@@ -159,6 +159,11 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
}
-// 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 @@
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 tokenOf(o types.Object) string {
- switch o.(type) {
+ switch o := o.(type) {
case *types.Func:
return "func"
case *types.Var:
@@ -756,6 +765,11 @@
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 62dcd6d..2eb1247 100644
--- a/cmd/guru/guru_test.go
+++ b/cmd/guru/guru_test.go
@@ -230,6 +230,7 @@
"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 @@
// 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 e80b0a6..a5164a3 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 @@
// 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 277f57a..0000000
--- 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 0000000..164d131
--- /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 0000000..fcd7c39
--- /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 0000000..4f07a42
--- /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 4ddb680..940ef57 100644
--- a/cmd/guru/testdata/src/describe/main.golden
+++ b/cmd/guru/testdata/src/describe/main.golden
@@ -30,16 +30,16 @@
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 @@
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 @@
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 8035515..89cfb2b 100644
--- a/cmd/guru/testdata/src/imports/main.golden
+++ b/cmd/guru/testdata/src/imports/main.golden
@@ -19,7 +19,7 @@
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 --------