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>
This commit is contained in:
parent
50193ec1c5
commit
d15da3015c
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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,8 +367,11 @@ 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)
|
||||
}
|
||||
|
||||
// Describe the expression.
|
||||
if r.obj != nil {
|
||||
switch obj := r.obj.(type) {
|
||||
case *types.Func:
|
||||
if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
|
||||
|
@ -370,10 +381,10 @@ func (r *describeValueResult) PrintPlain(printf printfFunc) {
|
|||
prefix = "method "
|
||||
}
|
||||
}
|
||||
case *types.Alias:
|
||||
prefix = tokenOf(obj.Orig()) + " "
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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_"
|
|
@ -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
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package aliaslib
|
||||
|
||||
type T int
|
||||
|
||||
func (T) Method(x *int) *int
|
||||
|
||||
func F()
|
||||
|
||||
const C = 3
|
||||
|
||||
var V = 0
|
|
@ -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)
|
||||
|
|
|
@ -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 --------
|
||||
|
|
Loading…
Reference in New Issue