Skip to content

Commit c8ad2e1

Browse files
committed
go/analysis/passes/ctrlflow: add typeparams test
This CL adds a test for the assign pass that involves use of generics. Updates golang/go#48704 Change-Id: I19d27932f0d326e2456b6714f76de01d46f1b1e3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/357777 Trust: Zvonimir Pavlinovic <[email protected]> Run-TryBot: Zvonimir Pavlinovic <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 8de2a7f commit c8ad2e1

File tree

5 files changed

+131
-3
lines changed

5 files changed

+131
-3
lines changed

go/analysis/passes/ctrlflow/ctrlflow.go

+52-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@ import (
1616

1717
"golang.org/x/tools/go/analysis"
1818
"golang.org/x/tools/go/analysis/passes/inspect"
19+
"golang.org/x/tools/go/ast/astutil"
1920
"golang.org/x/tools/go/ast/inspector"
2021
"golang.org/x/tools/go/cfg"
2122
"golang.org/x/tools/go/types/typeutil"
23+
"golang.org/x/tools/internal/typeparams"
2224
)
2325

2426
var Analyzer = &analysis.Analyzer{
@@ -187,8 +189,12 @@ func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
187189
return false // panic never returns
188190
}
189191

190-
// Is this a static call?
191-
fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
192+
// Is this a static call? Also includes static functions
193+
// parameterized by a type. Such functions may or may not
194+
// return depending on the parameter type, but in some
195+
// cases the answer is definite. We let ctrlflow figure
196+
// that out.
197+
fn := staticCallee(c.pass.TypesInfo, call)
192198
if fn == nil {
193199
return true // callee not statically known; be conservative
194200
}
@@ -204,6 +210,50 @@ func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
204210
return !c.pass.ImportObjectFact(fn, new(noReturn))
205211
}
206212

213+
// staticCallee returns static function, if any, called by call.
214+
// Effectivelly reduces to typeutil.StaticCallee. In addition,
215+
// returns static function parameterized by a type, if any.
216+
//
217+
// TODO(zpavlinovic): can this be replaced by typeutil.StaticCallee
218+
// in the future?
219+
func staticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
220+
if fn := typeutil.StaticCallee(info, call); fn != nil {
221+
return fn
222+
}
223+
return staticTypeParamCallee(info, call)
224+
}
225+
226+
// staticTypeParamCallee returns the static function in call, if any,
227+
// expected to be parameterized by a type.
228+
func staticTypeParamCallee(info *types.Info, call *ast.CallExpr) *types.Func {
229+
ix := typeparams.GetIndexExprData(astutil.Unparen(call.Fun))
230+
if ix == nil {
231+
return nil
232+
}
233+
234+
var obj types.Object
235+
switch fun := ix.X.(type) {
236+
case *ast.Ident:
237+
obj = info.Uses[fun] // type, var, builtin, or declared func
238+
case *ast.SelectorExpr:
239+
if sel, ok := info.Selections[fun]; ok {
240+
obj = sel.Obj() // method or field
241+
} else {
242+
obj = info.Uses[fun.Sel] // qualified identifier?
243+
}
244+
}
245+
246+
if f, ok := obj.(*types.Func); ok && !interfaceMethod(f) {
247+
return f
248+
}
249+
return nil
250+
}
251+
252+
func interfaceMethod(f *types.Func) bool {
253+
recv := f.Type().(*types.Signature).Recv()
254+
return recv != nil && types.IsInterface(recv.Type())
255+
}
256+
207257
var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
208258

209259
func hasReachableReturn(g *cfg.CFG) bool {

go/analysis/passes/ctrlflow/ctrlflow_test.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@ import (
1010

1111
"golang.org/x/tools/go/analysis/analysistest"
1212
"golang.org/x/tools/go/analysis/passes/ctrlflow"
13+
"golang.org/x/tools/internal/typeparams"
1314
)
1415

1516
func Test(t *testing.T) {
1617
testdata := analysistest.TestData()
1718

1819
// load testdata/src/a/a.go
19-
results := analysistest.Run(t, testdata, ctrlflow.Analyzer, "a")
20+
tests := []string{"a"}
21+
if typeparams.Enabled {
22+
// and testdata/src/typeparams/typeparams.go when possible
23+
tests = append(tests, "typeparams")
24+
}
25+
results := analysistest.Run(t, testdata, ctrlflow.Analyzer, tests...)
2026

2127
// Perform a minimal smoke test on
2228
// the result (CFG) computed by ctrlflow.

go/analysis/passes/ctrlflow/testdata/src/a/a.go

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
15
package a
26

37
// This file tests facts produced by ctrlflow.

go/analysis/passes/ctrlflow/testdata/src/lib/lib.go

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
15
package lib
26

37
func CanReturn() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package a
6+
7+
// This file tests facts produced by ctrlflow.
8+
9+
var cond bool
10+
11+
var funcs = []func(){func() {}}
12+
13+
func a[A any]() { // want a:"noReturn"
14+
if cond {
15+
funcs[0]()
16+
b[A]()
17+
} else {
18+
for {
19+
}
20+
}
21+
}
22+
23+
func b[B any]() { // want b:"noReturn"
24+
select {}
25+
}
26+
27+
func c[A, B any]() { // want c:"noReturn"
28+
if cond {
29+
a[A]()
30+
} else {
31+
d[A, B]()
32+
}
33+
}
34+
35+
func d[A, B any]() { // want d:"noReturn"
36+
b[B]()
37+
}
38+
39+
type I[T any] interface {
40+
Id(T) T
41+
}
42+
43+
func e[T any](i I[T], t T) T {
44+
return i.Id(t)
45+
}
46+
47+
func k[T any](i I[T], t T) T { // want k:"noReturn"
48+
b[T]()
49+
return i.Id(t)
50+
}
51+
52+
type T[X any] int
53+
54+
func (T[X]) method1() { // want method1:"noReturn"
55+
a[X]()
56+
}
57+
58+
func (T[X]) method2() { // (may return)
59+
if cond {
60+
a[X]()
61+
} else {
62+
funcs[0]()
63+
}
64+
}

0 commit comments

Comments
 (0)