// 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) } }) } }