oracle: when 'implements' is invoked on a method, show related methods, not types.

Fixes #9972

Change-Id: I25b65a64dcc4d551be3db8566783a9d23d410a2e
Reviewed-on: https://go-review.googlesource.com/5860
Reviewed-by: David Crawshaw <crawshaw@golang.org>
This commit is contained in:
Alan Donovan 2015-02-24 18:02:49 -05:00
parent 69db398fe0
commit 264bffc00c
10 changed files with 545 additions and 42 deletions

View File

@ -59,7 +59,7 @@ The mode argument determines the query to perform:
callstack show path from callgraph root to selected function
describe describe selected syntax: definition, methods, etc
freevars show free variables of selection
implements show 'implements' relation for selected type
implements show 'implements' relation for selected type or method
peers show send/receive corresponding to selected channel op
referrers show all refs to entity denoted by selected identifier
what show basic information about the selected syntax node

View File

@ -215,13 +215,6 @@ func findInterestingNode(pkginfo *loader.PackageInfo, path []ast.Node) ([]ast.No
return path, actionExpr
case *types.Func:
// For f in 'interface {f()}', return the interface type, for now.
if _, ok := path[1].(*ast.Field); ok {
_ = path[2].(*ast.FieldList) // assertion
if _, ok := path[3].(*ast.InterfaceType); ok {
return path[3:], actionType
}
}
return path, actionExpr
case *types.Builtin:
@ -737,10 +730,14 @@ func isAccessibleFrom(obj types.Object, pkg *types.Package) bool {
func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
var jmethods []serial.DescribeMethod
for _, meth := range methods {
jmethods = append(jmethods, serial.DescribeMethod{
Name: types.SelectionString(this, meth),
Pos: fset.Position(meth.Obj().Pos()).String(),
})
var ser serial.DescribeMethod
if meth != nil { // may contain nils when called by implements (on a method)
ser = serial.DescribeMethod{
Name: types.SelectionString(this, meth),
Pos: fset.Position(meth.Obj().Pos()).String(),
}
}
jmethods = append(jmethods, ser)
}
return jmethods
}

View File

@ -17,18 +17,35 @@ import (
)
// Implements displays the "implements" relation as it pertains to the
// selected type.
// selected type. If the selection is a method, 'implements' displays
// the corresponding methods of the types that would have been reported
// by an implements query on the receiver type.
//
func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
// Find the selected type.
// TODO(adonovan): fix: make it work on qualified Idents too.
path, action := findInterestingNode(qpos.info, qpos.path)
if action != actionType {
return nil, fmt.Errorf("no type here")
var method *types.Func
var T types.Type // selected type (receiver if method != nil)
switch action {
case actionExpr:
// method?
if id, ok := path[0].(*ast.Ident); ok {
if obj, ok := qpos.info.ObjectOf(id).(*types.Func); ok {
recv := obj.Type().(*types.Signature).Recv()
if recv == nil {
return nil, fmt.Errorf("this function is not a method")
}
method = obj
T = recv.Type()
}
}
case actionType:
T = qpos.info.TypeOf(path[0].(ast.Expr))
}
T := qpos.info.TypeOf(path[0].(ast.Expr))
if T == nil {
return nil, fmt.Errorf("no type here")
return nil, fmt.Errorf("no type or method here")
}
// Find all named types, even local types (which can have
@ -102,52 +119,128 @@ func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
sort.Sort(typesByString(from))
sort.Sort(typesByString(fromPtr))
return &implementsResult{T, pos, to, from, fromPtr}, nil
var toMethod, fromMethod, fromPtrMethod []*types.Selection // contain nils
if method != nil {
for _, t := range to {
toMethod = append(toMethod,
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
}
for _, t := range from {
fromMethod = append(fromMethod,
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
}
for _, t := range fromPtr {
fromPtrMethod = append(fromPtrMethod,
types.NewMethodSet(t).Lookup(method.Pkg(), method.Name()))
}
}
return &implementsResult{qpos, T, pos, to, from, fromPtr, method, toMethod, fromMethod, fromPtrMethod}, nil
}
type implementsResult struct {
qpos *QueryPos
t types.Type // queried type (not necessarily named)
pos interface{} // pos of t (*types.Name or *QueryPos)
to []types.Type // named or ptr-to-named types assignable to interface T
from []types.Type // named interfaces assignable from T
fromPtr []types.Type // named interfaces assignable only from *T
// if a method was queried:
method *types.Func // queried method
toMethod []*types.Selection // method of type to[i], if any
fromMethod []*types.Selection // method of type from[i], if any
fromPtrMethod []*types.Selection // method of type fromPtrMethod[i], if any
}
func (r *implementsResult) display(printf printfFunc) {
relation := "is implemented by"
meth := func(sel *types.Selection) {
if sel != nil {
printf(sel.Obj(), "\t%s method (%s).%s",
relation, r.qpos.TypeString(sel.Recv()), sel.Obj().Name())
}
}
if isInterface(r.t) {
if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset
printf(r.pos, "empty interface type %s", r.t)
return
}
printf(r.pos, "interface type %s", r.t)
// Show concrete types first; use two passes.
for _, sub := range r.to {
if r.method == nil {
printf(r.pos, "interface type %s", r.t)
} else {
printf(r.method, "abstract method %s", r.qpos.ObjectString(r.method))
}
// Show concrete types (or methods) first; use two passes.
for i, sub := range r.to {
if !isInterface(sub) {
printf(deref(sub).(*types.Named).Obj(), "\tis implemented by %s type %s",
typeKind(sub), sub)
if r.method == nil {
printf(deref(sub).(*types.Named).Obj(), "\t%s %s type %s",
relation, typeKind(sub), sub)
} else {
meth(r.toMethod[i])
}
}
}
for _, sub := range r.to {
for i, sub := range r.to {
if isInterface(sub) {
printf(deref(sub).(*types.Named).Obj(), "\tis implemented by %s type %s", typeKind(sub), sub)
if r.method == nil {
printf(sub.(*types.Named).Obj(), "\t%s %s type %s",
relation, typeKind(sub), sub)
} else {
meth(r.toMethod[i])
}
}
}
for _, super := range r.from {
printf(super.(*types.Named).Obj(), "\timplements %s", super)
relation = "implements"
for i, super := range r.from {
if r.method == nil {
printf(super.(*types.Named).Obj(), "\t%s %s", relation, super)
} else {
meth(r.fromMethod[i])
}
}
} else {
relation = "implements"
if r.from != nil {
printf(r.pos, "%s type %s", typeKind(r.t), r.t)
for _, super := range r.from {
printf(super.(*types.Named).Obj(), "\timplements %s", super)
if r.method == nil {
printf(r.pos, "%s type %s", typeKind(r.t), r.t)
} else {
printf(r.method, "concrete method %s",
r.qpos.ObjectString(r.method))
}
for i, super := range r.from {
if r.method == nil {
printf(super.(*types.Named).Obj(), "\t%s %s",
relation, super)
} else {
meth(r.fromMethod[i])
}
}
}
if r.fromPtr != nil {
printf(r.pos, "pointer type *%s", r.t)
for _, psuper := range r.fromPtr {
printf(psuper.(*types.Named).Obj(), "\timplements %s", psuper)
if r.method == nil {
printf(r.pos, "pointer type *%s", r.t)
} else {
// TODO(adonovan): de-dup (C).f and (*C).f implementing (I).f.
printf(r.method, "concrete method %s",
r.qpos.ObjectString(r.method))
}
for i, psuper := range r.fromPtr {
if r.method == nil {
printf(psuper.(*types.Named).Obj(), "\t%s %s",
relation, psuper)
} else {
meth(r.fromPtrMethod[i])
}
}
} else if r.from == nil {
printf(r.pos, "%s type %s implements only interface{}", typeKind(r.t), r.t)
@ -157,10 +250,19 @@ func (r *implementsResult) display(printf printfFunc) {
func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) {
res.Implements = &serial.Implements{
T: makeImplementsType(r.t, fset),
AssignableTo: makeImplementsTypes(r.to, fset),
AssignableFrom: makeImplementsTypes(r.from, fset),
AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset),
T: makeImplementsType(r.t, fset),
AssignableTo: makeImplementsTypes(r.to, fset),
AssignableFrom: makeImplementsTypes(r.from, fset),
AssignableFromPtr: makeImplementsTypes(r.fromPtr, fset),
AssignableToMethod: methodsToSerial(r.qpos.info.Pkg, r.toMethod, fset),
AssignableFromMethod: methodsToSerial(r.qpos.info.Pkg, r.fromMethod, fset),
AssignableFromPtrMethod: methodsToSerial(r.qpos.info.Pkg, r.fromPtrMethod, fset),
}
if r.method != nil {
res.Implements.Method = &serial.DescribeMethod{
Name: r.qpos.ObjectString(r.method),
Pos: fset.Position(r.method.Pos()).String(),
}
}
}

View File

@ -208,6 +208,7 @@ func TestOracle(t *testing.T) {
"testdata/src/main/describe.go",
"testdata/src/main/freevars.go",
"testdata/src/main/implements.go",
"testdata/src/main/implements-methods.go",
"testdata/src/main/imports.go",
"testdata/src/main/peers.go",
"testdata/src/main/pointsto.go",
@ -221,6 +222,7 @@ func TestOracle(t *testing.T) {
"testdata/src/main/peers-json.go",
"testdata/src/main/describe-json.go",
"testdata/src/main/implements-json.go",
"testdata/src/main/implements-methods-json.go",
"testdata/src/main/pointsto-json.go",
"testdata/src/main/referrers-json.go",
"testdata/src/main/what-json.go",

View File

@ -101,7 +101,6 @@ type FreeVar struct {
}
// An Implements contains the result of an 'implements' query.
// It describes the queried type, the set of named non-empty interface
// types to which it is assignable, and the set of named/*named types
// (concrete or non-empty interface) which may be assigned to it.
@ -111,6 +110,15 @@ type Implements struct {
AssignableTo []ImplementsType `json:"to,omitempty"` // types assignable to T
AssignableFrom []ImplementsType `json:"from,omitempty"` // interface types assignable from T
AssignableFromPtr []ImplementsType `json:"fromptr,omitempty"` // interface types assignable only from *T
// The following fields are set only if the query was a method.
// Assignable{To,From,FromPtr}Method[i] is the corresponding
// method of type Assignable{To,From,FromPtr}[i], or blank
// {"",""} if that type lacks the method.
Method *DescribeMethod `json:"method,omitempty"` // the queried method
AssignableToMethod []DescribeMethod `json:"to_method,omitempty"`
AssignableFromMethod []DescribeMethod `json:"from_method,omitempty"`
AssignableFromPtrMethod []DescribeMethod `json:"fromptr_method,omitempty"`
}
// An ImplementsType describes a single type as part of an 'implements' query.

View File

@ -167,7 +167,5 @@ Method set:
method (I) f()
-------- @describe def-imethod-I.f --------
type interface{f()}
Method set:
method (interface{f()}) f()
definition of interface method func (I).f()

View File

@ -0,0 +1,38 @@
package main
// Tests of 'implements' query applied to methods, -output=json.
// See go.tools/oracle/oracle_test.go for explanation.
// See implements-methods.golden for expected query results.
import _ "lib"
import _ "sort"
func main() {
}
type F interface {
f() // @implements F.f "f"
}
type FG interface {
f() // @implements FG.f "f"
g() []int // @implements FG.g "g"
}
type C int
type D struct{}
func (c *C) f() {} // @implements *C.f "f"
func (d D) f() {} // @implements D.f "f"
func (d *D) g() []int { return nil } // @implements *D.g "g"
type sorter []int
func (sorter) Len() int { return 0 } // @implements Len "Len"
func (sorter) Less(i, j int) bool { return false }
func (sorter) Swap(i, j int) {}
type I interface {
Method(*int) *int // @implements I.Method "Method"
}

View File

@ -0,0 +1,283 @@
-------- @implements F.f --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.F",
"pos": "testdata/src/main/implements-methods-json.go:13:6",
"kind": "interface"
},
"to": [
{
"name": "*main.C",
"pos": "testdata/src/main/implements-methods-json.go:22:6",
"kind": "pointer"
},
{
"name": "main.D",
"pos": "testdata/src/main/implements-methods-json.go:23:6",
"kind": "struct"
},
{
"name": "main.FG",
"pos": "testdata/src/main/implements-methods-json.go:17:6",
"kind": "interface"
}
],
"method": {
"name": "func (F).f()",
"pos": "testdata/src/main/implements-methods-json.go:14:2"
},
"to_method": [
{
"name": "method (*C) f()",
"pos": "testdata/src/main/implements-methods-json.go:25:13"
},
{
"name": "method (D) f()",
"pos": "testdata/src/main/implements-methods-json.go:26:12"
},
{
"name": "method (FG) f()",
"pos": "testdata/src/main/implements-methods-json.go:18:2"
}
]
}
}-------- @implements FG.f --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.FG",
"pos": "testdata/src/main/implements-methods-json.go:17:6",
"kind": "interface"
},
"to": [
{
"name": "*main.D",
"pos": "testdata/src/main/implements-methods-json.go:23:6",
"kind": "pointer"
}
],
"from": [
{
"name": "main.F",
"pos": "testdata/src/main/implements-methods-json.go:13:6",
"kind": "interface"
}
],
"method": {
"name": "func (FG).f()",
"pos": "testdata/src/main/implements-methods-json.go:18:2"
},
"to_method": [
{
"name": "method (*D) f()",
"pos": "testdata/src/main/implements-methods-json.go:26:12"
}
],
"from_method": [
{
"name": "method (F) f()",
"pos": "testdata/src/main/implements-methods-json.go:14:2"
}
]
}
}-------- @implements FG.g --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.FG",
"pos": "testdata/src/main/implements-methods-json.go:17:6",
"kind": "interface"
},
"to": [
{
"name": "*main.D",
"pos": "testdata/src/main/implements-methods-json.go:23:6",
"kind": "pointer"
}
],
"from": [
{
"name": "main.F",
"pos": "testdata/src/main/implements-methods-json.go:13:6",
"kind": "interface"
}
],
"method": {
"name": "func (FG).g() []int",
"pos": "testdata/src/main/implements-methods-json.go:19:2"
},
"to_method": [
{
"name": "method (*D) g() []int",
"pos": "testdata/src/main/implements-methods-json.go:28:13"
}
],
"from_method": [
{
"name": "",
"pos": ""
}
]
}
}-------- @implements *C.f --------
{
"mode": "implements",
"implements": {
"type": {
"name": "*main.C",
"pos": "testdata/src/main/implements-methods-json.go:22:6",
"kind": "pointer"
},
"from": [
{
"name": "main.F",
"pos": "testdata/src/main/implements-methods-json.go:13:6",
"kind": "interface"
}
],
"method": {
"name": "func (*C).f()",
"pos": "testdata/src/main/implements-methods-json.go:25:13"
},
"from_method": [
{
"name": "method (F) f()",
"pos": "testdata/src/main/implements-methods-json.go:14:2"
}
]
}
}-------- @implements D.f --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.D",
"pos": "testdata/src/main/implements-methods-json.go:23:6",
"kind": "struct"
},
"from": [
{
"name": "main.F",
"pos": "testdata/src/main/implements-methods-json.go:13:6",
"kind": "interface"
}
],
"fromptr": [
{
"name": "main.FG",
"pos": "testdata/src/main/implements-methods-json.go:17:6",
"kind": "interface"
}
],
"method": {
"name": "func (D).f()",
"pos": "testdata/src/main/implements-methods-json.go:26:12"
},
"from_method": [
{
"name": "method (F) f()",
"pos": "testdata/src/main/implements-methods-json.go:14:2"
}
],
"fromptr_method": [
{
"name": "method (FG) f()",
"pos": "testdata/src/main/implements-methods-json.go:18:2"
}
]
}
}-------- @implements *D.g --------
{
"mode": "implements",
"implements": {
"type": {
"name": "*main.D",
"pos": "testdata/src/main/implements-methods-json.go:23:6",
"kind": "pointer"
},
"from": [
{
"name": "main.F",
"pos": "testdata/src/main/implements-methods-json.go:13:6",
"kind": "interface"
},
{
"name": "main.FG",
"pos": "testdata/src/main/implements-methods-json.go:17:6",
"kind": "interface"
}
],
"method": {
"name": "func (*D).g() []int",
"pos": "testdata/src/main/implements-methods-json.go:28:13"
},
"from_method": [
{
"name": "",
"pos": ""
},
{
"name": "method (FG) g() []int",
"pos": "testdata/src/main/implements-methods-json.go:19:2"
}
]
}
}-------- @implements Len --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.sorter",
"pos": "testdata/src/main/implements-methods-json.go:30:6",
"kind": "slice"
},
"from": [
{
"name": "sort.Interface",
"pos": "/usr/local/google/home/adonovan/go/src/sort/sort.go:12:6",
"kind": "interface"
}
],
"method": {
"name": "func (sorter).Len() int",
"pos": "testdata/src/main/implements-methods-json.go:32:15"
},
"from_method": [
{
"name": "method (sort.Interface) Len() int",
"pos": "/usr/local/google/home/adonovan/go/src/sort/sort.go:14:2"
}
]
}
}-------- @implements I.Method --------
{
"mode": "implements",
"implements": {
"type": {
"name": "main.I",
"pos": "testdata/src/main/implements-methods-json.go:36:6",
"kind": "interface"
},
"to": [
{
"name": "lib.Type",
"pos": "testdata/src/lib/lib.go:3:6",
"kind": "basic"
}
],
"method": {
"name": "func (I).Method(*int) *int",
"pos": "testdata/src/main/implements-methods-json.go:37:2"
},
"to_method": [
{
"name": "method (lib.Type) Method(x *int) *int",
"pos": "testdata/src/lib/lib.go:5:13"
}
]
}
}

View File

@ -0,0 +1,38 @@
package main
// Tests of 'implements' query applied to methods.
// See go.tools/oracle/oracle_test.go for explanation.
// See implements-methods.golden for expected query results.
import _ "lib"
import _ "sort"
func main() {
}
type F interface {
f() // @implements F.f "f"
}
type FG interface {
f() // @implements FG.f "f"
g() []int // @implements FG.g "g"
}
type C int
type D struct{}
func (c *C) f() {} // @implements *C.f "f"
func (d D) f() {} // @implements D.f "f"
func (d *D) g() []int { return nil } // @implements *D.g "g"
type sorter []int
func (sorter) Len() int { return 0 } // @implements Len "Len"
func (sorter) Less(i, j int) bool { return false }
func (sorter) Swap(i, j int) {}
type I interface {
Method(*int) *int // @implements I.Method "Method"
}

View File

@ -0,0 +1,37 @@
-------- @implements F.f --------
abstract method func (F).f()
is implemented by method (*C).f
is implemented by method (D).f
is implemented by method (FG).f
-------- @implements FG.f --------
abstract method func (FG).f()
is implemented by method (*D).f
implements method (F).f
-------- @implements FG.g --------
abstract method func (FG).g() []int
is implemented by method (*D).g
-------- @implements *C.f --------
concrete method func (*C).f()
implements method (F).f
-------- @implements D.f --------
concrete method func (D).f()
implements method (F).f
concrete method func (D).f()
implements method (FG).f
-------- @implements *D.g --------
concrete method func (*D).g() []int
implements method (FG).g
-------- @implements Len --------
concrete method func (sorter).Len() int
implements method (sort.Interface).Len
-------- @implements I.Method --------
abstract method func (I).Method(*int) *int
is implemented by method (lib.Type).Method