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