blob: 1fc16aebaacccd873c7a427d09a48a121e7532b2 [file] [log] [blame]
// Copyright 2023 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 inline_test
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"go/types"
"testing"
"golang.org/x/tools/internal/refactor/inline"
)
// TestCalleeEffects is a unit test of the calleefx analysis.
func TestCalleeEffects(t *testing.T) {
// Each callee must declare a function or method named f.
const funcName = "f"
var tests = []struct {
descr string
callee string // Go source file (sans package decl) containing callee decl
want string // expected effects string (-1=R∞ -2=W∞)
}{
{
"Assignments have unknown effects.",
`func f(x, y int) { x = y }`,
`[0 1 -2]`,
},
{
"Reads from globals are impure.",
`func f() { _ = g }; var g int`,
`[-1]`,
},
{
"Writes to globals have effects.",
`func f() { g = 0 }; var g int`,
`[-1 -2]`, // the -1 is spurious but benign
},
{
"Blank assign has no effect.",
`func f(x int) { _ = x }`,
`[0]`,
},
{
"Short decl of new var has has no effect.",
`func f(x int) { y := x; _ = y }`,
`[0]`,
},
{
"Short decl of existing var (y) is an assignment.",
`func f(x int) { y := x; y, z := 1, 2; _, _ = y, z }`,
`[0 -2]`,
},
{
"Unreferenced parameters are excluded.",
`func f(x, y, z int) { _ = z + x }`,
`[2 0]`,
},
{
"Built-in len has no effect.",
`func f(x, y string) { _ = len(y) + len(x) }`,
`[1 0]`,
},
{
"Built-in println has effects.",
`func f(x, y int) { println(y, x) }`,
`[1 0 -2]`,
},
{
"Return has no effect, and no control successor.",
`func f(x, y int) int { return x + y; panic(1) }`,
`[0 1]`,
},
{
"Loops (etc) have unknown effects.",
`func f(x, y bool) { for x { _ = y } }`,
`[0 -2 1]`,
},
{
"Calls have unknown effects.",
`func f(x, y int) { _, _, _ = x, g(), y }; func g() int`,
`[0 -2 1]`,
},
{
"Calls to some built-ins are pure.",
`func f(x, y int) { _, _, _ = x, len("hi"), y }`,
`[0 1]`,
},
{
"Calls to some built-ins are pure (variant).",
`func f(x, y int) { s := "hi"; _, _, _ = x, len(s), y; s = "bye" }`,
`[0 1 -2]`,
},
{
"Calls to some built-ins are pure (another variants).",
`func f(x, y int) { s := "hi"; _, _, _ = x, len(s), y }`,
`[0 1]`,
},
{
"Reading a local var is impure but does not have effects.",
`func f(x, y bool) { for x { _ = y } }`,
`[0 -2 1]`,
},
}
for _, test := range tests {
test := test
t.Run(test.descr, func(t *testing.T) {
fset := token.NewFileSet()
mustParse := func(filename string, content any) *ast.File {
f, err := parser.ParseFile(fset, filename, content, parser.ParseComments|parser.SkipObjectResolution)
if err != nil {
t.Fatalf("ParseFile: %v", err)
}
return f
}
// Parse callee file and find first func decl named f.
calleeContent := "package p\n" + test.callee
calleeFile := mustParse("callee.go", calleeContent)
var decl *ast.FuncDecl
for _, d := range calleeFile.Decls {
if d, ok := d.(*ast.FuncDecl); ok && d.Name.Name == funcName {
decl = d
break
}
}
if decl == nil {
t.Fatalf("declaration of func %s not found: %s", funcName, test.callee)
}
info := &types.Info{
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Types: make(map[ast.Expr]types.TypeAndValue),
Implicits: make(map[ast.Node]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Scopes: make(map[ast.Node]*types.Scope),
}
conf := &types.Config{Error: func(err error) { t.Error(err) }}
pkg, err := conf.Check("p", fset, []*ast.File{calleeFile}, info)
if err != nil {
t.Fatal(err)
}
callee, err := inline.AnalyzeCallee(t.Logf, fset, pkg, info, decl, []byte(calleeContent))
if err != nil {
t.Fatal(err)
}
if got := fmt.Sprint(callee.Effects()); got != test.want {
t.Errorf("for effects of %s, got %s want %s",
test.callee, got, test.want)
}
})
}
}