golang.org/x/tools@v0.21.0/internal/refactor/inline/calleefx_test.go (about)

     1  // Copyright 2023 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 inline_test
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/parser"
    11  	"go/token"
    12  	"go/types"
    13  	"testing"
    14  
    15  	"golang.org/x/tools/internal/refactor/inline"
    16  )
    17  
    18  // TestCalleeEffects is a unit test of the calleefx analysis.
    19  func TestCalleeEffects(t *testing.T) {
    20  	// Each callee must declare a function or method named f.
    21  	const funcName = "f"
    22  
    23  	var tests = []struct {
    24  		descr  string
    25  		callee string // Go source file (sans package decl) containing callee decl
    26  		want   string // expected effects string (-1=R∞ -2=W∞)
    27  	}{
    28  		{
    29  			"Assignments have unknown effects.",
    30  			`func f(x, y int) { x = y }`,
    31  			`[0 1 -2]`,
    32  		},
    33  		{
    34  			"Reads from globals are impure.",
    35  			`func f() { _ = g }; var g int`,
    36  			`[-1]`,
    37  		},
    38  		{
    39  			"Writes to globals have effects.",
    40  			`func f() { g = 0 }; var g int`,
    41  			`[-1 -2]`, // the -1 is spurious but benign
    42  		},
    43  		{
    44  			"Blank assign has no effect.",
    45  			`func f(x int) { _ = x }`,
    46  			`[0]`,
    47  		},
    48  		{
    49  			"Short decl of new var has has no effect.",
    50  			`func f(x int) { y := x; _ = y }`,
    51  			`[0]`,
    52  		},
    53  		{
    54  			"Short decl of existing var (y) is an assignment.",
    55  			`func f(x int) { y := x; y, z := 1, 2; _, _ = y, z }`,
    56  			`[0 -2]`,
    57  		},
    58  		{
    59  			"Unreferenced parameters are excluded.",
    60  			`func f(x, y, z int) { _ = z + x }`,
    61  			`[2 0]`,
    62  		},
    63  		{
    64  			"Built-in len has no effect.",
    65  			`func f(x, y string) { _ = len(y) + len(x) }`,
    66  			`[1 0]`,
    67  		},
    68  		{
    69  			"Built-in println has effects.",
    70  			`func f(x, y int) { println(y, x) }`,
    71  			`[1 0 -2]`,
    72  		},
    73  		{
    74  			"Return has no effect, and no control successor.",
    75  			`func f(x, y int) int { return x + y; panic(1) }`,
    76  			`[0 1]`,
    77  		},
    78  		{
    79  			"Loops (etc) have unknown effects.",
    80  			`func f(x, y bool) { for x { _ = y } }`,
    81  			`[0 -2 1]`,
    82  		},
    83  		{
    84  			"Calls have unknown effects.",
    85  			`func f(x, y int) { _, _, _ = x, g(), y }; func g() int`,
    86  			`[0 -2 1]`,
    87  		},
    88  		{
    89  			"Calls to some built-ins are pure.",
    90  			`func f(x, y int) { _, _, _ = x, len("hi"), y }`,
    91  			`[0 1]`,
    92  		},
    93  		{
    94  			"Calls to some built-ins are pure (variant).",
    95  			`func f(x, y int) { s := "hi"; _, _, _ = x, len(s), y; s = "bye" }`,
    96  			`[0 1 -2]`,
    97  		},
    98  		{
    99  			"Calls to some built-ins are pure (another variants).",
   100  			`func f(x, y int) { s := "hi"; _, _, _ = x, len(s), y }`,
   101  			`[0 1]`,
   102  		},
   103  		{
   104  			"Reading a local var is impure but does not have effects.",
   105  			`func f(x, y bool) { for x { _ = y } }`,
   106  			`[0 -2 1]`,
   107  		},
   108  	}
   109  	for _, test := range tests {
   110  		test := test
   111  		t.Run(test.descr, func(t *testing.T) {
   112  			fset := token.NewFileSet()
   113  			mustParse := func(filename string, content any) *ast.File {
   114  				f, err := parser.ParseFile(fset, filename, content, parser.ParseComments|parser.SkipObjectResolution)
   115  				if err != nil {
   116  					t.Fatalf("ParseFile: %v", err)
   117  				}
   118  				return f
   119  			}
   120  
   121  			// Parse callee file and find first func decl named f.
   122  			calleeContent := "package p\n" + test.callee
   123  			calleeFile := mustParse("callee.go", calleeContent)
   124  			var decl *ast.FuncDecl
   125  			for _, d := range calleeFile.Decls {
   126  				if d, ok := d.(*ast.FuncDecl); ok && d.Name.Name == funcName {
   127  					decl = d
   128  					break
   129  				}
   130  			}
   131  			if decl == nil {
   132  				t.Fatalf("declaration of func %s not found: %s", funcName, test.callee)
   133  			}
   134  
   135  			info := &types.Info{
   136  				Defs:       make(map[*ast.Ident]types.Object),
   137  				Uses:       make(map[*ast.Ident]types.Object),
   138  				Types:      make(map[ast.Expr]types.TypeAndValue),
   139  				Implicits:  make(map[ast.Node]types.Object),
   140  				Selections: make(map[*ast.SelectorExpr]*types.Selection),
   141  				Scopes:     make(map[ast.Node]*types.Scope),
   142  			}
   143  			conf := &types.Config{Error: func(err error) { t.Error(err) }}
   144  			pkg, err := conf.Check("p", fset, []*ast.File{calleeFile}, info)
   145  			if err != nil {
   146  				t.Fatal(err)
   147  			}
   148  
   149  			callee, err := inline.AnalyzeCallee(t.Logf, fset, pkg, info, decl, []byte(calleeContent))
   150  			if err != nil {
   151  				t.Fatal(err)
   152  			}
   153  			if got := fmt.Sprint(callee.Effects()); got != test.want {
   154  				t.Errorf("for effects of %s, got %s want %s",
   155  					test.callee, got, test.want)
   156  			}
   157  		})
   158  	}
   159  }