internal/lsp: make interface methods children of the interface symbol

Change-Id: I8dbb1400a228ea077f63c2c48f8dba0ac93990d2
Reviewed-on: https://go-review.googlesource.com/c/tools/+/173237
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/source/symbols.go b/internal/lsp/source/symbols.go
index 1ce5fc8..c0dcaf4 100644
--- a/internal/lsp/source/symbols.go
+++ b/internal/lsp/source/symbols.go
@@ -68,7 +68,7 @@
 				switch spec := spec.(type) {
 				case *ast.TypeSpec:
 					if obj := info.ObjectOf(spec.Name); obj != nil {
-						ts := typeSymbol(spec, obj, fset, q)
+						ts := typeSymbol(info, spec, obj, fset, q)
 						symbols = append(symbols, ts)
 						symbolsToReceiver[obj.Type()] = len(symbols) - 1
 					}
@@ -161,7 +161,7 @@
 	}
 }
 
-func typeSymbol(spec *ast.TypeSpec, obj types.Object, fset *token.FileSet, q types.Qualifier) Symbol {
+func typeSymbol(info *types.Info, spec *ast.TypeSpec, obj types.Object, fset *token.FileSet, q types.Qualifier) Symbol {
 	s := Symbol{Name: obj.Name()}
 	s.Detail, _ = formatType(obj.Type(), q)
 	setKind(&s, obj.Type(), q)
@@ -193,6 +193,66 @@
 		}
 	}
 
+	ti, objIsInterface := obj.Type().Underlying().(*types.Interface)
+	ai, specIsInterface := spec.Type.(*ast.InterfaceType)
+	if objIsInterface && specIsInterface {
+		for i := 0; i < ti.NumExplicitMethods(); i++ {
+			method := ti.ExplicitMethod(i)
+			child := Symbol{
+				Name: method.Name(),
+				Kind: MethodSymbol,
+			}
+
+			var spanNode, selectionNode ast.Node
+		Methods:
+			for _, f := range ai.Methods.List {
+				for _, id := range f.Names {
+					if id.Name == method.Name() {
+						spanNode, selectionNode = f, id
+						break Methods
+					}
+				}
+			}
+			if span, err := nodeSpan(spanNode, fset); err == nil {
+				child.Span = span
+			}
+			if span, err := nodeSpan(selectionNode, fset); err == nil {
+				child.SelectionSpan = span
+			}
+			s.Children = append(s.Children, child)
+		}
+
+		for i := 0; i < ti.NumEmbeddeds(); i++ {
+			embedded := ti.EmbeddedType(i)
+			nt, isNamed := embedded.(*types.Named)
+			if !isNamed {
+				continue
+			}
+
+			child := Symbol{Name: types.TypeString(embedded, q)}
+			setKind(&child, embedded, q)
+			var spanNode, selectionNode ast.Node
+		Embeddeds:
+			for _, f := range ai.Methods.List {
+				if len(f.Names) > 0 {
+					continue
+				}
+
+				if t := info.TypeOf(f.Type); types.Identical(nt, t) {
+					spanNode, selectionNode = f, f.Type
+					break Embeddeds
+				}
+			}
+
+			if span, err := nodeSpan(spanNode, fset); err == nil {
+				child.Span = span
+			}
+			if span, err := nodeSpan(selectionNode, fset); err == nil {
+				child.SelectionSpan = span
+			}
+			s.Children = append(s.Children, child)
+		}
+	}
 	return s
 }
 
diff --git a/internal/lsp/testdata/symbols/main.go b/internal/lsp/testdata/symbols/main.go
index 032f74e..b9ee77c 100644
--- a/internal/lsp/testdata/symbols/main.go
+++ b/internal/lsp/testdata/symbols/main.go
@@ -1,6 +1,8 @@
 package main
 
-import "io"
+import (
+	"io"
+)
 
 var x = 42 //@symbol("x", "x", "Variable", "")
 
@@ -39,5 +41,16 @@
 }
 
 type Stringer interface { //@symbol("Stringer", "Stringer", "Interface", "")
-	String() string
+	String() string //@symbol("String", "String", "Method", "Stringer")
+}
+
+type ABer interface { //@symbol("ABer", "ABer", "Interface", "")
+	B()        //@symbol("B", "B", "Method", "ABer")
+	A() string //@symbol("A", "A", "Method", "ABer")
+}
+
+type WithEmbeddeds interface { //@symbol("WithEmbeddeds", "WithEmbeddeds", "Interface", "")
+	Do()      //@symbol("Do", "Do", "Method", "WithEmbeddeds")
+	ABer      //@symbol("ABer", "ABer", "Interface", "WithEmbeddeds")
+	io.Writer //@symbol("io.Writer", "io.Writer", "Interface", "WithEmbeddeds")
 }