Skip to content

Commit ee08195

Browse files
committed
go/types/typeutil: Callee supports generics
Adds support for finding the Callee if it is a generic function. Generic methods happen to already work without this change. Updating this so that they are consistent. For golang/go#48704 Change-Id: I649ec746e350db4a0086ed31535b2e14baa32314 Reviewed-on: https://go-review.googlesource.com/c/tools/+/359974 Run-TryBot: Tim King <taking@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com> Reviewed-by: Robert Findley <rfindley@google.com> Trust: Zvonimir Pavlinovic <zpavlinovic@google.com>
1 parent e8bb373 commit ee08195

File tree

2 files changed

+157
-51
lines changed

2 files changed

+157
-51
lines changed

go/types/typeutil/callee.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,30 @@ import (
99
"go/types"
1010

1111
"golang.org/x/tools/go/ast/astutil"
12+
"golang.org/x/tools/internal/typeparams"
1213
)
1314

1415
// Callee returns the named target of a function call, if any:
1516
// a function, method, builtin, or variable.
17+
//
18+
// Functions and methods may potentially have type parameters.
1619
func Callee(info *types.Info, call *ast.CallExpr) types.Object {
20+
fun := astutil.Unparen(call.Fun)
21+
22+
// Look through type instantiation if necessary.
23+
isInstance := false
24+
switch fun.(type) {
25+
case *ast.IndexExpr, *typeparams.IndexListExpr:
26+
// When extracting the callee from an *IndexExpr, we need to check that
27+
// it is a *types.Func and not a *types.Var.
28+
// Example: Don't match a slice m within the expression `m[0]()`.
29+
isInstance = true
30+
ix := typeparams.GetIndexExprData(fun)
31+
fun = ix.X
32+
}
33+
1734
var obj types.Object
18-
switch fun := astutil.Unparen(call.Fun).(type) {
35+
switch fun := fun.(type) {
1936
case *ast.Ident:
2037
obj = info.Uses[fun] // type, var, builtin, or declared func
2138
case *ast.SelectorExpr:
@@ -28,11 +45,18 @@ func Callee(info *types.Info, call *ast.CallExpr) types.Object {
2845
if _, ok := obj.(*types.TypeName); ok {
2946
return nil // T(x) is a conversion, not a call
3047
}
48+
// A Func is required to match instantiations.
49+
if _, ok := obj.(*types.Func); isInstance && !ok {
50+
return nil // Was not a Func.
51+
}
3152
return obj
3253
}
3354

34-
// StaticCallee returns the target (function or method) of a static
35-
// function call, if any. It returns nil for calls to builtins.
55+
// StaticCallee returns the target (function or method) of a static function
56+
// call, if any. It returns nil for calls to builtins.
57+
//
58+
// Note: for calls of instantiated functions and methods, StaticCallee returns
59+
// the corresponding generic function or method on the generic type.
3660
func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
3761
if f, ok := Callee(info, call).(*types.Func); ok && !interfaceMethod(f) {
3862
return f

go/types/typeutil/callee_test.go

Lines changed: 130 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,85 +5,167 @@
55
package typeutil_test
66

77
import (
8+
"fmt"
89
"go/ast"
9-
"go/importer"
1010
"go/parser"
1111
"go/token"
1212
"go/types"
1313
"strings"
1414
"testing"
1515

1616
"golang.org/x/tools/go/types/typeutil"
17+
"golang.org/x/tools/internal/typeparams"
1718
)
1819

1920
func TestStaticCallee(t *testing.T) {
20-
const src = `package p
21+
testStaticCallee(t, []string{
22+
`package q;
23+
func Abs(x int) int {
24+
if x < 0 {
25+
return -x
26+
}
27+
return x
28+
}`,
29+
`package p
30+
import "q"
2131
22-
import "fmt"
32+
type T int
2333
24-
type T int
34+
func g(int)
2535
26-
func g(int)
36+
var f = g
2737
28-
var f = g
38+
var x int
2939
30-
var x int
40+
type s struct{ f func(int) }
41+
func (s) g(int)
3142
32-
type s struct{ f func(int) }
33-
func (s) g(int)
43+
type I interface{ f(int) }
3444
35-
type I interface{ f(int) }
45+
var a struct{b struct{c s}}
3646
37-
var a struct{b struct{c s}}
47+
var n map[int]func()
48+
var m []func()
3849
39-
func calls() {
40-
g(x) // a declared func
41-
s{}.g(x) // a concrete method
42-
a.b.c.g(x) // same
43-
fmt.Println(x) // declared func, qualified identifier
44-
}
50+
func calls() {
51+
g(x) // a declared func
52+
s{}.g(x) // a concrete method
53+
a.b.c.g(x) // same
54+
_ = q.Abs(x) // declared func, qualified identifier
55+
}
4556
46-
func noncalls() {
47-
_ = T(x) // a type
48-
f(x) // a var
49-
panic(x) // a built-in
50-
s{}.f(x) // a field
51-
I(nil).f(x) // interface method
57+
func noncalls() {
58+
_ = T(x) // a type
59+
f(x) // a var
60+
panic(x) // a built-in
61+
s{}.f(x) // a field
62+
I(nil).f(x) // interface method
63+
m[0]() // a map
64+
n[0]() // a slice
65+
}
66+
`})
5267
}
53-
`
54-
// parse
55-
fset := token.NewFileSet()
56-
f, err := parser.ParseFile(fset, "p.go", src, 0)
57-
if err != nil {
58-
t.Fatal(err)
68+
69+
func TestTypeParamStaticCallee(t *testing.T) {
70+
if !typeparams.Enabled {
71+
t.Skip("type parameters are not enabled")
5972
}
73+
testStaticCallee(t, []string{
74+
`package q
75+
func R[T any]() {}
76+
`,
77+
`package p
78+
import "q"
79+
type I interface{
80+
i()
81+
}
82+
83+
type G[T any] func() T
84+
func F[T any]() T { var x T; return x }
85+
86+
type M[T I] struct{ t T }
87+
func (m M[T]) noncalls() {
88+
m.t.i() // method on a type parameter
89+
}
90+
91+
func (m M[T]) calls() {
92+
m.calls() // method on a generic type
93+
}
94+
95+
type Chain[T I] struct{ r struct { s M[T] } }
6096
61-
// type-check
97+
type S int
98+
func (S) i() {}
99+
100+
func Multi[TP0, TP1 any](){}
101+
102+
func calls() {
103+
_ = F[int]() // instantiated function
104+
_ = (F[int])() // go through parens
105+
M[S]{}.calls() // instantiated method
106+
Chain[S]{}.r.s.calls() // same as above
107+
Multi[int,string]() // multiple type parameters
108+
q.R[int]() // different package
109+
}
110+
111+
func noncalls() {
112+
_ = G[int](nil)() // instantiated function
113+
}
114+
`})
115+
}
116+
117+
// testStaticCallee parses and type checks each file content in contents
118+
// as a single file package in order. Within functions that have the suffix
119+
// "calls" it checks that the CallExprs within have a static callee.
120+
// If the function's name == "calls" all calls must have static callees,
121+
// and if the name != "calls", the calls must not have static callees.
122+
// Failures are reported on t.
123+
func testStaticCallee(t *testing.T, contents []string) {
124+
fset := token.NewFileSet()
125+
packages := make(map[string]*types.Package)
126+
cfg := &types.Config{Importer: closure(packages)}
62127
info := &types.Info{
63128
Uses: make(map[*ast.Ident]types.Object),
64129
Selections: make(map[*ast.SelectorExpr]*types.Selection),
65130
}
66-
cfg := &types.Config{Importer: importer.ForCompiler(fset, "source", nil)}
67-
if _, err := cfg.Check("p", fset, []*ast.File{f}, info); err != nil {
68-
t.Fatal(err)
131+
typeparams.InitInstanceInfo(info)
132+
133+
var files []*ast.File
134+
for i, content := range contents {
135+
// parse
136+
f, err := parser.ParseFile(fset, fmt.Sprintf("%d.go", i), content, 0)
137+
if err != nil {
138+
t.Fatal(err)
139+
}
140+
files = append(files, f)
141+
142+
// type-check
143+
pkg, err := cfg.Check(f.Name.Name, fset, []*ast.File{f}, info)
144+
if err != nil {
145+
t.Fatal(err)
146+
}
147+
packages[pkg.Path()] = pkg
69148
}
70149

71-
for _, decl := range f.Decls {
72-
if decl, ok := decl.(*ast.FuncDecl); ok && strings.HasSuffix(decl.Name.Name, "calls") {
73-
wantCallee := decl.Name.Name == "calls" // false within func noncalls()
74-
ast.Inspect(decl.Body, func(n ast.Node) bool {
75-
if call, ok := n.(*ast.CallExpr); ok {
76-
fn := typeutil.StaticCallee(info, call)
77-
if fn == nil && wantCallee {
78-
t.Errorf("%s: StaticCallee returned nil",
79-
fset.Position(call.Lparen))
80-
} else if fn != nil && !wantCallee {
81-
t.Errorf("%s: StaticCallee returned %s, want nil",
82-
fset.Position(call.Lparen), fn)
150+
// check
151+
for _, f := range files {
152+
for _, decl := range f.Decls {
153+
if decl, ok := decl.(*ast.FuncDecl); ok && strings.HasSuffix(decl.Name.Name, "calls") {
154+
wantCallee := decl.Name.Name == "calls" // false within func noncalls()
155+
ast.Inspect(decl.Body, func(n ast.Node) bool {
156+
if call, ok := n.(*ast.CallExpr); ok {
157+
fn := typeutil.StaticCallee(info, call)
158+
if fn == nil && wantCallee {
159+
t.Errorf("%s: StaticCallee returned nil",
160+
fset.Position(call.Lparen))
161+
} else if fn != nil && !wantCallee {
162+
t.Errorf("%s: StaticCallee returned %s, want nil",
163+
fset.Position(call.Lparen), fn)
164+
}
83165
}
84-
}
85-
return true
86-
})
166+
return true
167+
})
168+
}
87169
}
88170
}
89171
}

0 commit comments

Comments
 (0)