Skip to content

Commit 29d48d6

Browse files
committed
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>
1 parent ed968f6 commit 29d48d6

File tree

3 files changed

+148
-26
lines changed

3 files changed

+148
-26
lines changed

go/callgraph/rta/rta.go

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ package rta // import "golang.org/x/tools/go/callgraph/rta"
4545
// replacing all "unreachable" functions by a special intrinsic, and
4646
// ensure that that intrinsic is never called.
4747

48+
// TODO(zpavlinovic): decide if the clients must use ssa.InstantiateGenerics
49+
// mode when building programs with generics. It might be possible to
50+
// extend rta to accurately support generics with just ssa.BuilderMode(0).
51+
4852
import (
4953
"fmt"
5054
"go/types"

go/callgraph/rta/rta_test.go

+65-26
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
"go/parser"
1717
"go/token"
1818
"go/types"
19-
"io/ioutil"
19+
"os"
2020
"sort"
2121
"strings"
2222
"testing"
@@ -26,6 +26,7 @@ import (
2626
"golang.org/x/tools/go/loader"
2727
"golang.org/x/tools/go/ssa"
2828
"golang.org/x/tools/go/ssa/ssautil"
29+
"golang.org/x/tools/internal/typeparams"
2930
)
3031

3132
var inputs = []string{
@@ -53,16 +54,7 @@ func expectation(f *ast.File) (string, token.Pos) {
5354
// one per line. Each set is sorted.
5455
func TestRTA(t *testing.T) {
5556
for _, filename := range inputs {
56-
content, err := ioutil.ReadFile(filename)
57-
if err != nil {
58-
t.Errorf("couldn't read file '%s': %s", filename, err)
59-
continue
60-
}
61-
62-
conf := loader.Config{
63-
ParserMode: parser.ParseComments,
64-
}
65-
f, err := conf.ParseFile(filename, content)
57+
prog, f, mainPkg, err := loadProgInfo(filename, ssa.BuilderMode(0))
6658
if err != nil {
6759
t.Error(err)
6860
continue
@@ -74,30 +66,77 @@ func TestRTA(t *testing.T) {
7466
continue
7567
}
7668

77-
conf.CreateFromFiles("main", f)
78-
iprog, err := conf.Load()
79-
if err != nil {
80-
t.Error(err)
81-
continue
82-
}
83-
84-
prog := ssautil.CreateProgram(iprog, 0)
85-
mainPkg := prog.Package(iprog.Created[0].Pkg)
86-
prog.Build()
87-
8869
res := rta.Analyze([]*ssa.Function{
8970
mainPkg.Func("main"),
9071
mainPkg.Func("init"),
9172
}, true)
9273

93-
if got := printResult(res, mainPkg.Pkg); got != want {
74+
if got := printResult(res, mainPkg.Pkg, "dynamic", "Dynamic calls"); got != want {
9475
t.Errorf("%s: got:\n%s\nwant:\n%s",
9576
prog.Fset.Position(pos), got, want)
9677
}
9778
}
9879
}
9980

100-
func printResult(res *rta.Result, from *types.Package) string {
81+
// TestRTAGenerics is TestRTA specialized for testing generics.
82+
func TestRTAGenerics(t *testing.T) {
83+
if !typeparams.Enabled {
84+
t.Skip("TestRTAGenerics requires type parameters")
85+
}
86+
87+
filename := "testdata/generics.go"
88+
prog, f, mainPkg, err := loadProgInfo(filename, ssa.InstantiateGenerics)
89+
if err != nil {
90+
t.Fatal(err)
91+
}
92+
93+
want, pos := expectation(f)
94+
if pos == token.NoPos {
95+
t.Fatalf("No WANT: comment in %s", filename)
96+
}
97+
98+
res := rta.Analyze([]*ssa.Function{
99+
mainPkg.Func("main"),
100+
mainPkg.Func("init"),
101+
}, true)
102+
103+
if got := printResult(res, mainPkg.Pkg, "", "All calls"); got != want {
104+
t.Errorf("%s: got:\n%s\nwant:\n%s",
105+
prog.Fset.Position(pos), got, want)
106+
}
107+
}
108+
109+
func loadProgInfo(filename string, mode ssa.BuilderMode) (*ssa.Program, *ast.File, *ssa.Package, error) {
110+
content, err := os.ReadFile(filename)
111+
if err != nil {
112+
return nil, nil, nil, fmt.Errorf("couldn't read file '%s': %s", filename, err)
113+
}
114+
115+
conf := loader.Config{
116+
ParserMode: parser.ParseComments,
117+
}
118+
f, err := conf.ParseFile(filename, content)
119+
if err != nil {
120+
return nil, nil, nil, err
121+
}
122+
123+
conf.CreateFromFiles("main", f)
124+
iprog, err := conf.Load()
125+
if err != nil {
126+
return nil, nil, nil, err
127+
}
128+
129+
prog := ssautil.CreateProgram(iprog, mode)
130+
prog.Build()
131+
132+
return prog, f, prog.Package(iprog.Created[0].Pkg), nil
133+
}
134+
135+
// printResult returns a string representation of res, i.e., call graph,
136+
// reachable functions, and reflect types. For call graph, only edges
137+
// whose description contains edgeMatch are returned and their string
138+
// representation is prefixed with a desc line.
139+
func printResult(res *rta.Result, from *types.Package, edgeMatch, desc string) string {
101140
var buf bytes.Buffer
102141

103142
writeSorted := func(ss []string) {
@@ -107,10 +146,10 @@ func printResult(res *rta.Result, from *types.Package) string {
107146
}
108147
}
109148

110-
buf.WriteString("Dynamic calls\n")
149+
buf.WriteString(desc + "\n")
111150
var edges []string
112151
callgraph.GraphVisitEdges(res.CallGraph, func(e *callgraph.Edge) error {
113-
if strings.Contains(e.Description(), "dynamic") {
152+
if strings.Contains(e.Description(), edgeMatch) {
114153
edges = append(edges, fmt.Sprintf("%s --> %s",
115154
e.Caller.Func.RelString(from),
116155
e.Callee.Func.RelString(from)))

go/callgraph/rta/testdata/generics.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//go:build ignore
2+
// +build ignore
3+
4+
package main
5+
6+
// Test of generic function calls.
7+
8+
type I interface {
9+
Foo()
10+
}
11+
12+
type A struct{}
13+
14+
func (a A) Foo() {}
15+
16+
type B struct{}
17+
18+
func (b B) Foo() {}
19+
20+
func instantiated[X I](x X) {
21+
x.Foo()
22+
}
23+
24+
var a A
25+
var b B
26+
27+
func main() {
28+
instantiated[A](a) // static call
29+
instantiated[B](b) // static call
30+
31+
local[C]().Foo()
32+
33+
lambda[A]()()()
34+
}
35+
36+
func local[X I]() I {
37+
var x X
38+
return x
39+
}
40+
41+
type C struct{}
42+
43+
func (c C) Foo() {}
44+
45+
func lambda[X I]() func() func() {
46+
return func() func() {
47+
var x X
48+
return x.Foo
49+
}
50+
}
51+
52+
// WANT:
53+
// All calls
54+
// (*C).Foo --> (C).Foo
55+
// (A).Foo$bound --> (A).Foo
56+
// instantiated[[main.A]] --> (A).Foo
57+
// instantiated[[main.B]] --> (B).Foo
58+
// main --> (*C).Foo
59+
// main --> (A).Foo$bound
60+
// main --> (C).Foo
61+
// main --> instantiated[[main.A]]
62+
// main --> instantiated[[main.B]]
63+
// main --> lambda[[main.A]]
64+
// main --> lambda[[main.A]]$1
65+
// main --> local[[main.C]]
66+
// Reachable functions
67+
// (*C).Foo
68+
// (A).Foo
69+
// (A).Foo$bound
70+
// (B).Foo
71+
// (C).Foo
72+
// instantiated[[main.A]]
73+
// instantiated[[main.B]]
74+
// lambda[[main.A]]
75+
// lambda[[main.A]]$1
76+
// local[[main.C]]
77+
// Reflect types
78+
// *C
79+
// C

0 commit comments

Comments
 (0)