diff options
author | Alan Donovan <adonovan@google.com> | 2015-02-24 18:02:49 -0500 |
---|---|---|
committer | Alan Donovan <adonovan@google.com> | 2015-02-25 22:38:52 +0000 |
commit | 264bffc00c400ce4401cc2be15ed56e18a12e889 (patch) | |
tree | 696e6d630278be0bcb9e76024dd797401a7c18ba | |
parent | 69db398fe0e69396984e3967724820c1f631e971 (diff) | |
download | tools-264bffc00c400ce4401cc2be15ed56e18a12e889.tar.gz |
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>
-rw-r--r-- | cmd/oracle/main.go | 2 | ||||
-rw-r--r-- | oracle/describe.go | 19 | ||||
-rw-r--r-- | oracle/implements.go | 154 | ||||
-rw-r--r-- | oracle/oracle_test.go | 2 | ||||
-rw-r--r-- | oracle/serial/serial.go | 10 | ||||
-rw-r--r-- | oracle/testdata/src/main/describe.golden | 4 | ||||
-rw-r--r-- | oracle/testdata/src/main/implements-methods-json.go | 38 | ||||
-rw-r--r-- | oracle/testdata/src/main/implements-methods-json.golden | 283 | ||||
-rw-r--r-- | oracle/testdata/src/main/implements-methods.go | 38 | ||||
-rw-r--r-- | oracle/testdata/src/main/implements-methods.golden | 37 |
10 files changed, 545 insertions, 42 deletions
diff --git a/cmd/oracle/main.go b/cmd/oracle/main.go index 294bde7..96efcb6 100644 --- a/cmd/oracle/main.go +++ b/cmd/oracle/main.go @@ -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 diff --git a/oracle/describe.go b/oracle/describe.go index 1c329d0..9c21017 100644 --- a/oracle/describe.go +++ b/oracle/describe.go @@ -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 } diff --git a/oracle/implements.go b/oracle/implements.go index 8e54dea..1ad80b4 100644 --- a/oracle/implements.go +++ b/oracle/implements.go @@ -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(), + } } } diff --git a/oracle/oracle_test.go b/oracle/oracle_test.go index bbceb60..fe43fc2 100644 --- a/oracle/oracle_test.go +++ b/oracle/oracle_test.go @@ -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", diff --git a/oracle/serial/serial.go b/oracle/serial/serial.go index 6432b61..86ccb02 100644 --- a/oracle/serial/serial.go +++ b/oracle/serial/serial.go @@ -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. diff --git a/oracle/testdata/src/main/describe.golden b/oracle/testdata/src/main/describe.golden index 3f305d4..33d751a 100644 --- a/oracle/testdata/src/main/describe.golden +++ b/oracle/testdata/src/main/describe.golden @@ -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() diff --git a/oracle/testdata/src/main/implements-methods-json.go b/oracle/testdata/src/main/implements-methods-json.go new file mode 100644 index 0000000..507dca5 --- /dev/null +++ b/oracle/testdata/src/main/implements-methods-json.go @@ -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" +} diff --git a/oracle/testdata/src/main/implements-methods-json.golden b/oracle/testdata/src/main/implements-methods-json.golden new file mode 100644 index 0000000..a925caa --- /dev/null +++ b/oracle/testdata/src/main/implements-methods-json.golden @@ -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" + } + ] + } +}
\ No newline at end of file diff --git a/oracle/testdata/src/main/implements-methods.go b/oracle/testdata/src/main/implements-methods.go new file mode 100644 index 0000000..4cc4288 --- /dev/null +++ b/oracle/testdata/src/main/implements-methods.go @@ -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" +} diff --git a/oracle/testdata/src/main/implements-methods.golden b/oracle/testdata/src/main/implements-methods.golden new file mode 100644 index 0000000..11ccaf4 --- /dev/null +++ b/oracle/testdata/src/main/implements-methods.golden @@ -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 + |