aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Donovan <adonovan@google.com>2015-02-24 18:02:49 -0500
committerAlan Donovan <adonovan@google.com>2015-02-25 22:38:52 +0000
commit264bffc00c400ce4401cc2be15ed56e18a12e889 (patch)
tree696e6d630278be0bcb9e76024dd797401a7c18ba
parent69db398fe0e69396984e3967724820c1f631e971 (diff)
downloadtools-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.go2
-rw-r--r--oracle/describe.go19
-rw-r--r--oracle/implements.go154
-rw-r--r--oracle/oracle_test.go2
-rw-r--r--oracle/serial/serial.go10
-rw-r--r--oracle/testdata/src/main/describe.golden4
-rw-r--r--oracle/testdata/src/main/implements-methods-json.go38
-rw-r--r--oracle/testdata/src/main/implements-methods-json.golden283
-rw-r--r--oracle/testdata/src/main/implements-methods.go38
-rw-r--r--oracle/testdata/src/main/implements-methods.golden37
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
+