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 callstack show path from callgraph root to selected function
describe describe selected syntax: definition, methods, etc describe describe selected syntax: definition, methods, etc
freevars show free variables of selection 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 peers show send/receive corresponding to selected channel op
referrers show all refs to entity denoted by selected identifier referrers show all refs to entity denoted by selected identifier
what show basic information about the selected syntax node 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 return path, actionExpr
case *types.Func: 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 return path, actionExpr
case *types.Builtin: 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 { func methodsToSerial(this *types.Package, methods []*types.Selection, fset *token.FileSet) []serial.DescribeMethod {
var jmethods []serial.DescribeMethod var jmethods []serial.DescribeMethod
for _, meth := range methods { for _, meth := range methods {
jmethods = append(jmethods, serial.DescribeMethod{ 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), Name: types.SelectionString(this, meth),
Pos: fset.Position(meth.Obj().Pos()).String(), Pos: fset.Position(meth.Obj().Pos()).String(),
}) }
}
jmethods = append(jmethods, ser)
} }
return jmethods return jmethods
} }

View File

@ -17,18 +17,35 @@ import (
) )
// Implements displays the "implements" relation as it pertains to the // 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) { func implements(o *Oracle, qpos *QueryPos) (queryResult, error) {
// Find the selected type. // Find the selected type.
// TODO(adonovan): fix: make it work on qualified Idents too.
path, action := findInterestingNode(qpos.info, qpos.path) 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 { 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 // 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(from))
sort.Sort(typesByString(fromPtr)) 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 { type implementsResult struct {
qpos *QueryPos
t types.Type // queried type (not necessarily named) t types.Type // queried type (not necessarily named)
pos interface{} // pos of t (*types.Name or *QueryPos) pos interface{} // pos of t (*types.Name or *QueryPos)
to []types.Type // named or ptr-to-named types assignable to interface T to []types.Type // named or ptr-to-named types assignable to interface T
from []types.Type // named interfaces assignable from T from []types.Type // named interfaces assignable from T
fromPtr []types.Type // named interfaces assignable only 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) { 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 isInterface(r.t) {
if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset if types.NewMethodSet(r.t).Len() == 0 { // TODO(adonovan): cache mset
printf(r.pos, "empty interface type %s", r.t) printf(r.pos, "empty interface type %s", r.t)
return return
} }
if r.method == nil {
printf(r.pos, "interface type %s", r.t) printf(r.pos, "interface type %s", r.t)
// Show concrete types first; use two passes. } else {
for _, sub := range r.to { 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) { if !isInterface(sub) {
printf(deref(sub).(*types.Named).Obj(), "\tis implemented by %s type %s", if r.method == nil {
typeKind(sub), sub) 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) { 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 { relation = "implements"
printf(super.(*types.Named).Obj(), "\timplements %s", super) 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 { } else {
relation = "implements"
if r.from != nil { if r.from != nil {
if r.method == nil {
printf(r.pos, "%s type %s", typeKind(r.t), r.t) printf(r.pos, "%s type %s", typeKind(r.t), r.t)
for _, super := range r.from { } else {
printf(super.(*types.Named).Obj(), "\timplements %s", super) 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 { if r.fromPtr != nil {
if r.method == nil {
printf(r.pos, "pointer type *%s", r.t) printf(r.pos, "pointer type *%s", r.t)
for _, psuper := range r.fromPtr { } else {
printf(psuper.(*types.Named).Obj(), "\timplements %s", psuper) // 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 { } else if r.from == nil {
printf(r.pos, "%s type %s implements only interface{}", typeKind(r.t), r.t) printf(r.pos, "%s type %s implements only interface{}", typeKind(r.t), r.t)
@ -161,6 +254,15 @@ func (r *implementsResult) toSerial(res *serial.Result, fset *token.FileSet) {
AssignableTo: makeImplementsTypes(r.to, fset), AssignableTo: makeImplementsTypes(r.to, fset),
AssignableFrom: makeImplementsTypes(r.from, fset), AssignableFrom: makeImplementsTypes(r.from, fset),
AssignableFromPtr: makeImplementsTypes(r.fromPtr, 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/describe.go",
"testdata/src/main/freevars.go", "testdata/src/main/freevars.go",
"testdata/src/main/implements.go", "testdata/src/main/implements.go",
"testdata/src/main/implements-methods.go",
"testdata/src/main/imports.go", "testdata/src/main/imports.go",
"testdata/src/main/peers.go", "testdata/src/main/peers.go",
"testdata/src/main/pointsto.go", "testdata/src/main/pointsto.go",
@ -221,6 +222,7 @@ func TestOracle(t *testing.T) {
"testdata/src/main/peers-json.go", "testdata/src/main/peers-json.go",
"testdata/src/main/describe-json.go", "testdata/src/main/describe-json.go",
"testdata/src/main/implements-json.go", "testdata/src/main/implements-json.go",
"testdata/src/main/implements-methods-json.go",
"testdata/src/main/pointsto-json.go", "testdata/src/main/pointsto-json.go",
"testdata/src/main/referrers-json.go", "testdata/src/main/referrers-json.go",
"testdata/src/main/what-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. // An Implements contains the result of an 'implements' query.
// It describes the queried type, the set of named non-empty interface // 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 // types to which it is assignable, and the set of named/*named types
// (concrete or non-empty interface) which may be assigned to it. // (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 AssignableTo []ImplementsType `json:"to,omitempty"` // types assignable to T
AssignableFrom []ImplementsType `json:"from,omitempty"` // interface types assignable from T AssignableFrom []ImplementsType `json:"from,omitempty"` // interface types assignable from T
AssignableFromPtr []ImplementsType `json:"fromptr,omitempty"` // interface types assignable only 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. // An ImplementsType describes a single type as part of an 'implements' query.

View File

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