mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
cmd/guru: revert support for Go 1.8 aliases
Change-Id: I8ba368d5036be47b6d64e3576f366eb0301d2d5b Reviewed-on: https://go-review.googlesource.com/32831 Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
188d338f0b
commit
0e041570eb
@ -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 (
|
||||
|
@ -1,267 +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 (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/tools/cmd/guru/serial"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/pointer"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/ssa/ssautil"
|
||||
)
|
||||
|
||||
// Callees reports the possible callees of the function call site
|
||||
// identified by the specified source location.
|
||||
func callees(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
|
||||
if err := setPTAScope(&lconf, q.Scope); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // needs exact pos
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Determine the enclosing call for the specified position.
|
||||
var e *ast.CallExpr
|
||||
for _, n := range qpos.path {
|
||||
if e, _ = n.(*ast.CallExpr); e != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if e == nil {
|
||||
return fmt.Errorf("there is no function call here")
|
||||
}
|
||||
// TODO(adonovan): issue an error if the call is "too far
|
||||
// away" from the current selection, as this most likely is
|
||||
// not what the user intended.
|
||||
|
||||
// Reject type conversions.
|
||||
if qpos.info.Types[e.Fun].IsType() {
|
||||
return fmt.Errorf("this is a type conversion, not a function call")
|
||||
}
|
||||
|
||||
// Deal with obviously static calls before constructing SSA form.
|
||||
// Some static calls may yet require SSA construction,
|
||||
// e.g. f := func(){}; f().
|
||||
switch funexpr := unparen(e.Fun).(type) {
|
||||
case *ast.Ident:
|
||||
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())
|
||||
case *types.Func:
|
||||
// This is a static function call
|
||||
q.Output(lprog.Fset, &calleesTypesResult{
|
||||
site: e,
|
||||
callee: obj,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
sel := qpos.info.Selections[funexpr]
|
||||
if sel == nil {
|
||||
// qualified identifier.
|
||||
// May refer to top level function variable
|
||||
// or to top level function.
|
||||
callee := original(qpos.info.Uses[funexpr.Sel])
|
||||
if obj, ok := callee.(*types.Func); ok {
|
||||
q.Output(lprog.Fset, &calleesTypesResult{
|
||||
site: e,
|
||||
callee: obj,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
} else if sel.Kind() == types.MethodVal {
|
||||
// Inspect the receiver type of the selected method.
|
||||
// If it is concrete, the call is statically dispatched.
|
||||
// (Due to implicit field selections, it is not enough to look
|
||||
// at sel.Recv(), the type of the actual receiver expression.)
|
||||
method := sel.Obj().(*types.Func)
|
||||
recvtype := method.Type().(*types.Signature).Recv().Type()
|
||||
if !types.IsInterface(recvtype) {
|
||||
// static method call
|
||||
q.Output(lprog.Fset, &calleesTypesResult{
|
||||
site: e,
|
||||
callee: method,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug)
|
||||
|
||||
ptaConfig, err := setupPTA(prog, lprog, q.PTALog, q.Reflection)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkg := prog.Package(qpos.info.Pkg)
|
||||
if pkg == nil {
|
||||
return fmt.Errorf("no SSA package")
|
||||
}
|
||||
|
||||
// Defer SSA construction till after errors are reported.
|
||||
prog.Build()
|
||||
|
||||
// Ascertain calling function and call site.
|
||||
callerFn := ssa.EnclosingFunction(pkg, qpos.path)
|
||||
if callerFn == nil {
|
||||
return fmt.Errorf("no SSA function built for this location (dead code?)")
|
||||
}
|
||||
|
||||
// Find the call site.
|
||||
site, err := findCallSite(callerFn, e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
funcs, err := findCallees(ptaConfig, site)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.Output(lprog.Fset, &calleesSSAResult{
|
||||
site: site,
|
||||
funcs: funcs,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func findCallSite(fn *ssa.Function, call *ast.CallExpr) (ssa.CallInstruction, error) {
|
||||
instr, _ := fn.ValueForExpr(call)
|
||||
callInstr, _ := instr.(ssa.CallInstruction)
|
||||
if instr == nil {
|
||||
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
||||
}
|
||||
return callInstr, nil
|
||||
}
|
||||
|
||||
func findCallees(conf *pointer.Config, site ssa.CallInstruction) ([]*ssa.Function, error) {
|
||||
// Avoid running the pointer analysis for static calls.
|
||||
if callee := site.Common().StaticCallee(); callee != nil {
|
||||
switch callee.String() {
|
||||
case "runtime.SetFinalizer", "(reflect.Value).Call":
|
||||
// The PTA treats calls to these intrinsics as dynamic.
|
||||
// TODO(adonovan): avoid reliance on PTA internals.
|
||||
|
||||
default:
|
||||
return []*ssa.Function{callee}, nil // singleton
|
||||
}
|
||||
}
|
||||
|
||||
// Dynamic call: use pointer analysis.
|
||||
conf.BuildCallGraph = true
|
||||
cg := ptrAnalysis(conf).CallGraph
|
||||
cg.DeleteSyntheticNodes()
|
||||
|
||||
// Find all call edges from the site.
|
||||
n := cg.Nodes[site.Parent()]
|
||||
if n == nil {
|
||||
return nil, fmt.Errorf("this call site is unreachable in this analysis")
|
||||
}
|
||||
calleesMap := make(map[*ssa.Function]bool)
|
||||
for _, edge := range n.Out {
|
||||
if edge.Site == site {
|
||||
calleesMap[edge.Callee.Func] = true
|
||||
}
|
||||
}
|
||||
|
||||
// De-duplicate and sort.
|
||||
funcs := make([]*ssa.Function, 0, len(calleesMap))
|
||||
for f := range calleesMap {
|
||||
funcs = append(funcs, f)
|
||||
}
|
||||
sort.Sort(byFuncPos(funcs))
|
||||
return funcs, nil
|
||||
}
|
||||
|
||||
type calleesSSAResult struct {
|
||||
site ssa.CallInstruction
|
||||
funcs []*ssa.Function
|
||||
}
|
||||
|
||||
type calleesTypesResult struct {
|
||||
site *ast.CallExpr
|
||||
callee *types.Func
|
||||
}
|
||||
|
||||
func (r *calleesSSAResult) PrintPlain(printf printfFunc) {
|
||||
if len(r.funcs) == 0 {
|
||||
// dynamic call on a provably nil func/interface
|
||||
printf(r.site, "%s on nil value", r.site.Common().Description())
|
||||
} else {
|
||||
printf(r.site, "this %s dispatches to:", r.site.Common().Description())
|
||||
for _, callee := range r.funcs {
|
||||
printf(callee, "\t%s", callee)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *calleesSSAResult) JSON(fset *token.FileSet) []byte {
|
||||
j := &serial.Callees{
|
||||
Pos: fset.Position(r.site.Pos()).String(),
|
||||
Desc: r.site.Common().Description(),
|
||||
}
|
||||
for _, callee := range r.funcs {
|
||||
j.Callees = append(j.Callees, &serial.Callee{
|
||||
Name: callee.String(),
|
||||
Pos: fset.Position(callee.Pos()).String(),
|
||||
})
|
||||
}
|
||||
return toJSON(j)
|
||||
}
|
||||
|
||||
func (r *calleesTypesResult) PrintPlain(printf printfFunc) {
|
||||
printf(r.site, "this static function call dispatches to:")
|
||||
printf(r.callee, "\t%s", r.callee.FullName())
|
||||
}
|
||||
|
||||
func (r *calleesTypesResult) JSON(fset *token.FileSet) []byte {
|
||||
j := &serial.Callees{
|
||||
Pos: fset.Position(r.site.Pos()).String(),
|
||||
Desc: "static function call",
|
||||
}
|
||||
j.Callees = []*serial.Callee{
|
||||
&serial.Callee{
|
||||
Name: r.callee.FullName(),
|
||||
Pos: fset.Position(r.callee.Pos()).String(),
|
||||
},
|
||||
}
|
||||
return toJSON(j)
|
||||
}
|
||||
|
||||
// NB: byFuncPos is not deterministic across packages since it depends on load order.
|
||||
// Use lessPos if the tests need it.
|
||||
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
|
||||
}
|
@ -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 (
|
||||
|
@ -1,211 +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 (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
pathpkg "path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/cmd/guru/serial"
|
||||
"golang.org/x/tools/go/buildutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
// definition reports the location of the definition of an identifier.
|
||||
func definition(q *Query) error {
|
||||
// First try the simple resolution done by parser.
|
||||
// It only works for intra-file references but it is very fast.
|
||||
// (Extending this approach to all the files of the package,
|
||||
// resolved using ast.NewPackage, was not worth the effort.)
|
||||
{
|
||||
qpos, err := fastQueryPos(q.Build, q.Pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ := qpos.path[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return fmt.Errorf("no identifier here")
|
||||
}
|
||||
|
||||
// Did the parser resolve it to a local object?
|
||||
if obj := id.Obj; obj != nil && obj.Pos().IsValid() {
|
||||
q.Output(qpos.fset, &definitionResult{
|
||||
pos: obj.Pos(),
|
||||
descr: fmt.Sprintf("%s %s", obj.Kind, obj.Name),
|
||||
})
|
||||
return nil // success
|
||||
}
|
||||
|
||||
// Qualified identifier?
|
||||
if pkg := packageForQualIdent(qpos.path, id); pkg != "" {
|
||||
srcdir := filepath.Dir(qpos.fset.File(qpos.start).Name())
|
||||
tok, pos, err := findPackageMember(q.Build, qpos.fset, srcdir, pkg, id.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Output(qpos.fset, &definitionResult{
|
||||
pos: pos,
|
||||
descr: fmt.Sprintf("%s %s.%s", tok, pkg, id.Name),
|
||||
})
|
||||
return nil // success
|
||||
}
|
||||
|
||||
// Fall back on the type checker.
|
||||
}
|
||||
|
||||
// Run the type checker.
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
id, _ := qpos.path[0].(*ast.Ident)
|
||||
if id == nil {
|
||||
return fmt.Errorf("no identifier here")
|
||||
}
|
||||
|
||||
// Look up the declaration of this identifier.
|
||||
// If id is an anonymous field declaration,
|
||||
// it is both a use of a type and a def of a field;
|
||||
// prefer the use in that case.
|
||||
obj := qpos.info.Uses[id]
|
||||
if obj == nil {
|
||||
obj = qpos.info.Defs[id]
|
||||
if obj == nil {
|
||||
// Happens for y in "switch y := x.(type)",
|
||||
// and the package declaration,
|
||||
// but I think that's all.
|
||||
return fmt.Errorf("no object for identifier")
|
||||
}
|
||||
}
|
||||
|
||||
if !obj.Pos().IsValid() {
|
||||
return fmt.Errorf("%s is built in", obj.Name())
|
||||
}
|
||||
|
||||
q.Output(lprog.Fset, &definitionResult{
|
||||
pos: obj.Pos(),
|
||||
descr: qpos.objectString(obj),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// packageForQualIdent returns the package p if id is X in a qualified
|
||||
// identifier p.X; it returns "" otherwise.
|
||||
//
|
||||
// Precondition: id is path[0], and the parser did not resolve id to a
|
||||
// local object. For speed, packageForQualIdent assumes that p is a
|
||||
// package iff it is the basename of an import path (and not, say, a
|
||||
// package-level decl in another file or a predeclared identifier).
|
||||
func packageForQualIdent(path []ast.Node, id *ast.Ident) string {
|
||||
if sel, ok := path[1].(*ast.SelectorExpr); ok && sel.Sel == id && ast.IsExported(id.Name) {
|
||||
if pkgid, ok := sel.X.(*ast.Ident); ok && pkgid.Obj == nil {
|
||||
f := path[len(path)-1].(*ast.File)
|
||||
for _, imp := range f.Imports {
|
||||
path, _ := strconv.Unquote(imp.Path.Value)
|
||||
if imp.Name != nil {
|
||||
if imp.Name.Name == pkgid.Name {
|
||||
return path // renaming import
|
||||
}
|
||||
} else if pathpkg.Base(path) == pkgid.Name {
|
||||
return path // ordinary import
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// findPackageMember returns the type and position of the declaration of
|
||||
// pkg.member by loading and parsing the files of that package.
|
||||
// srcdir is the directory in which the import appears.
|
||||
func findPackageMember(ctxt *build.Context, fset *token.FileSet, srcdir, pkg, member string) (token.Token, token.Pos, error) {
|
||||
bp, err := ctxt.Import(pkg, srcdir, 0)
|
||||
if err != nil {
|
||||
return 0, token.NoPos, err // no files for package
|
||||
}
|
||||
|
||||
// TODO(adonovan): opt: parallelize.
|
||||
for _, fname := range bp.GoFiles {
|
||||
filename := filepath.Join(bp.Dir, fname)
|
||||
|
||||
// Parse the file, opening it the file via the build.Context
|
||||
// so that we observe the effects of the -modified flag.
|
||||
f, _ := buildutil.ParseFile(fset, ctxt, nil, ".", filename, parser.Mode(0))
|
||||
if f == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Find a package-level decl called 'member'.
|
||||
for _, decl := range f.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
for _, spec := range decl.Specs {
|
||||
switch spec := spec.(type) {
|
||||
case *ast.ValueSpec:
|
||||
// const or var
|
||||
for _, id := range spec.Names {
|
||||
if id.Name == member {
|
||||
return decl.Tok, id.Pos(), nil
|
||||
}
|
||||
}
|
||||
case *ast.TypeSpec:
|
||||
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:
|
||||
if decl.Recv == nil && decl.Name.Name == member {
|
||||
return token.FUNC, decl.Name.Pos(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, token.NoPos, fmt.Errorf("couldn't find declaration of %s in %q", member, pkg)
|
||||
}
|
||||
|
||||
type definitionResult struct {
|
||||
pos token.Pos // (nonzero) location of definition
|
||||
descr string // description of object it denotes
|
||||
}
|
||||
|
||||
func (r *definitionResult) PrintPlain(printf printfFunc) {
|
||||
printf(r.pos, "defined here as %s", r.descr)
|
||||
}
|
||||
|
||||
func (r *definitionResult) JSON(fset *token.FileSet) []byte {
|
||||
return toJSON(&serial.Definition{
|
||||
Desc: r.descr,
|
||||
ObjPos: fset.Position(r.pos).String(),
|
||||
})
|
||||
}
|
@ -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 (
|
||||
|
@ -1,914 +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"
|
||||
exact "go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/tools/cmd/guru/serial"
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
)
|
||||
|
||||
// describe describes the syntax node denoted by the query position,
|
||||
// including:
|
||||
// - its syntactic category
|
||||
// - the definition of its referent (for identifiers) [now redundant]
|
||||
// - its type, fields, and methods (for an expression or type expression)
|
||||
//
|
||||
func describe(q *Query) error {
|
||||
lconf := loader.Config{Build: q.Build}
|
||||
allowErrors(&lconf)
|
||||
|
||||
if _, err := importQueryPackage(q.Pos, &lconf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load/parse/type-check the program.
|
||||
lprog, err := lconf.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qpos, err := parseQueryPos(lprog, q.Pos, true) // (need exact pos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if false { // debugging
|
||||
fprintf(os.Stderr, lprog.Fset, qpos.path[0], "you selected: %s %s",
|
||||
astutil.NodeDescription(qpos.path[0]), pathToString(qpos.path))
|
||||
}
|
||||
|
||||
var qr QueryResult
|
||||
path, action := findInterestingNode(qpos.info, qpos.path)
|
||||
switch action {
|
||||
case actionExpr:
|
||||
qr, err = describeValue(qpos, path)
|
||||
|
||||
case actionType:
|
||||
qr, err = describeType(qpos, path)
|
||||
|
||||
case actionPackage:
|
||||
qr, err = describePackage(qpos, path)
|
||||
|
||||
case actionStmt:
|
||||
qr, err = describeStmt(qpos, path)
|
||||
|
||||
case actionUnknown:
|
||||
qr = &describeUnknownResult{path[0]}
|
||||
|
||||
default:
|
||||
panic(action) // unreachable
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q.Output(lprog.Fset, qr)
|
||||
return nil
|
||||
}
|
||||
|
||||
type describeUnknownResult struct {
|
||||
node ast.Node
|
||||
}
|
||||
|
||||
func (r *describeUnknownResult) PrintPlain(printf printfFunc) {
|
||||
// Nothing much to say about misc syntax.
|
||||
printf(r.node, "%s", astutil.NodeDescription(r.node))
|
||||
}
|
||||
|
||||
func (r *describeUnknownResult) JSON(fset *token.FileSet) []byte {
|
||||
return toJSON(&serial.Describe{
|
||||
Desc: astutil.NodeDescription(r.node),
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
})
|
||||
}
|
||||
|
||||
type action int
|
||||
|
||||
const (
|
||||
actionUnknown action = iota // None of the below
|
||||
actionExpr // FuncDecl, true Expr or Ident(types.{Const,Var})
|
||||
actionType // type Expr or Ident(types.TypeName).
|
||||
actionStmt // Stmt or Ident(types.Label)
|
||||
actionPackage // Ident(types.Package) or ImportSpec
|
||||
)
|
||||
|
||||
// findInterestingNode classifies the syntax node denoted by path as one of:
|
||||
// - an expression, part of an expression or a reference to a constant
|
||||
// or variable;
|
||||
// - a type, part of a type, or a reference to a named type;
|
||||
// - a statement, part of a statement, or a label referring to a statement;
|
||||
// - part of a package declaration or import spec.
|
||||
// - none of the above.
|
||||
// and returns the most "interesting" associated node, which may be
|
||||
// the same node, an ancestor or a descendent.
|
||||
//
|
||||
func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.Node, action) {
|
||||
// TODO(adonovan): integrate with go/types/stdlib_test.go and
|
||||
// apply this to every AST node we can find to make sure it
|
||||
// doesn't crash.
|
||||
|
||||
// TODO(adonovan): audit for ParenExpr safety, esp. since we
|
||||
// traverse up and down.
|
||||
|
||||
// TODO(adonovan): if the users selects the "." in
|
||||
// "fmt.Fprintf()", they'll get an ambiguous selection error;
|
||||
// we won't even reach here. Can we do better?
|
||||
|
||||
// TODO(adonovan): describing a field within 'type T struct {...}'
|
||||
// describes the (anonymous) struct type and concludes "no methods".
|
||||
// We should ascend to the enclosing type decl, if any.
|
||||
|
||||
for len(path) > 0 {
|
||||
switch n := path[0].(type) {
|
||||
case *ast.GenDecl:
|
||||
if len(n.Specs) == 1 {
|
||||
// Descend to sole {Import,Type,Value}Spec child.
|
||||
path = append([]ast.Node{n.Specs[0]}, path...)
|
||||
continue
|
||||
}
|
||||
return path, actionUnknown // uninteresting
|
||||
|
||||
case *ast.FuncDecl:
|
||||
// Descend to function name.
|
||||
path = append([]ast.Node{n.Name}, path...)
|
||||
continue
|
||||
|
||||
case *ast.ImportSpec:
|
||||
return path, actionPackage
|
||||
|
||||
case *ast.ValueSpec:
|
||||
if len(n.Names) == 1 {
|
||||
// Descend to sole Ident child.
|
||||
path = append([]ast.Node{n.Names[0]}, path...)
|
||||
continue
|
||||
}
|
||||
return path, actionUnknown // uninteresting
|
||||
|
||||
case *ast.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...)
|
||||
continue
|
||||
|
||||
case ast.Stmt:
|
||||
return path, actionStmt
|
||||
|
||||
case *ast.ArrayType,
|
||||
*ast.StructType,
|
||||
*ast.FuncType,
|
||||
*ast.InterfaceType,
|
||||
*ast.MapType,
|
||||
*ast.ChanType:
|
||||
return path, actionType
|
||||
|
||||
case *ast.Comment, *ast.CommentGroup, *ast.File, *ast.KeyValueExpr, *ast.CommClause:
|
||||
return path, actionUnknown // uninteresting
|
||||
|
||||
case *ast.Ellipsis:
|
||||
// Continue to enclosing node.
|
||||
// e.g. [...]T in ArrayType
|
||||
// f(x...) in CallExpr
|
||||
// f(x...T) in FuncType
|
||||
|
||||
case *ast.Field:
|
||||
// TODO(adonovan): this needs more thought,
|
||||
// since fields can be so many things.
|
||||
if len(n.Names) == 1 {
|
||||
// Descend to sole Ident child.
|
||||
path = append([]ast.Node{n.Names[0]}, path...)
|
||||
continue
|
||||
}
|
||||
// Zero names (e.g. anon field in struct)
|
||||
// or multiple field or param names:
|
||||
// continue to enclosing field list.
|
||||
|
||||
case *ast.FieldList:
|
||||
// Continue to enclosing node:
|
||||
// {Struct,Func,Interface}Type or FuncDecl.
|
||||
|
||||
case *ast.BasicLit:
|
||||
if _, ok := path[1].(*ast.ImportSpec); ok {
|
||||
return path[1:], actionPackage
|
||||
}
|
||||
return path, actionExpr
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
// TODO(adonovan): use Selections info directly.
|
||||
if pkginfo.Uses[n.Sel] == nil {
|
||||
// TODO(adonovan): is this reachable?
|
||||
return path, actionUnknown
|
||||
}
|
||||
// Descend to .Sel child.
|
||||
path = append([]ast.Node{n.Sel}, path...)
|
||||
continue
|
||||
|
||||
case *ast.Ident:
|
||||
switch original(pkginfo.ObjectOf(n)).(type) {
|
||||
case *types.PkgName:
|
||||
return path, actionPackage
|
||||
|
||||
case *types.Const:
|
||||
return path, actionExpr
|
||||
|
||||
case *types.Label:
|
||||
return path, actionStmt
|
||||
|
||||
case *types.TypeName:
|
||||
return path, actionType
|
||||
|
||||
case *types.Var:
|
||||
// For x in 'struct {x T}', return struct type, for now.
|
||||
if _, ok := path[1].(*ast.Field); ok {
|
||||
_ = path[2].(*ast.FieldList) // assertion
|
||||
if _, ok := path[3].(*ast.StructType); ok {
|
||||
return path[3:], actionType
|
||||
}
|
||||
}
|
||||
return path, actionExpr
|
||||
|
||||
case *types.Func:
|
||||
return path, actionExpr
|
||||
|
||||
case *types.Builtin:
|
||||
// For reference to built-in function, return enclosing call.
|
||||
path = path[1:] // ascend to enclosing function call
|
||||
continue
|
||||
|
||||
case *types.Nil:
|
||||
return path, actionExpr
|
||||
}
|
||||
|
||||
// No object.
|
||||
switch path[1].(type) {
|
||||
case *ast.SelectorExpr:
|
||||
// Return enclosing selector expression.
|
||||
return path[1:], actionExpr
|
||||
|
||||
case *ast.Field:
|
||||
// TODO(adonovan): test this.
|
||||
// e.g. all f in:
|
||||
// struct { f, g int }
|
||||
// interface { f() }
|
||||
// func (f T) method(f, g int) (f, g bool)
|
||||
//
|
||||
// switch path[3].(type) {
|
||||
// case *ast.FuncDecl:
|
||||
// case *ast.StructType:
|
||||
// case *ast.InterfaceType:
|
||||
// }
|
||||
//
|
||||
// return path[1:], actionExpr
|
||||
//
|
||||
// Unclear what to do with these.
|
||||
// Struct.Fields -- field
|
||||
// Interface.Methods -- field
|
||||
// FuncType.{Params.Results} -- actionExpr
|
||||
// FuncDecl.Recv -- actionExpr
|
||||
|
||||
case *ast.File:
|
||||
// 'package foo'
|
||||
return path, actionPackage
|
||||
|
||||
case *ast.ImportSpec:
|
||||
return path[1:], actionPackage
|
||||
|
||||
default:
|
||||
// e.g. blank identifier
|
||||
// or y in "switch y := x.(type)"
|
||||
// or code in a _test.go file that's not part of the package.
|
||||
return path, actionUnknown
|
||||
}
|
||||
|
||||
case *ast.StarExpr:
|
||||
if pkginfo.Types[n].IsType() {
|
||||
return path, actionType
|
||||
}
|
||||
return path, actionExpr
|
||||
|
||||
case ast.Expr:
|
||||
// All Expr but {BasicLit,Ident,StarExpr} are
|
||||
// "true" expressions that evaluate to a value.
|
||||
return path, actionExpr
|
||||
}
|
||||
|
||||
// Ascend to parent.
|
||||
path = path[1:]
|
||||
}
|
||||
|
||||
return nil, actionUnknown // unreachable
|
||||
}
|
||||
|
||||
func describeValue(qpos *queryPos, path []ast.Node) (*describeValueResult, error) {
|
||||
var expr ast.Expr
|
||||
var obj types.Object
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ValueSpec:
|
||||
// ambiguous ValueSpec containing multiple names
|
||||
return nil, fmt.Errorf("multiple value specification")
|
||||
case *ast.Ident:
|
||||
obj = qpos.info.ObjectOf(n)
|
||||
expr = n
|
||||
case ast.Expr:
|
||||
expr = n
|
||||
default:
|
||||
// TODO(adonovan): is this reachable?
|
||||
return nil, fmt.Errorf("unexpected AST for expr: %T", n)
|
||||
}
|
||||
|
||||
t := qpos.info.TypeOf(expr)
|
||||
if t == nil {
|
||||
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,
|
||||
expr: expr,
|
||||
typ: t,
|
||||
constVal: constVal,
|
||||
obj: obj,
|
||||
methods: accessibleMethods(t, qpos.info.Pkg),
|
||||
fields: accessibleFields(t, qpos.info.Pkg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type describeValueResult struct {
|
||||
qpos *queryPos
|
||||
expr ast.Expr // query node
|
||||
typ types.Type // type of expression
|
||||
constVal exact.Value // value of expression, if constant
|
||||
obj types.Object // var/func/const object, if expr was Ident, or alias to same
|
||||
methods []*types.Selection
|
||||
fields []describeField
|
||||
}
|
||||
|
||||
func (r *describeValueResult) PrintPlain(printf printfFunc) {
|
||||
var prefix, suffix string
|
||||
if r.constVal != nil {
|
||||
suffix = fmt.Sprintf(" of value %s", r.constVal)
|
||||
}
|
||||
|
||||
// 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)
|
||||
} else {
|
||||
// referring ident
|
||||
printf(r.expr, "reference to %s%s%s", prefix, r.qpos.objectString(r.obj), suffix)
|
||||
if def := r.obj.Pos(); def != token.NoPos {
|
||||
printf(def, "defined here")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
desc := astutil.NodeDescription(r.expr)
|
||||
if suffix != "" {
|
||||
// constant expression
|
||||
printf(r.expr, "%s%s", desc, suffix)
|
||||
} else {
|
||||
// non-constant expression
|
||||
printf(r.expr, "%s of type %s", desc, r.qpos.typeString(r.typ))
|
||||
}
|
||||
}
|
||||
|
||||
printMethods(printf, r.expr, r.methods)
|
||||
printFields(printf, r.expr, r.fields)
|
||||
}
|
||||
|
||||
func (r *describeValueResult) JSON(fset *token.FileSet) []byte {
|
||||
var value, objpos string
|
||||
if r.constVal != nil {
|
||||
value = r.constVal.String()
|
||||
}
|
||||
if r.obj != nil {
|
||||
objpos = fset.Position(r.obj.Pos()).String()
|
||||
}
|
||||
|
||||
return toJSON(&serial.Describe{
|
||||
Desc: astutil.NodeDescription(r.expr),
|
||||
Pos: fset.Position(r.expr.Pos()).String(),
|
||||
Detail: "value",
|
||||
Value: &serial.DescribeValue{
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
Value: value,
|
||||
ObjPos: objpos,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ---- TYPE ------------------------------------------------------------
|
||||
|
||||
func describeType(qpos *queryPos, path []ast.Node) (*describeTypeResult, error) {
|
||||
var description string
|
||||
var t types.Type
|
||||
switch n := path[0].(type) {
|
||||
case *ast.Ident:
|
||||
t = qpos.info.TypeOf(n)
|
||||
switch t := t.(type) {
|
||||
case *types.Basic:
|
||||
description = "reference to built-in "
|
||||
|
||||
case *types.Named:
|
||||
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 "
|
||||
}
|
||||
}
|
||||
|
||||
case ast.Expr:
|
||||
t = qpos.info.TypeOf(n)
|
||||
|
||||
default:
|
||||
// Unreachable?
|
||||
return nil, fmt.Errorf("unexpected AST for type: %T", n)
|
||||
}
|
||||
|
||||
description = description + "type " + qpos.typeString(t)
|
||||
|
||||
// Show sizes for structs and named types (it's fairly obvious for others).
|
||||
switch t.(type) {
|
||||
case *types.Named, *types.Struct:
|
||||
szs := types.StdSizes{WordSize: 8, MaxAlign: 8} // assume amd64
|
||||
description = fmt.Sprintf("%s (size %d, align %d)", description,
|
||||
szs.Sizeof(t), szs.Alignof(t))
|
||||
}
|
||||
|
||||
return &describeTypeResult{
|
||||
qpos: qpos,
|
||||
node: path[0],
|
||||
description: description,
|
||||
typ: t,
|
||||
methods: accessibleMethods(t, qpos.info.Pkg),
|
||||
fields: accessibleFields(t, qpos.info.Pkg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type describeTypeResult struct {
|
||||
qpos *queryPos
|
||||
node ast.Node
|
||||
description string
|
||||
typ types.Type
|
||||
methods []*types.Selection
|
||||
fields []describeField
|
||||
}
|
||||
|
||||
type describeField struct {
|
||||
implicits []*types.Named
|
||||
field *types.Var
|
||||
}
|
||||
|
||||
func printMethods(printf printfFunc, node ast.Node, methods []*types.Selection) {
|
||||
if len(methods) > 0 {
|
||||
printf(node, "Methods:")
|
||||
}
|
||||
for _, meth := range methods {
|
||||
// Print the method type relative to the package
|
||||
// in which it was defined, not the query package,
|
||||
printf(meth.Obj(), "\t%s",
|
||||
types.SelectionString(meth, types.RelativeTo(meth.Obj().Pkg())))
|
||||
}
|
||||
}
|
||||
|
||||
func printFields(printf printfFunc, node ast.Node, fields []describeField) {
|
||||
if len(fields) > 0 {
|
||||
printf(node, "Fields:")
|
||||
}
|
||||
|
||||
// Align the names and the types (requires two passes).
|
||||
var width int
|
||||
var names []string
|
||||
for _, f := range fields {
|
||||
var buf bytes.Buffer
|
||||
for _, fld := range f.implicits {
|
||||
buf.WriteString(fld.Obj().Name())
|
||||
buf.WriteByte('.')
|
||||
}
|
||||
buf.WriteString(f.field.Name())
|
||||
name := buf.String()
|
||||
if n := utf8.RuneCountInString(name); n > width {
|
||||
width = n
|
||||
}
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
for i, f := range fields {
|
||||
// Print the field type relative to the package
|
||||
// in which it was defined, not the query package,
|
||||
printf(f.field, "\t%*s %s", -width, names[i],
|
||||
types.TypeString(f.field.Type(), types.RelativeTo(f.field.Pkg())))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *describeTypeResult) PrintPlain(printf printfFunc) {
|
||||
printf(r.node, "%s", r.description)
|
||||
|
||||
// Show the underlying type for a reference to a named type.
|
||||
if nt, ok := r.typ.(*types.Named); ok && r.node.Pos() != nt.Obj().Pos() {
|
||||
// TODO(adonovan): improve display of complex struct/interface types.
|
||||
printf(nt.Obj(), "defined as %s", r.qpos.typeString(nt.Underlying()))
|
||||
}
|
||||
|
||||
printMethods(printf, r.node, r.methods)
|
||||
if len(r.methods) == 0 {
|
||||
// Only report null result for type kinds
|
||||
// capable of bearing methods.
|
||||
switch r.typ.(type) {
|
||||
case *types.Interface, *types.Struct, *types.Named:
|
||||
printf(r.node, "No methods.")
|
||||
}
|
||||
}
|
||||
|
||||
printFields(printf, r.node, r.fields)
|
||||
}
|
||||
|
||||
func (r *describeTypeResult) JSON(fset *token.FileSet) []byte {
|
||||
var namePos, nameDef string
|
||||
if nt, ok := r.typ.(*types.Named); ok {
|
||||
namePos = fset.Position(nt.Obj().Pos()).String()
|
||||
nameDef = nt.Underlying().String()
|
||||
}
|
||||
return toJSON(&serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "type",
|
||||
Type: &serial.DescribeType{
|
||||
Type: r.qpos.typeString(r.typ),
|
||||
NamePos: namePos,
|
||||
NameDef: nameDef,
|
||||
Methods: methodsToSerial(r.qpos.info.Pkg, r.methods, fset),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ---- PACKAGE ------------------------------------------------------------
|
||||
|
||||
func describePackage(qpos *queryPos, path []ast.Node) (*describePackageResult, error) {
|
||||
var description string
|
||||
var pkg *types.Package
|
||||
switch n := path[0].(type) {
|
||||
case *ast.ImportSpec:
|
||||
var obj types.Object
|
||||
if n.Name != nil {
|
||||
obj = qpos.info.Defs[n.Name]
|
||||
} else {
|
||||
obj = qpos.info.Implicits[n]
|
||||
}
|
||||
pkgname, _ := obj.(*types.PkgName)
|
||||
if pkgname == nil {
|
||||
return nil, fmt.Errorf("can't import package %s", n.Path.Value)
|
||||
}
|
||||
pkg = pkgname.Imported()
|
||||
description = fmt.Sprintf("import of package %q", pkg.Path())
|
||||
|
||||
case *ast.Ident:
|
||||
if _, isDef := path[1].(*ast.File); isDef {
|
||||
// e.g. package id
|
||||
pkg = qpos.info.Pkg
|
||||
description = fmt.Sprintf("definition of package %q", pkg.Path())
|
||||
} else {
|
||||
// e.g. import id "..."
|
||||
// or id.F()
|
||||
pkg = qpos.info.ObjectOf(n).(*types.PkgName).Imported()
|
||||
description = fmt.Sprintf("reference to package %q", pkg.Path())
|
||||
}
|
||||
|
||||
default:
|
||||
// Unreachable?
|
||||
return nil, fmt.Errorf("unexpected AST for package: %T", n)
|
||||
}
|
||||
|
||||
var members []*describeMember
|
||||
// NB: "unsafe" has no types.Package
|
||||
if pkg != nil {
|
||||
// Enumerate the accessible package members
|
||||
// in lexicographic order.
|
||||
for _, name := range pkg.Scope().Names() {
|
||||
if pkg == qpos.info.Pkg || ast.IsExported(name) {
|
||||
mem := pkg.Scope().Lookup(name)
|
||||
var methods []*types.Selection
|
||||
if mem, ok := mem.(*types.TypeName); ok {
|
||||
methods = accessibleMethods(mem.Type(), qpos.info.Pkg)
|
||||
}
|
||||
members = append(members, &describeMember{
|
||||
mem,
|
||||
methods,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &describePackageResult{qpos.fset, path[0], description, pkg, members}, nil
|
||||
}
|
||||
|
||||
type describePackageResult struct {
|
||||
fset *token.FileSet
|
||||
node ast.Node
|
||||
description string
|
||||
pkg *types.Package
|
||||
members []*describeMember // in lexicographic name order
|
||||
}
|
||||
|
||||
type describeMember struct {
|
||||
obj types.Object
|
||||
methods []*types.Selection // in types.MethodSet order
|
||||
}
|
||||
|
||||
func (r *describePackageResult) PrintPlain(printf printfFunc) {
|
||||
printf(r.node, "%s", r.description)
|
||||
|
||||
// Compute max width of name "column".
|
||||
maxname := 0
|
||||
for _, mem := range r.members {
|
||||
if l := len(mem.obj.Name()); l > maxname {
|
||||
maxname = l
|
||||
}
|
||||
}
|
||||
|
||||
for _, mem := range r.members {
|
||||
printf(mem.obj, "\t%s", formatMember(mem.obj, maxname))
|
||||
for _, meth := range mem.methods {
|
||||
printf(meth.Obj(), "\t\t%s", types.SelectionString(meth, types.RelativeTo(r.pkg)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func formatMember(obj types.Object, maxname int) string {
|
||||
qualifier := types.RelativeTo(obj.Pkg())
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%-5s %-*s", tokenOf(obj), maxname, obj.Name())
|
||||
switch obj := obj.(type) {
|
||||
case *types.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), obj.Val())
|
||||
|
||||
case *types.Func:
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
||||
|
||||
case *types.TypeName:
|
||||
// Abbreviate long aggregate type names.
|
||||
var abbrev string
|
||||
switch t := obj.Type().Underlying().(type) {
|
||||
case *types.Interface:
|
||||
if t.NumMethods() > 1 {
|
||||
abbrev = "interface{...}"
|
||||
}
|
||||
case *types.Struct:
|
||||
if t.NumFields() > 1 {
|
||||
abbrev = "struct{...}"
|
||||
}
|
||||
}
|
||||
if abbrev == "" {
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type().Underlying(), qualifier))
|
||||
} else {
|
||||
fmt.Fprintf(&buf, " %s", abbrev)
|
||||
}
|
||||
|
||||
case *types.Var:
|
||||
fmt.Fprintf(&buf, " %s", types.TypeString(obj.Type(), qualifier))
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (r *describePackageResult) JSON(fset *token.FileSet) []byte {
|
||||
var members []*serial.DescribeMember
|
||||
for _, mem := range r.members {
|
||||
typ := mem.obj.Type()
|
||||
var val string
|
||||
switch mem := mem.obj.(type) {
|
||||
case *types.Const:
|
||||
val = mem.Val().String()
|
||||
case *types.TypeName:
|
||||
typ = typ.Underlying()
|
||||
}
|
||||
members = append(members, &serial.DescribeMember{
|
||||
Name: mem.obj.Name(),
|
||||
Type: typ.String(),
|
||||
Value: val,
|
||||
Pos: fset.Position(mem.obj.Pos()).String(),
|
||||
Kind: tokenOf(mem.obj),
|
||||
Methods: methodsToSerial(r.pkg, mem.methods, fset),
|
||||
})
|
||||
}
|
||||
return toJSON(&serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "package",
|
||||
Package: &serial.DescribePackage{
|
||||
Path: r.pkg.Path(),
|
||||
Members: members,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func tokenOf(o types.Object) string {
|
||||
switch o := o.(type) {
|
||||
case *types.Func:
|
||||
return "func"
|
||||
case *types.Var:
|
||||
return "var"
|
||||
case *types.TypeName:
|
||||
return "type"
|
||||
case *types.Const:
|
||||
return "const"
|
||||
case *types.PkgName:
|
||||
return "package"
|
||||
case *types.Builtin:
|
||||
return "builtin" // e.g. when describing package "unsafe"
|
||||
case *types.Nil:
|
||||
return "nil"
|
||||
case *types.Label:
|
||||
return "label"
|
||||
case *types.Alias:
|
||||
if o.Orig() == nil {
|
||||
return "alias"
|
||||
}
|
||||
return tokenOf(o.Orig())
|
||||
}
|
||||
panic(o)
|
||||
}
|
||||
|
||||
// ---- STATEMENT ------------------------------------------------------------
|
||||
|
||||
func describeStmt(qpos *queryPos, path []ast.Node) (*describeStmtResult, error) {
|
||||
var description string
|
||||
switch n := path[0].(type) {
|
||||
case *ast.Ident:
|
||||
if qpos.info.Defs[n] != nil {
|
||||
description = "labelled statement"
|
||||
} else {
|
||||
description = "reference to labelled statement"
|
||||
}
|
||||
|
||||
default:
|
||||
// Nothing much to say about statements.
|
||||
description = astutil.NodeDescription(n)
|
||||
}
|
||||
return &describeStmtResult{qpos.fset, path[0], description}, nil
|
||||
}
|
||||
|
||||
type describeStmtResult struct {
|
||||
fset *token.FileSet
|
||||
node ast.Node
|
||||
description string
|
||||
}
|
||||
|
||||
func (r *describeStmtResult) PrintPlain(printf printfFunc) {
|
||||
printf(r.node, "%s", r.description)
|
||||
}
|
||||
|
||||
func (r *describeStmtResult) JSON(fset *token.FileSet) []byte {
|
||||
return toJSON(&serial.Describe{
|
||||
Desc: r.description,
|
||||
Pos: fset.Position(r.node.Pos()).String(),
|
||||
Detail: "unknown",
|
||||
})
|
||||
}
|
||||
|
||||
// ------------------- Utilities -------------------
|
||||
|
||||
// pathToString returns a string containing the concrete types of the
|
||||
// nodes in path.
|
||||
func pathToString(path []ast.Node) string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, "[")
|
||||
for i, n := range path {
|
||||
if i > 0 {
|
||||
fmt.Fprint(&buf, " ")
|
||||
}
|
||||
fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast."))
|
||||
}
|
||||
fmt.Fprint(&buf, "]")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func accessibleMethods(t types.Type, from *types.Package) []*types.Selection {
|
||||
var methods []*types.Selection
|
||||
for _, meth := range typeutil.IntuitiveMethodSet(t, nil) {
|
||||
if isAccessibleFrom(meth.Obj(), from) {
|
||||
methods = append(methods, meth)
|
||||
}
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
// accessibleFields returns the set of accessible
|
||||
// field selections on a value of type recv.
|
||||
func accessibleFields(recv types.Type, from *types.Package) []describeField {
|
||||
wantField := func(f *types.Var) bool {
|
||||
if !isAccessibleFrom(f, from) {
|
||||
return false
|
||||
}
|
||||
// Check that the field is not shadowed.
|
||||
obj, _, _ := types.LookupFieldOrMethod(recv, true, f.Pkg(), f.Name())
|
||||
return obj == f
|
||||
}
|
||||
|
||||
var fields []describeField
|
||||
var visit func(t types.Type, stack []*types.Named)
|
||||
visit = func(t types.Type, stack []*types.Named) {
|
||||
tStruct, ok := deref(t).Underlying().(*types.Struct)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
fieldloop:
|
||||
for i := 0; i < tStruct.NumFields(); i++ {
|
||||
f := tStruct.Field(i)
|
||||
|
||||
// Handle recursion through anonymous fields.
|
||||
if f.Anonymous() {
|
||||
tf := f.Type()
|
||||
if ptr, ok := tf.(*types.Pointer); ok {
|
||||
tf = ptr.Elem()
|
||||
}
|
||||
if named, ok := tf.(*types.Named); ok { // (be defensive)
|
||||
// If we've already visited this named type
|
||||
// on this path, break the cycle.
|
||||
for _, x := range stack {
|
||||
if x == named {
|
||||
continue fieldloop
|
||||
}
|
||||
}
|
||||
visit(f.Type(), append(stack, named))
|
||||
}
|
||||
}
|
||||
|
||||
// Save accessible fields.
|
||||
if wantField(f) {
|
||||
fields = append(fields, describeField{
|
||||
implicits: append([]*types.Named(nil), stack...),
|
||||
field: f,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(recv, nil)
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
|
||||
return ast.IsExported(obj.Name()) || obj.Pkg() == pkg
|
||||
}
|
||||
|
||||
func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
|
||||
qualifier := types.RelativeTo(this)
|
||||
var jmethods []serial.DescribeMethod
|
||||
for _, meth := range methods {
|
||||
var ser serial.DescribeMethod
|
||||
if meth != nil { // may contain nils when called by implements (on a method)
|
||||
ser = serial.DescribeMethod{
|
||||
Name: types.SelectionString(meth, qualifier),
|
||||
Pos: fset.Position(meth.Obj().Pos()).String(),
|
||||
}
|
||||
}
|
||||
jmethods = append(jmethods, ser)
|
||||
}
|
||||
return jmethods
|
||||
}
|
@ -230,7 +230,6 @@ 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",
|
||||
@ -248,10 +247,6 @@ 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)
|
||||
|
@ -25,12 +25,6 @@ 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}
|
||||
|
25
cmd/guru/testdata/src/alias/main.go
vendored
25
cmd/guru/testdata/src/alias/main.go
vendored
@ -1,25 +0,0 @@
|
||||
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_"
|
Loading…
x
Reference in New Issue
Block a user