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 <zpavlinovic@google.com>
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Zvonimir Pavlinovic 2021-10-21 13:48:40 -07:00
parent 8de2a7fd17
commit c8ad2e1325
5 changed files with 131 additions and 3 deletions

View File

@ -16,9 +16,11 @@ import (
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/cfg"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typeparams"
)
var Analyzer = &analysis.Analyzer{
@ -187,8 +189,12 @@ func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
return false // panic never returns
}
// Is this a static call?
fn := typeutil.StaticCallee(c.pass.TypesInfo, call)
// Is this a static call? Also includes static functions
// parameterized by a type. Such functions may or may not
// return depending on the parameter type, but in some
// cases the answer is definite. We let ctrlflow figure
// that out.
fn := staticCallee(c.pass.TypesInfo, call)
if fn == nil {
return true // callee not statically known; be conservative
}
@ -204,6 +210,50 @@ func (c *CFGs) callMayReturn(call *ast.CallExpr) (r bool) {
return !c.pass.ImportObjectFact(fn, new(noReturn))
}
// staticCallee returns static function, if any, called by call.
// Effectivelly reduces to typeutil.StaticCallee. In addition,
// returns static function parameterized by a type, if any.
//
// TODO(zpavlinovic): can this be replaced by typeutil.StaticCallee
// in the future?
func staticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
if fn := typeutil.StaticCallee(info, call); fn != nil {
return fn
}
return staticTypeParamCallee(info, call)
}
// staticTypeParamCallee returns the static function in call, if any,
// expected to be parameterized by a type.
func staticTypeParamCallee(info *types.Info, call *ast.CallExpr) *types.Func {
ix := typeparams.GetIndexExprData(astutil.Unparen(call.Fun))
if ix == nil {
return nil
}
var obj types.Object
switch fun := ix.X.(type) {
case *ast.Ident:
obj = info.Uses[fun] // type, var, builtin, or declared func
case *ast.SelectorExpr:
if sel, ok := info.Selections[fun]; ok {
obj = sel.Obj() // method or field
} else {
obj = info.Uses[fun.Sel] // qualified identifier?
}
}
if f, ok := obj.(*types.Func); ok && !interfaceMethod(f) {
return f
}
return nil
}
func interfaceMethod(f *types.Func) bool {
recv := f.Type().(*types.Signature).Recv()
return recv != nil && types.IsInterface(recv.Type())
}
var panicBuiltin = types.Universe.Lookup("panic").(*types.Builtin)
func hasReachableReturn(g *cfg.CFG) bool {

View File

@ -10,13 +10,19 @@ import (
"golang.org/x/tools/go/analysis/analysistest"
"golang.org/x/tools/go/analysis/passes/ctrlflow"
"golang.org/x/tools/internal/typeparams"
)
func Test(t *testing.T) {
testdata := analysistest.TestData()
// load testdata/src/a/a.go
results := analysistest.Run(t, testdata, ctrlflow.Analyzer, "a")
tests := []string{"a"}
if typeparams.Enabled {
// and testdata/src/typeparams/typeparams.go when possible
tests = append(tests, "typeparams")
}
results := analysistest.Run(t, testdata, ctrlflow.Analyzer, tests...)
// Perform a minimal smoke test on
// the result (CFG) computed by ctrlflow.

View File

@ -1,3 +1,7 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
// This file tests facts produced by ctrlflow.

View File

@ -1,3 +1,7 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package lib
func CanReturn() {}

View File

@ -0,0 +1,64 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package a
// This file tests facts produced by ctrlflow.
var cond bool
var funcs = []func(){func() {}}
func a[A any]() { // want a:"noReturn"
if cond {
funcs[0]()
b[A]()
} else {
for {
}
}
}
func b[B any]() { // want b:"noReturn"
select {}
}
func c[A, B any]() { // want c:"noReturn"
if cond {
a[A]()
} else {
d[A, B]()
}
}
func d[A, B any]() { // want d:"noReturn"
b[B]()
}
type I[T any] interface {
Id(T) T
}
func e[T any](i I[T], t T) T {
return i.Id(t)
}
func k[T any](i I[T], t T) T { // want k:"noReturn"
b[T]()
return i.Id(t)
}
type T[X any] int
func (T[X]) method1() { // want method1:"noReturn"
a[X]()
}
func (T[X]) method2() { // (may return)
if cond {
a[X]()
} else {
funcs[0]()
}
}