go/callgraph/rta: adds tests for (instantiated) generics

Updates golang/go#48525

Change-Id: I7e25ab136dd69ebd50b12894bc893986fc59999b
Reviewed-on: https://go-review.googlesource.com/c/tools/+/402994
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Tim King <taking@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/go/callgraph/rta/rta.go b/go/callgraph/rta/rta.go
index 5dfb441..2e80415 100644
--- a/go/callgraph/rta/rta.go
+++ b/go/callgraph/rta/rta.go
@@ -45,6 +45,10 @@
 // replacing all "unreachable" functions by a special intrinsic, and
 // ensure that that intrinsic is never called.
 
+// TODO(zpavlinovic): decide if the clients must use ssa.InstantiateGenerics
+// mode when building programs with generics. It might be possible to
+// extend rta to accurately support generics with just ssa.BuilderMode(0).
+
 import (
 	"fmt"
 	"go/types"
diff --git a/go/callgraph/rta/rta_test.go b/go/callgraph/rta/rta_test.go
index 89ade37..67d05d6 100644
--- a/go/callgraph/rta/rta_test.go
+++ b/go/callgraph/rta/rta_test.go
@@ -16,7 +16,7 @@
 	"go/parser"
 	"go/token"
 	"go/types"
-	"io/ioutil"
+	"os"
 	"sort"
 	"strings"
 	"testing"
@@ -26,6 +26,7 @@
 	"golang.org/x/tools/go/loader"
 	"golang.org/x/tools/go/ssa"
 	"golang.org/x/tools/go/ssa/ssautil"
+	"golang.org/x/tools/internal/typeparams"
 )
 
 var inputs = []string{
@@ -53,16 +54,7 @@
 // one per line.  Each set is sorted.
 func TestRTA(t *testing.T) {
 	for _, filename := range inputs {
-		content, err := ioutil.ReadFile(filename)
-		if err != nil {
-			t.Errorf("couldn't read file '%s': %s", filename, err)
-			continue
-		}
-
-		conf := loader.Config{
-			ParserMode: parser.ParseComments,
-		}
-		f, err := conf.ParseFile(filename, content)
+		prog, f, mainPkg, err := loadProgInfo(filename, ssa.BuilderMode(0))
 		if err != nil {
 			t.Error(err)
 			continue
@@ -74,30 +66,77 @@
 			continue
 		}
 
-		conf.CreateFromFiles("main", f)
-		iprog, err := conf.Load()
-		if err != nil {
-			t.Error(err)
-			continue
-		}
-
-		prog := ssautil.CreateProgram(iprog, 0)
-		mainPkg := prog.Package(iprog.Created[0].Pkg)
-		prog.Build()
-
 		res := rta.Analyze([]*ssa.Function{
 			mainPkg.Func("main"),
 			mainPkg.Func("init"),
 		}, true)
 
-		if got := printResult(res, mainPkg.Pkg); got != want {
+		if got := printResult(res, mainPkg.Pkg, "dynamic", "Dynamic calls"); got != want {
 			t.Errorf("%s: got:\n%s\nwant:\n%s",
 				prog.Fset.Position(pos), got, want)
 		}
 	}
 }
 
-func printResult(res *rta.Result, from *types.Package) string {
+// TestRTAGenerics is TestRTA specialized for testing generics.
+func TestRTAGenerics(t *testing.T) {
+	if !typeparams.Enabled {
+		t.Skip("TestRTAGenerics requires type parameters")
+	}
+
+	filename := "testdata/generics.go"
+	prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	want, pos := expectation(f)
+	if pos == token.NoPos {
+		t.Fatalf("No WANT: comment in %s", filename)
+	}
+
+	res := rta.Analyze([]*ssa.Function{
+		mainPkg.Func("main"),
+		mainPkg.Func("init"),
+	}, true)
+
+	if got := printResult(res, mainPkg.Pkg, "", "All calls"); got != want {
+		t.Errorf("%s: got:\n%s\nwant:\n%s",
+			prog.Fset.Position(pos), got, want)
+	}
+}
+
+func loadProgInfo(filename string, mode ssa.BuilderMode) (*ssa.Program, *ast.File, *ssa.Package, error) {
+	content, err := os.ReadFile(filename)
+	if err != nil {
+		return nil, nil, nil, fmt.Errorf("couldn't read file '%s': %s", filename, err)
+	}
+
+	conf := loader.Config{
+		ParserMode: parser.ParseComments,
+	}
+	f, err := conf.ParseFile(filename, content)
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	conf.CreateFromFiles("main", f)
+	iprog, err := conf.Load()
+	if err != nil {
+		return nil, nil, nil, err
+	}
+
+	prog := ssautil.CreateProgram(iprog, mode)
+	prog.Build()
+
+	return prog, f, prog.Package(iprog.Created[0].Pkg), nil
+}
+
+// printResult returns a string representation of res, i.e., call graph,
+// reachable functions, and reflect types. For call graph, only edges
+// whose description contains edgeMatch are returned and their string
+// representation is prefixed with a desc line.
+func printResult(res *rta.Result, from *types.Package, edgeMatch, desc string) string {
 	var buf bytes.Buffer
 
 	writeSorted := func(ss []string) {
@@ -107,10 +146,10 @@
 		}
 	}
 
-	buf.WriteString("Dynamic calls\n")
+	buf.WriteString(desc + "\n")
 	var edges []string
 	callgraph.GraphVisitEdges(res.CallGraph, func(e *callgraph.Edge) error {
-		if strings.Contains(e.Description(), "dynamic") {
+		if strings.Contains(e.Description(), edgeMatch) {
 			edges = append(edges, fmt.Sprintf("%s --> %s",
 				e.Caller.Func.RelString(from),
 				e.Callee.Func.RelString(from)))
diff --git a/go/callgraph/rta/testdata/generics.go b/go/callgraph/rta/testdata/generics.go
new file mode 100644
index 0000000..012a64c
--- /dev/null
+++ b/go/callgraph/rta/testdata/generics.go
@@ -0,0 +1,79 @@
+//go:build ignore
+// +build ignore
+
+package main
+
+// Test of generic function calls.
+
+type I interface {
+	Foo()
+}
+
+type A struct{}
+
+func (a A) Foo() {}
+
+type B struct{}
+
+func (b B) Foo() {}
+
+func instantiated[X I](x X) {
+	x.Foo()
+}
+
+var a A
+var b B
+
+func main() {
+	instantiated[A](a) // static call
+	instantiated[B](b) // static call
+
+	local[C]().Foo()
+
+	lambda[A]()()()
+}
+
+func local[X I]() I {
+	var x X
+	return x
+}
+
+type C struct{}
+
+func (c C) Foo() {}
+
+func lambda[X I]() func() func() {
+	return func() func() {
+		var x X
+		return x.Foo
+	}
+}
+
+// WANT:
+// All calls
+//   (*C).Foo --> (C).Foo
+//   (A).Foo$bound --> (A).Foo
+//   instantiated[[main.A]] --> (A).Foo
+//   instantiated[[main.B]] --> (B).Foo
+//   main --> (*C).Foo
+//   main --> (A).Foo$bound
+//   main --> (C).Foo
+//   main --> instantiated[[main.A]]
+//   main --> instantiated[[main.B]]
+//   main --> lambda[[main.A]]
+//   main --> lambda[[main.A]]$1
+//   main --> local[[main.C]]
+// Reachable functions
+//   (*C).Foo
+//   (A).Foo
+//   (A).Foo$bound
+//   (B).Foo
+//   (C).Foo
+//   instantiated[[main.A]]
+//   instantiated[[main.B]]
+//   lambda[[main.A]]
+//   lambda[[main.A]]$1
+//   local[[main.C]]
+// Reflect types
+//   *C
+//   C