|  | // 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 ( | 
|  | "bytes" | 
|  | "crypto/sha256" | 
|  | "encoding/binary" | 
|  | "encoding/gob" | 
|  | "fmt" | 
|  | "go/ast" | 
|  | "go/parser" | 
|  | "go/token" | 
|  | "go/types" | 
|  | "os" | 
|  | "path/filepath" | 
|  | "reflect" | 
|  | "regexp" | 
|  | "strings" | 
|  | "testing" | 
|  | "unsafe" | 
|  |  | 
|  | "golang.org/x/tools/go/ast/astutil" | 
|  | "golang.org/x/tools/go/expect" | 
|  | "golang.org/x/tools/go/packages" | 
|  | "golang.org/x/tools/go/types/typeutil" | 
|  | "golang.org/x/tools/internal/diff" | 
|  | "golang.org/x/tools/internal/refactor/inline" | 
|  | "golang.org/x/tools/internal/testenv" | 
|  | "golang.org/x/tools/txtar" | 
|  | ) | 
|  |  | 
|  | // TestData executes test scenarios specified by files in testdata/*.txtar. | 
|  | func TestData(t *testing.T) { | 
|  | testenv.NeedsGoPackages(t) | 
|  |  | 
|  | files, err := filepath.Glob("testdata/*.txtar") | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | for _, file := range files { | 
|  | file := file | 
|  | t.Run(filepath.Base(file), func(t *testing.T) { | 
|  | t.Parallel() | 
|  |  | 
|  | // The few tests that use cgo should be in | 
|  | // files whose name includes "cgo". | 
|  | if strings.Contains(t.Name(), "cgo") { | 
|  | testenv.NeedsTool(t, "cgo") | 
|  | } | 
|  |  | 
|  | // Extract archive to temporary tree. | 
|  | ar, err := txtar.ParseFile(file) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | dir := t.TempDir() | 
|  | if err := extractTxtar(ar, dir); err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | // Load packages. | 
|  | cfg := &packages.Config{ | 
|  | Dir:  dir, | 
|  | Mode: packages.LoadAllSyntax, | 
|  | Env: append(os.Environ(), | 
|  | "GO111MODULES=on", | 
|  | "GOPATH=", | 
|  | "GOWORK=off", | 
|  | "GOPROXY=off"), | 
|  | } | 
|  | pkgs, err := packages.Load(cfg, "./...") | 
|  | if err != nil { | 
|  | t.Errorf("Load: %v", err) | 
|  | } | 
|  | // Report parse/type errors; they may be benign. | 
|  | packages.Visit(pkgs, nil, func(pkg *packages.Package) { | 
|  | for _, err := range pkg.Errors { | 
|  | t.Log(err) | 
|  | } | 
|  | }) | 
|  |  | 
|  | // Process @inline notes in comments in initial packages. | 
|  | for _, pkg := range pkgs { | 
|  | for _, file := range pkg.Syntax { | 
|  | // Read file content (for @inline regexp, and inliner). | 
|  | content, err := os.ReadFile(pkg.Fset.File(file.Pos()).Name()) | 
|  | if err != nil { | 
|  | t.Error(err) | 
|  | continue | 
|  | } | 
|  |  | 
|  | // Read and process @inline notes. | 
|  | notes, err := expect.ExtractGo(pkg.Fset, file) | 
|  | if err != nil { | 
|  | t.Errorf("parsing notes in %q: %v", pkg.Fset.File(file.Pos()).Name(), err) | 
|  | continue | 
|  | } | 
|  | for _, note := range notes { | 
|  | posn := pkg.Fset.PositionFor(note.Pos, false) | 
|  | if note.Name != "inline" { | 
|  | t.Errorf("%s: invalid marker @%s", posn, note.Name) | 
|  | continue | 
|  | } | 
|  | if nargs := len(note.Args); nargs != 2 { | 
|  | t.Errorf("@inline: want 2 args, got %d", nargs) | 
|  | continue | 
|  | } | 
|  | pattern, ok := note.Args[0].(*regexp.Regexp) | 
|  | if !ok { | 
|  | t.Errorf("%s: @inline(rx, want): want regular expression rx", posn) | 
|  | continue | 
|  | } | 
|  |  | 
|  | // want is a []byte (success) or *Regexp (failure) | 
|  | var want any | 
|  | switch x := note.Args[1].(type) { | 
|  | case string, expect.Identifier: | 
|  | for _, file := range ar.Files { | 
|  | if file.Name == fmt.Sprint(x) { | 
|  | want = file.Data | 
|  | break | 
|  | } | 
|  | } | 
|  | if want == nil { | 
|  | t.Errorf("%s: @inline(rx, want): archive entry %q not found", posn, x) | 
|  | continue | 
|  | } | 
|  | case *regexp.Regexp: | 
|  | want = x | 
|  | default: | 
|  | t.Errorf("%s: @inline(rx, want): want file name (to assert success) or error message regexp (to assert failure)", posn) | 
|  | continue | 
|  | } | 
|  | if err := doInlineNote(t.Logf, pkg, file, content, pattern, posn, want); err != nil { | 
|  | t.Errorf("%s: @inline(%v, %v): %v", posn, note.Args[0], note.Args[1], err) | 
|  | continue | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | // doInlineNote executes an assertion specified by a single | 
|  | // @inline(re"pattern", want) note in a comment. It finds the first | 
|  | // match of regular expression 'pattern' on the same line, finds the | 
|  | // innermost enclosing CallExpr, and inlines it. | 
|  | // | 
|  | // Finally it checks that, on success, the transformed file is equal | 
|  | // to want (a []byte), or on failure that the error message matches | 
|  | // want (a *Regexp). | 
|  | func doInlineNote(logf func(string, ...any), pkg *packages.Package, file *ast.File, content []byte, pattern *regexp.Regexp, posn token.Position, want any) error { | 
|  | // Find extent of pattern match within commented line. | 
|  | var startPos, endPos token.Pos | 
|  | { | 
|  | tokFile := pkg.Fset.File(file.Pos()) | 
|  | lineStartOffset := int(tokFile.LineStart(posn.Line)) - tokFile.Base() | 
|  | line := content[lineStartOffset:] | 
|  | if i := bytes.IndexByte(line, '\n'); i >= 0 { | 
|  | line = line[:i] | 
|  | } | 
|  | matches := pattern.FindSubmatchIndex(line) | 
|  | var start, end int // offsets | 
|  | switch len(matches) { | 
|  | case 2: | 
|  | // no subgroups: return the range of the regexp expression | 
|  | start, end = matches[0], matches[1] | 
|  | case 4: | 
|  | // one subgroup: return its range | 
|  | start, end = matches[2], matches[3] | 
|  | default: | 
|  | return fmt.Errorf("invalid location regexp %q: expect either 0 or 1 subgroups, got %d", | 
|  | pattern, len(matches)/2-1) | 
|  | } | 
|  | startPos = tokFile.Pos(lineStartOffset + start) | 
|  | endPos = tokFile.Pos(lineStartOffset + end) | 
|  | } | 
|  |  | 
|  | // Find innermost call enclosing the pattern match. | 
|  | var caller *inline.Caller | 
|  | { | 
|  | path, _ := astutil.PathEnclosingInterval(file, startPos, endPos) | 
|  | for _, n := range path { | 
|  | if call, ok := n.(*ast.CallExpr); ok { | 
|  | caller = &inline.Caller{ | 
|  | Fset:    pkg.Fset, | 
|  | Types:   pkg.Types, | 
|  | Info:    pkg.TypesInfo, | 
|  | File:    file, | 
|  | Call:    call, | 
|  | Content: content, | 
|  | } | 
|  | break | 
|  | } | 
|  | } | 
|  | if caller == nil { | 
|  | return fmt.Errorf("no enclosing call") | 
|  | } | 
|  | } | 
|  |  | 
|  | // Is it a static function call? | 
|  | fn := typeutil.StaticCallee(caller.Info, caller.Call) | 
|  | if fn == nil { | 
|  | return fmt.Errorf("cannot inline: not a static call") | 
|  | } | 
|  |  | 
|  | // Find callee function. | 
|  | var calleePkg *packages.Package | 
|  | { | 
|  | // Is the call within the package? | 
|  | if fn.Pkg() == caller.Types { | 
|  | calleePkg = pkg // same as caller | 
|  | } else { | 
|  | // Different package. Load it now. | 
|  | // (The primary load loaded all dependencies, | 
|  | // but we choose to load it again, with | 
|  | // a distinct token.FileSet and types.Importer, | 
|  | // to keep the implementation honest.) | 
|  | cfg := &packages.Config{ | 
|  | // TODO(adonovan): get the original module root more cleanly | 
|  | Dir:  filepath.Dir(filepath.Dir(pkg.GoFiles[0])), | 
|  | Fset: token.NewFileSet(), | 
|  | Mode: packages.LoadSyntax, | 
|  | } | 
|  | roots, err := packages.Load(cfg, fn.Pkg().Path()) | 
|  | if err != nil { | 
|  | return fmt.Errorf("loading callee package: %v", err) | 
|  | } | 
|  | if packages.PrintErrors(roots) > 0 { | 
|  | return fmt.Errorf("callee package had errors") // (see log) | 
|  | } | 
|  | calleePkg = roots[0] | 
|  | } | 
|  | } | 
|  |  | 
|  | calleeDecl, err := findFuncByPosition(calleePkg, caller.Fset.PositionFor(fn.Pos(), false)) | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  |  | 
|  | // Do the inlining. For the purposes of the test, | 
|  | // AnalyzeCallee and Inline are a single operation. | 
|  | got, err := func() ([]byte, error) { | 
|  | filename := calleePkg.Fset.File(calleeDecl.Pos()).Name() | 
|  | content, err := os.ReadFile(filename) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | callee, err := inline.AnalyzeCallee( | 
|  | logf, | 
|  | calleePkg.Fset, | 
|  | calleePkg.Types, | 
|  | calleePkg.TypesInfo, | 
|  | calleeDecl, | 
|  | content) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | if err := checkTranscode(callee); err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | check := checkNoMutation(caller.File) | 
|  | defer check() | 
|  | return inline.Inline(logf, caller, callee) | 
|  | }() | 
|  | if err != nil { | 
|  | if wantRE, ok := want.(*regexp.Regexp); ok { | 
|  | if !wantRE.MatchString(err.Error()) { | 
|  | return fmt.Errorf("Inline failed with wrong error: %v (want error matching %q)", err, want) | 
|  | } | 
|  | return nil // expected error | 
|  | } | 
|  | return fmt.Errorf("Inline failed: %v", err) // success was expected | 
|  | } | 
|  |  | 
|  | // Inline succeeded. | 
|  | if want, ok := want.([]byte); ok { | 
|  | got = append(bytes.TrimSpace(got), '\n') | 
|  | want = append(bytes.TrimSpace(want), '\n') | 
|  | if diff := diff.Unified("want", "got", string(want), string(got)); diff != "" { | 
|  | return fmt.Errorf("Inline returned wrong output:\n%s\nWant:\n%s\nDiff:\n%s", | 
|  | got, want, diff) | 
|  | } | 
|  | return nil | 
|  | } | 
|  | return fmt.Errorf("Inline succeeded unexpectedly: want error matching %q, got <<%s>>", want, got) | 
|  |  | 
|  | } | 
|  |  | 
|  | // findFuncByPosition returns the FuncDecl at the specified (package-agnostic) position. | 
|  | func findFuncByPosition(pkg *packages.Package, posn token.Position) (*ast.FuncDecl, error) { | 
|  | same := func(decl *ast.FuncDecl) bool { | 
|  | // We can't rely on columns in export data: | 
|  | // some variants replace it with 1. | 
|  | // We can't expect file names to have the same prefix. | 
|  | // export data for go1.20 std packages have  $GOROOT written in | 
|  | // them, so how are we supposed to find the source? Yuck! | 
|  | // Ugh. need to samefile? Nope $GOROOT just won't work | 
|  | // This is highly client specific anyway. | 
|  | posn2 := pkg.Fset.PositionFor(decl.Name.Pos(), false) | 
|  | return posn.Filename == posn2.Filename && | 
|  | posn.Line == posn2.Line | 
|  | } | 
|  | for _, file := range pkg.Syntax { | 
|  | for _, decl := range file.Decls { | 
|  | if decl, ok := decl.(*ast.FuncDecl); ok && same(decl) { | 
|  | return decl, nil | 
|  | } | 
|  | } | 
|  | } | 
|  | return nil, fmt.Errorf("can't find FuncDecl at %v in package %q", posn, pkg.PkgPath) | 
|  | } | 
|  |  | 
|  | // Each callee must declare a function or method named f, | 
|  | // and each caller must call it. | 
|  | const funcName = "f" | 
|  |  | 
|  | // A testcase is an item in a table-driven test. | 
|  | // | 
|  | // The table-driven tests are less flexible, but enable more compact | 
|  | // expression of single-package test cases than is possible with the | 
|  | // txtar notation. | 
|  | // | 
|  | // TODO(adonovan): improve coverage of the cross product of each | 
|  | // strategy with the checklist of concerns enumerated in the package | 
|  | // doc comment. | 
|  | type testcase struct { | 
|  | descr          string | 
|  | callee, caller string // Go source files (sans package decl) of caller, callee | 
|  | want           string // expected new portion of caller file, or "error: regexp" | 
|  | } | 
|  |  | 
|  | func TestErrors(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Generic functions are not yet supported.", | 
|  | `func f[T any](x T) T { return x }`, | 
|  | `var _ = f(0)`, | 
|  | `error: type parameters are not yet supported`, | 
|  | }, | 
|  | { | 
|  | "Methods on generic types are not yet supported.", | 
|  | `type G[T any] struct{}; func (G[T]) f(x T) T { return x }`, | 
|  | `var _ = G[int]{}.f(0)`, | 
|  | `error: type parameters are not yet supported`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestBasics(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Basic", | 
|  | `func f(x int) int { return x }`, | 
|  | `var _ = f(0)`, | 
|  | `var _ = 0`, | 
|  | }, | 
|  | { | 
|  | "Empty body, no arg effects.", | 
|  | `func f(x, y int) {}`, | 
|  | `func _() { f(1, 2) }`, | 
|  | `func _() {}`, | 
|  | }, | 
|  | { | 
|  | "Empty body, some arg effects.", | 
|  | `func f(x, y, z int) {}`, | 
|  | `func _() { f(1, recover().(int), 3) }`, | 
|  | `func _() { _ = recover().(int) }`, | 
|  | }, | 
|  | { | 
|  | "Non-duplicable arguments are not substituted even if pure.", | 
|  | `func f(s string, i int) { print(s, s, i, i) }`, | 
|  | `func _() { f("hi", 0)  }`, | 
|  | `func _() { | 
|  | var s string = "hi" | 
|  | print(s, s, 0, 0) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Workaround for T(x) misformatting (#63362).", | 
|  | `func f(ch <-chan int) { <-ch }`, | 
|  | `func _(ch chan int) { f(ch) }`, | 
|  | `func _(ch chan int) { <-(<-chan int)(ch) }`, | 
|  | }, | 
|  | { | 
|  | // (a regression test for unnecessary braces) | 
|  | "In block elision, blank decls don't count when computing name conflicts.", | 
|  | `func f(x int) { var _ = x; var _ = 3 }`, | 
|  | `func _() { var _ = 1; f(2) }`, | 
|  | `func _() { | 
|  | var _ = 1 | 
|  | var _ = 2 | 
|  | var _ = 3 | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | // (a regression test for a missing conversion) | 
|  | "Implicit return conversions are inserted in expr-context reduction.", | 
|  | `func f(x int) error { return nil }`, | 
|  | `func _() { if err := f(0); err != nil {} }`, | 
|  | `func _() { | 
|  | if err := error(nil); err != nil { | 
|  | } | 
|  | }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestDuplicable(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Empty strings are duplicable.", | 
|  | `func f(s string) { print(s, s) }`, | 
|  | `func _() { f("")  }`, | 
|  | `func _() { print("", "") }`, | 
|  | }, | 
|  | { | 
|  | "Non-empty string literals are not duplicable.", | 
|  | `func f(s string) { print(s, s) }`, | 
|  | `func _() { f("hi")  }`, | 
|  | `func _() { | 
|  | var s string = "hi" | 
|  | print(s, s) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Empty array literals are duplicable.", | 
|  | `func f(a [2]int) { print(a, a) }`, | 
|  | `func _() { f([2]int{})  }`, | 
|  | `func _() { print([2]int{}, [2]int{}) }`, | 
|  | }, | 
|  | { | 
|  | "Non-empty array literals are not duplicable.", | 
|  | `func f(a [2]int) { print(a, a) }`, | 
|  | `func _() { f([2]int{1, 2})  }`, | 
|  | `func _() { | 
|  | var a [2]int = [2]int{1, 2} | 
|  | print(a, a) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Empty struct literals are duplicable.", | 
|  | `func f(s S) { print(s, s) }; type S struct { x int }`, | 
|  | `func _() { f(S{})  }`, | 
|  | `func _() { print(S{}, S{}) }`, | 
|  | }, | 
|  | { | 
|  | "Non-empty struct literals are not duplicable.", | 
|  | `func f(s S) { print(s, s) }; type S struct { x int }`, | 
|  | `func _() { f(S{x: 1})  }`, | 
|  | `func _() { | 
|  | var s S = S{x: 1} | 
|  | print(s, s) | 
|  | }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestExprStmtReduction(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "A call in an unrestricted ExprStmt may be replaced by the body stmts.", | 
|  | `func f() { var _ = len("") }`, | 
|  | `func _() { f() }`, | 
|  | `func _() { var _ = len("") }`, | 
|  | }, | 
|  | { | 
|  | "ExprStmts in the body of a switch case are unrestricted.", | 
|  | `func f() { x := 1; print(x) }`, | 
|  | `func _() { switch { case true: f() } }`, | 
|  | `func _() { | 
|  | switch { | 
|  | case true: | 
|  | x := 1 | 
|  | print(x) | 
|  | } | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "ExprStmts in the body of a select case are unrestricted.", | 
|  | `func f() { x := 1; print(x) }`, | 
|  | `func _() { select { default: f() } }`, | 
|  | `func _() { | 
|  | select { | 
|  | default: | 
|  | x := 1 | 
|  | print(x) | 
|  | } | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Some ExprStmt contexts are restricted to simple statements.", | 
|  | `func f() { var _ = len("") }`, | 
|  | `func _(cond bool) { if f(); cond {} }`, | 
|  | `func _(cond bool) { | 
|  | if func() { var _ = len("") }(); cond { | 
|  | } | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Braces must be preserved to avoid a name conflict (decl before).", | 
|  | `func f() { x := 1; print(x) }`, | 
|  | `func _() { x := 2; print(x); f() }`, | 
|  | `func _() { | 
|  | x := 2 | 
|  | print(x) | 
|  | { | 
|  | x := 1 | 
|  | print(x) | 
|  | } | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Braces must be preserved to avoid a name conflict (decl after).", | 
|  | `func f() { x := 1; print(x) }`, | 
|  | `func _() { f(); x := 2; print(x) }`, | 
|  | `func _() { | 
|  | { | 
|  | x := 1 | 
|  | print(x) | 
|  | } | 
|  | x := 2 | 
|  | print(x) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Braces must be preserved to avoid a forward jump across a decl.", | 
|  | `func f() { x := 1; print(x) }`, | 
|  | `func _() { goto label; f(); label: }`, | 
|  | `func _() { | 
|  | goto label | 
|  | { | 
|  | x := 1 | 
|  | print(x) | 
|  | } | 
|  | label: | 
|  | }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestPrecedenceParens(t *testing.T) { | 
|  | // Ensure that parens are inserted when (and only when) necessary | 
|  | // around the replacement for the call expression. (This is a special | 
|  | // case in the way the inliner uses a combination of AST formatting | 
|  | // for the call and text splicing for the rest of the file.) | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Multiplication in addition context (no parens).", | 
|  | `func f(x, y int) int { return x * y }`, | 
|  | `func _() { _ = 1 + f(2, 3) }`, | 
|  | `func _() { _ = 1 + 2*3 }`, | 
|  | }, | 
|  | { | 
|  | "Addition in multiplication context (parens).", | 
|  | `func f(x, y int) int { return x + y }`, | 
|  | `func _() { _ = 1 * f(2, 3) }`, | 
|  | `func _() { _ = 1 * (2 + 3) }`, | 
|  | }, | 
|  | { | 
|  | "Addition in negation context (parens).", | 
|  | `func f(x, y int) int { return x + y }`, | 
|  | `func _() { _ = -f(1, 2) }`, | 
|  | `func _() { _ = -(1 + 2) }`, | 
|  | }, | 
|  | { | 
|  | "Addition in call context (no parens).", | 
|  | `func f(x, y int) int { return x + y }`, | 
|  | `func _() { println(f(1, 2)) }`, | 
|  | `func _() { println(1 + 2) }`, | 
|  | }, | 
|  | { | 
|  | "Addition in slice operand context (parens).", | 
|  | `func f(x, y string) string { return x + y }`, | 
|  | `func _() { _ = f("x",  "y")[1:2] }`, | 
|  | `func _() { _ = ("x" + "y")[1:2] }`, | 
|  | }, | 
|  | { | 
|  | "String literal in slice operand context (no parens).", | 
|  | `func f(x string) string { return x }`, | 
|  | `func _() { _ = f("xy")[1:2] }`, | 
|  | `func _() { _ = "xy"[1:2] }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestSubstitution(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Arg to unref'd param can be eliminated if has no effects.", | 
|  | `func f(x, y int) {}; var global int`, | 
|  | `func _() { f(0, global) }`, | 
|  | `func _() {}`, | 
|  | }, | 
|  | { | 
|  | "But not if it may contain last reference to a caller local var.", | 
|  | `func f(int) {}`, | 
|  | `func _() { var local int; f(local) }`, | 
|  | `func _() { var local int; _ = local }`, | 
|  | }, | 
|  | { | 
|  | "Arguments that are used are detected", | 
|  | `func f(int) {}`, | 
|  | `func _() { var local int; _ = local; f(local) }`, | 
|  | `func _() { var local int; _ = local }`, | 
|  | }, | 
|  | { | 
|  | "Arguments that are used are detected", | 
|  | `func f(x, y int) { print(x) }`, | 
|  | `func _() { var z int; f(z, z) }`, | 
|  | `func _() { | 
|  | var z int | 
|  | var _ int = z | 
|  | print(z) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Function parameters are always used", | 
|  | `func f(int) {}`, | 
|  | `func _() { | 
|  | func(local int) { | 
|  | f(local) | 
|  | }(1) | 
|  | }`, | 
|  | `func _() { | 
|  | func(local int) { | 
|  |  | 
|  | }(1) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Regression test for detection of shadowing in nested functions.", | 
|  | `func f(x int) { _ = func() { y := 1; print(y); print(x) } }`, | 
|  | `func _(y int) { f(y) } `, | 
|  | `func _(y int) { | 
|  | var x int = y | 
|  | _ = func() { y := 1; print(y); print(x) } | 
|  | }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestTailCallStrategy(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Tail call.", | 
|  | `func f() int { return 1 }`, | 
|  | `func _() int { return f() }`, | 
|  | `func _() int { return 1 }`, | 
|  | }, | 
|  | { | 
|  | "Void tail call.", | 
|  | `func f() { println() }`, | 
|  | `func _() { f() }`, | 
|  | `func _() { println() }`, | 
|  | }, | 
|  | { | 
|  | "Void tail call with defer.", // => literalized | 
|  | `func f() { defer f(); println() }`, | 
|  | `func _() { f() }`, | 
|  | `func _() { func() { defer f(); println() }() }`, | 
|  | }, | 
|  | // Tests for issue #63336: | 
|  | { | 
|  | "Tail call with non-trivial return conversion (caller.sig = callee.sig).", | 
|  | `func f() error { if true { return nil } else { return e } }; var e struct{error}`, | 
|  | `func _() error { return f() }`, | 
|  | `func _() error { | 
|  | if true { | 
|  | return nil | 
|  | } else { | 
|  | return e | 
|  | } | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Tail call with non-trivial return conversion (caller.sig != callee.sig).", | 
|  | `func f() error { return E{} }; type E struct{error}`, | 
|  | `func _() any { return f() }`, | 
|  | `func _() any { return error(E{}) }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestSpreadCalls(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Edge case: cannot literalize spread method call.", | 
|  | `type I int | 
|  | func g() (I, I) | 
|  | func (r I) f(x, y I) I { | 
|  | defer g() // force literalization | 
|  | return x + y + r | 
|  | }`, | 
|  | `func _() I { return recover().(I).f(g()) }`, | 
|  | `error: can't yet inline spread call to method`, | 
|  | }, | 
|  | { | 
|  | "Spread argument evaluated for effect.", | 
|  | `func f(int, int) {}; func g() (int, int)`, | 
|  | `func _() { f(g())  }`, | 
|  | `func _() { _, _ = g() }`, | 
|  | }, | 
|  | { | 
|  | "Edge case: receiver and spread argument, both evaluated for effect.", | 
|  | `type T int; func (T) f(int, int) {}; func g() (int, int)`, | 
|  | `func _() { T(0).f(g())  }`, | 
|  | `func _() { | 
|  | var ( | 
|  | _    = T(0) | 
|  | _, _ = g() | 
|  | ) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Spread call in return (#63398).", | 
|  | `func f() (int, error) { return 0, nil }`, | 
|  | `func _() (int, error) { return f() }`, | 
|  | `func _() (int, error) { return 0, nil }`, | 
|  | }, | 
|  | { | 
|  | "Implicit return conversions defeat reduction of spread returns, for now.", | 
|  | `func f(x int) (_, _ error) { return nil, nil }`, | 
|  | `func _() { _, _ = f(0) }`, | 
|  | `func _() { _, _ = func() (_, _ error) { return nil, nil }() }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestVariadic(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Variadic cancellation (basic).", | 
|  | `func f(args ...any) { defer f(&args); println(args) }`, | 
|  | `func _(slice []any) { f(slice...) }`, | 
|  | `func _(slice []any) { func() { var args []any = slice; defer f(&args); println(args) }() }`, | 
|  | }, | 
|  | { | 
|  | "Variadic cancellation (literalization with parameter elimination).", | 
|  | `func f(args ...any) { defer f(); println(args) }`, | 
|  | `func _(slice []any) { f(slice...) }`, | 
|  | `func _(slice []any) { func() { defer f(); println(slice) }() }`, | 
|  | }, | 
|  | { | 
|  | "Variadic cancellation (reduction).", | 
|  | `func f(args ...any) { println(args) }`, | 
|  | `func _(slice []any) { f(slice...) }`, | 
|  | `func _(slice []any) { println(slice) }`, | 
|  | }, | 
|  | { | 
|  | "Variadic elimination (literalization).", | 
|  | `func f(x any, rest ...any) { defer println(x, rest) }`, // defer => literalization | 
|  | `func _() { f(1, 2, 3) }`, | 
|  | `func _() { func() { defer println(any(1), []any{2, 3}) }() }`, | 
|  | }, | 
|  | { | 
|  | "Variadic elimination (reduction).", | 
|  | `func f(x int, rest ...int) { println(x, rest) }`, | 
|  | `func _() { f(1, 2, 3) }`, | 
|  | `func _() { println(1, []int{2, 3}) }`, | 
|  | }, | 
|  | { | 
|  | "Spread call to variadic (1 arg, 1 param).", | 
|  | `func f(rest ...int) { println(rest) }; func g() (a, b int)`, | 
|  | `func _() { f(g()) }`, | 
|  | `func _() { func(rest ...int) { println(rest) }(g()) }`, | 
|  | }, | 
|  | { | 
|  | "Spread call to variadic (1 arg, 2 params).", | 
|  | `func f(x int, rest ...int) { println(x, rest) }; func g() (a, b int)`, | 
|  | `func _() { f(g()) }`, | 
|  | `func _() { func(x int, rest ...int) { println(x, rest) }(g()) }`, | 
|  | }, | 
|  | { | 
|  | "Spread call to variadic (1 arg, 3 params).", | 
|  | `func f(x, y int, rest ...int) { println(x, y, rest) }; func g() (a, b, c int)`, | 
|  | `func _() { f(g()) }`, | 
|  | `func _() { func(x, y int, rest ...int) { println(x, y, rest) }(g()) }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestParameterBindingDecl(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "IncDec counts as assignment.", | 
|  | `func f(x int) { x++ }`, | 
|  | `func _() { f(1) }`, | 
|  | `func _() { | 
|  | var x int = 1 | 
|  | x++ | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Binding declaration (x, y, z eliminated).", | 
|  | `func f(w, x, y any, z int) { println(w, y, z) }; func g(int) int`, | 
|  | `func _() { f(g(0), g(1), g(2), g(3)) }`, | 
|  | `func _() { | 
|  | var w, _ any = g(0), g(1) | 
|  | println(w, any(g(2)), g(3)) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Reduction of stmt-context call to { return exprs }, with substitution", | 
|  | `func f(ch chan int) int { return <-ch }; func g() chan int`, | 
|  | `func _() { f(g()) }`, | 
|  | `func _() { <-g() }`, | 
|  | }, | 
|  | { | 
|  | // Same again, with callee effects: | 
|  | "Binding decl in reduction of stmt-context call to { return exprs }", | 
|  | `func f(x int) int { return <-h(g(2), x) }; func g(int) int; func h(int, int) chan int`, | 
|  | `func _() { f(g(1)) }`, | 
|  | `func _() { | 
|  | var x int = g(1) | 
|  | <-h(g(2), x) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "No binding decl due to shadowing of int", | 
|  | `func f(int, y any, z int) { defer g(0); println(int, y, z) }; func g(int) int`, | 
|  | `func _() { f(g(1), g(2), g(3)) }`, | 
|  | `func _() { func(int, y any, z int) { defer g(0); println(int, y, z) }(g(1), g(2), g(3)) }`, | 
|  | }, | 
|  | { | 
|  | "An indirect method selection (*x).g acts as a read.", | 
|  | `func f(x *T, y any) any { return x.g(y) }; type T struct{}; func (T) g(x any) any { return x }`, | 
|  | `func _(x *T) { f(x, recover()) }`, | 
|  | `func _(x *T) { | 
|  | var y any = recover() | 
|  | x.g(y) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "A direct method selection x.g is pure.", | 
|  | `func f(x *T, y any) any { return x.g(y) }; type T struct{}; func (*T) g(x any) any { return x }`, | 
|  | `func _(x *T) { f(x, recover()) }`, | 
|  | `func _(x *T) { x.g(recover()) }`, | 
|  | }, | 
|  | { | 
|  | "Literalization can make use of a binding decl (all params).", | 
|  | `func f(x, y int) int { defer println(); return y + x }; func g(int) int`, | 
|  | `func _() { println(f(g(1), g(2))) }`, | 
|  | `func _() { println(func() int { var x, y int = g(1), g(2); defer println(); return y + x }()) }`, | 
|  | }, | 
|  | { | 
|  | "Literalization can make use of a binding decl (some params).", | 
|  | `func f(x, y int) int { z := y + x; defer println(); return z }; func g(int) int`, | 
|  | `func _() { println(f(g(1), g(2))) }`, | 
|  | `func _() { println(func() int { var x int = g(1); z := g(2) + x; defer println(); return z }()) }`, | 
|  | }, | 
|  | { | 
|  | "Literalization can't yet use of a binding decl if named results.", | 
|  | `func f(x, y int) (z int) { z = y + x; defer println(); return }; func g(int) int`, | 
|  | `func _() { println(f(g(1), g(2))) }`, | 
|  | `func _() { println(func(x int) (z int) { z = g(2) + x; defer println(); return }(g(1))) }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestEmbeddedFields(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Embedded fields in x.f method selection (direct).", | 
|  | `type T int; func (t T) f() { print(t) }; type U struct{ T }`, | 
|  | `func _(u U) { u.f() }`, | 
|  | `func _(u U) { print(u.T) }`, | 
|  | }, | 
|  | { | 
|  | "Embedded fields in x.f method selection (implicit *).", | 
|  | `type ( T int; U struct{*T}; V struct {U} ); func (t T) f() { print(t) }`, | 
|  | `func _(v V) { v.f() }`, | 
|  | `func _(v V) { print(*v.U.T) }`, | 
|  | }, | 
|  | { | 
|  | "Embedded fields in x.f method selection (implicit &).", | 
|  | `type ( T int; U struct{T}; V struct {U} ); func (t *T) f() { print(t) }`, | 
|  | `func _(v V) { v.f() }`, | 
|  | `func _(v V) { print(&v.U.T) }`, | 
|  | }, | 
|  | // Now the same tests again with T.f(recv). | 
|  | { | 
|  | "Embedded fields in T.f method selection.", | 
|  | `type T int; func (t T) f() { print(t) }; type U struct{ T }`, | 
|  | `func _(u U) { U.f(u) }`, | 
|  | `func _(u U) { print(u.T) }`, | 
|  | }, | 
|  | { | 
|  | "Embedded fields in T.f method selection (implicit *).", | 
|  | `type ( T int; U struct{*T}; V struct {U} ); func (t T) f() { print(t) }`, | 
|  | `func _(v V) { V.f(v) }`, | 
|  | `func _(v V) { print(*v.U.T) }`, | 
|  | }, | 
|  | { | 
|  | "Embedded fields in (*T).f method selection.", | 
|  | `type ( T int; U struct{T}; V struct {U} ); func (t *T) f() { print(t) }`, | 
|  | `func _(v V) { (*V).f(&v) }`, | 
|  | `func _(v V) { print(&(&v).U.T) }`, | 
|  | }, | 
|  | { | 
|  | // x is a single-assign var, and x.f does not load through a pointer | 
|  | // (despite types.Selection.Indirect=true), so x is pure. | 
|  | "No binding decl is required for recv in method-to-method calls.", | 
|  | `type T struct{}; func (x *T) f() { g(); print(*x) }; func g()`, | 
|  | `func (x *T) _() { x.f() }`, | 
|  | `func (x *T) _() { | 
|  | g() | 
|  | print(*x) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Same, with implicit &recv.", | 
|  | `type T struct{}; func (x *T) f() { g(); print(*x) }; func g()`, | 
|  | `func (x T) _() { x.f() }`, | 
|  | `func (x T) _() { | 
|  | { | 
|  | var x *T = &x | 
|  | g() | 
|  | print(*x) | 
|  | } | 
|  | }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestSubstitutionPreservesArgumentEffectOrder(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Arguments have effects, but parameters are evaluated in order.", | 
|  | `func f(a, b, c int) { print(a, b, c) }; func g(int) int`, | 
|  | `func _() { f(g(1), g(2), g(3)) }`, | 
|  | `func _() { print(g(1), g(2), g(3)) }`, | 
|  | }, | 
|  | { | 
|  | "Arguments have effects, and parameters are evaluated out of order.", | 
|  | `func f(a, b, c int) { print(a, c, b) }; func g(int) int`, | 
|  | `func _() { f(g(1), g(2), g(3)) }`, | 
|  | `func _() { | 
|  | var a, b int = g(1), g(2) | 
|  | print(a, g(3), b) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Pure arguments may commute with argument that have effects.", | 
|  | `func f(a, b, c int) { print(a, c, b) }; func g(int) int`, | 
|  | `func _() { f(g(1), 2, g(3)) }`, | 
|  | `func _() { print(g(1), g(3), 2) }`, | 
|  | }, | 
|  | { | 
|  | "Impure arguments may commute with each other.", | 
|  | `func f(a, b, c, d int) { print(a, c, b, d) }; func g(int) int; var x, y int`, | 
|  | `func _() { f(g(1), x, y, g(2)) }`, | 
|  | `func _() { print(g(1), y, x, g(2)) }`, | 
|  | }, | 
|  | { | 
|  | "Impure arguments do not commute with arguments that have effects (1)", | 
|  | `func f(a, b, c, d int) { print(a, c, b, d) }; func g(int) int; var x, y int`, | 
|  | `func _() { f(g(1), g(2), y, g(3)) }`, | 
|  | `func _() { | 
|  | var a, b int = g(1), g(2) | 
|  | print(a, y, b, g(3)) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Impure arguments do not commute with those that have effects (2).", | 
|  | `func f(a, b, c, d int) { print(a, c, b, d) }; func g(int) int; var x, y int`, | 
|  | `func _() { f(g(1), y, g(2), g(3)) }`, | 
|  | `func _() { | 
|  | var a, b int = g(1), y | 
|  | print(a, g(2), b, g(3)) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Callee effects commute with pure arguments.", | 
|  | `func f(a, b, c int) { print(a, c, recover().(int), b) }; func g(int) int`, | 
|  | `func _() { f(g(1), 2, g(3)) }`, | 
|  | `func _() { print(g(1), g(3), recover().(int), 2) }`, | 
|  | }, | 
|  | { | 
|  | "Callee reads may commute with impure arguments.", | 
|  | `func f(a, b int) { print(a, x, b) }; func g(int) int; var x, y int`, | 
|  | `func _() { f(g(1), y) }`, | 
|  | `func _() { print(g(1), x, y) }`, | 
|  | }, | 
|  | { | 
|  | "All impure parameters preceding a read hazard must be kept.", | 
|  | `func f(a, b, c int) { print(a, b, recover().(int), c) }; var x, y, z int`, | 
|  | `func _() { f(x, y, z) }`, | 
|  | `func _() { | 
|  | var c int = z | 
|  | print(x, y, recover().(int), c) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "All parameters preceding a write hazard must be kept.", | 
|  | `func f(a, b, c int) { print(a, b, recover().(int), c) }; func g(int) int; var x, y, z int`, | 
|  | `func _() { f(x, y, g(0))  }`, | 
|  | `func _() { | 
|  | var a, b, c int = x, y, g(0) | 
|  | print(a, b, recover().(int), c) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "[W1 R0 W2 W4 R3] -- test case for second iteration of effect loop", | 
|  | `func f(a, b, c, d, e int) { print(b, a, c, e, d) }; func g(int) int; var x, y int`, | 
|  | `func _() { f(x, g(1), g(2), y, g(3))  }`, | 
|  | `func _() { | 
|  | var a, b, c, d int = x, g(1), g(2), y | 
|  | print(b, a, c, g(3), d) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | // In this example, the set() call is rejected as a substitution | 
|  | // candidate due to a shadowing conflict (x). This must entail that the | 
|  | // selection x.y (R) is also rejected, because it is lower numbered. | 
|  | // | 
|  | // Incidentally this program (which panics when executed) illustrates | 
|  | // that although effects occur left-to-right, read operations such | 
|  | // as x.y are not ordered wrt writes, depending on the compiler. | 
|  | // Changing x.y to identity(x).y forces the ordering and avoids the panic. | 
|  | "Hazards with args already rejected (e.g. due to shadowing) are detected too.", | 
|  | `func f(x, y int) int { return x + y }; func set[T any](ptr *T, old, new T) int { println(old); *ptr = new; return 0; }`, | 
|  | `func _() { x := new(struct{ y int }); f(x.y, set(&x, x, nil)) }`, | 
|  | `func _() { | 
|  | x := new(struct{ y int }) | 
|  | { | 
|  | var x, y int = x.y, set(&x, x, nil) | 
|  | _ = x + y | 
|  | } | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | // Rejection of a later parameter for reasons other than callee | 
|  | // effects (e.g. escape) may create hazards with lower-numbered | 
|  | // parameters that require them to be rejected too. | 
|  | "Hazards with already eliminated parameters (variant)", | 
|  | `func f(x, y int) { _ = &y }; func g(int) int`, | 
|  | `func _() { f(g(1), g(2)) }`, | 
|  | `func _() { | 
|  | var _, y int = g(1), g(2) | 
|  | _ = &y | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | // In this case g(2) is rejected for substitution because it is | 
|  | // unreferenced but has effects, so parameter x must also be rejected | 
|  | // so that its argument v can be evaluated earlier in the binding decl. | 
|  | "Hazards with already eliminated parameters (unreferenced fx variant)", | 
|  | `func f(x, y int) { _ = x }; func g(int) int; var v int`, | 
|  | `func _() { f(v, g(2)) }`, | 
|  | `func _() { | 
|  | var x, _ int = v, g(2) | 
|  | _ = x | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Defer f() evaluates f() before unknown effects", | 
|  | `func f(int, y any, z int) { defer println(int, y, z) }; func g(int) int`, | 
|  | `func _() { f(g(1), g(2), g(3)) }`, | 
|  | `func _() { func() { defer println(any(g(1)), any(g(2)), g(3)) }() }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestNamedResultVars(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Stmt-context call to {return g()} that mentions named result.", | 
|  | `func f() (x int) { return g(x) }; func g(int) int`, | 
|  | `func _() { f() }`, | 
|  | `func _() { | 
|  | var x int | 
|  | g(x) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Ditto, with binding decl again.", | 
|  | `func f(y string) (x int) { return x+x+len(y+y) }`, | 
|  | `func _() { f(".") }`, | 
|  | `func _() { | 
|  | var ( | 
|  | y string = "." | 
|  | x int | 
|  | ) | 
|  | _ = x + x + len(y+y) | 
|  | }`, | 
|  | }, | 
|  |  | 
|  | { | 
|  | "Ditto, with binding decl (due to repeated y refs).", | 
|  | `func f(y string) (x string) { return x+y+y }`, | 
|  | `func _() { f(".") }`, | 
|  | `func _() { | 
|  | var ( | 
|  | y string = "." | 
|  | x string | 
|  | ) | 
|  | _ = x + y + y | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Stmt-context call to {return binary} that mentions named result.", | 
|  | `func f() (x int) { return x+x }`, | 
|  | `func _() { f() }`, | 
|  | `func _() { | 
|  | var x int | 
|  | _ = x + x | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Tail call to {return expr} that mentions named result.", | 
|  | `func f() (x int) { return x }`, | 
|  | `func _() int { return f() }`, | 
|  | `func _() int { return func() (x int) { return x }() }`, | 
|  | }, | 
|  | { | 
|  | "Tail call to {return} that implicitly reads named result.", | 
|  | `func f() (x int) { return }`, | 
|  | `func _() int { return f() }`, | 
|  | `func _() int { return func() (x int) { return }() }`, | 
|  | }, | 
|  | { | 
|  | "Spread-context call to {return expr} that mentions named result.", | 
|  | `func f() (x, y int) { return x, y }`, | 
|  | `func _() { var _, _ = f() }`, | 
|  | `func _() { var _, _ = func() (x, y int) { return x, y }() }`, | 
|  | }, | 
|  | { | 
|  | "Shadowing in binding decl for named results => literalization.", | 
|  | `func f(y string) (x y) { return x+x+len(y+y) }; type y = int`, | 
|  | `func _() { f(".") }`, | 
|  | `func _() { func(y string) (x y) { return x + x + len(y+y) }(".") }`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestSubstitutionPreservesParameterType(t *testing.T) { | 
|  | runTests(t, []testcase{ | 
|  | { | 
|  | "Substitution preserves argument type (#63193).", | 
|  | `func f(x int16) { y := x; _ = (*int16)(&y) }`, | 
|  | `func _() { f(1) }`, | 
|  | `func _() { | 
|  | y := int16(1) | 
|  | _ = (*int16)(&y) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Same, with non-constant (unnamed to named struct) conversion.", | 
|  | `func f(x T) { y := x; _ = (*T)(&y) }; type T struct{}`, | 
|  | `func _() { f(struct{}{}) }`, | 
|  | `func _() { | 
|  | y := T(struct{}{}) | 
|  | _ = (*T)(&y) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Same, with non-constant (chan to <-chan) conversion.", | 
|  | `func f(x T) { y := x; _ = (*T)(&y) }; type T = <-chan int; var ch chan int`, | 
|  | `func _() { f(ch) }`, | 
|  | `func _() { | 
|  | y := T(ch) | 
|  | _ = (*T)(&y) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Same, with untyped nil to typed nil conversion.", | 
|  | `func f(x *int) { y := x; _ = (**int)(&y) }`, | 
|  | `func _() { f(nil) }`, | 
|  | `func _() { | 
|  | y := (*int)(nil) | 
|  | _ = (**int)(&y) | 
|  | }`, | 
|  | }, | 
|  | { | 
|  | "Conversion of untyped int to named type is made explicit.", | 
|  | `type T int; func (x T) f() { x.g() }; func (T) g() {}`, | 
|  | `func _() { T.f(1) }`, | 
|  | `func _() { T(1).g() }`, | 
|  | }, | 
|  | { | 
|  | "Check for shadowing error on type used in the conversion.", | 
|  | `func f(x T) { _ = &x == (*T)(nil) }; type T int16`, | 
|  | `func _() { type T bool; f(1) }`, | 
|  | `error: T.*shadowed.*by.*type`, | 
|  | }, | 
|  | }) | 
|  | } | 
|  |  | 
|  | func runTests(t *testing.T, tests []testcase) { | 
|  | 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) | 
|  | } | 
|  |  | 
|  | // Parse caller file and find first call to f(). | 
|  | callerContent := "package p\n" + test.caller | 
|  | callerFile := mustParse("caller.go", callerContent) | 
|  | var call *ast.CallExpr | 
|  | ast.Inspect(callerFile, func(n ast.Node) bool { | 
|  | if n, ok := n.(*ast.CallExpr); ok { | 
|  | switch fun := n.Fun.(type) { | 
|  | case *ast.SelectorExpr: | 
|  | if fun.Sel.Name == funcName { | 
|  | call = n | 
|  | } | 
|  | case *ast.Ident: | 
|  | if fun.Name == funcName { | 
|  | call = n | 
|  | } | 
|  | } | 
|  | } | 
|  | return call == nil | 
|  | }) | 
|  | if call == nil { | 
|  | t.Fatalf("call to %s not found: %s", funcName, test.caller) | 
|  | } | 
|  |  | 
|  | // Type check both files as one package. | 
|  | 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{callerFile, calleeFile}, info) | 
|  | if err != nil { | 
|  | t.Fatal("transformation introduced type errors") | 
|  | } | 
|  |  | 
|  | // Analyze callee and inline call. | 
|  | doIt := func() ([]byte, error) { | 
|  | callee, err := inline.AnalyzeCallee(t.Logf, fset, pkg, info, decl, []byte(calleeContent)) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | if err := checkTranscode(callee); err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | caller := &inline.Caller{ | 
|  | Fset:    fset, | 
|  | Types:   pkg, | 
|  | Info:    info, | 
|  | File:    callerFile, | 
|  | Call:    call, | 
|  | Content: []byte(callerContent), | 
|  | } | 
|  | check := checkNoMutation(caller.File) | 
|  | defer check() | 
|  | return inline.Inline(t.Logf, caller, callee) | 
|  | } | 
|  | gotContent, err := doIt() | 
|  |  | 
|  | // Want error? | 
|  | if rest := strings.TrimPrefix(test.want, "error: "); rest != test.want { | 
|  | if err == nil { | 
|  | t.Fatalf("unexpected sucess: want error matching %q", rest) | 
|  | } | 
|  | msg := err.Error() | 
|  | if ok, err := regexp.MatchString(rest, msg); err != nil { | 
|  | t.Fatalf("invalid regexp: %v", err) | 
|  | } else if !ok { | 
|  | t.Fatalf("wrong error: %s (want match for %q)", msg, rest) | 
|  | } | 
|  | return | 
|  | } | 
|  |  | 
|  | // Want success. | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  |  | 
|  | // Compute a single-hunk line-based diff. | 
|  | srcLines := strings.Split(callerContent, "\n") | 
|  | gotLines := strings.Split(string(gotContent), "\n") | 
|  | for len(srcLines) > 0 && len(gotLines) > 0 && | 
|  | srcLines[0] == gotLines[0] { | 
|  | srcLines = srcLines[1:] | 
|  | gotLines = gotLines[1:] | 
|  | } | 
|  | for len(srcLines) > 0 && len(gotLines) > 0 && | 
|  | srcLines[len(srcLines)-1] == gotLines[len(gotLines)-1] { | 
|  | srcLines = srcLines[:len(srcLines)-1] | 
|  | gotLines = gotLines[:len(gotLines)-1] | 
|  | } | 
|  | got := strings.Join(gotLines, "\n") | 
|  |  | 
|  | if strings.TrimSpace(got) != strings.TrimSpace(test.want) { | 
|  | t.Fatalf("\nInlining this call:\t%s\nof this callee:    \t%s\nproduced:\n%s\nWant:\n\n%s", | 
|  | test.caller, | 
|  | test.callee, | 
|  | got, | 
|  | test.want) | 
|  | } | 
|  |  | 
|  | // Check that resulting code type-checks. | 
|  | newCallerFile := mustParse("newcaller.go", gotContent) | 
|  | if _, err := conf.Check("p", fset, []*ast.File{newCallerFile, calleeFile}, nil); err != nil { | 
|  | t.Fatalf("modified source failed to typecheck: <<%s>>", gotContent) | 
|  | } | 
|  | }) | 
|  | } | 
|  | } | 
|  |  | 
|  | // -- helpers -- | 
|  |  | 
|  | // checkNoMutation returns a function that, when called, | 
|  | // asserts that file was not modified since the checkNoMutation call. | 
|  | func checkNoMutation(file *ast.File) func() { | 
|  | pre := deepHash(file) | 
|  | return func() { | 
|  | post := deepHash(file) | 
|  | if pre != post { | 
|  | panic("Inline mutated caller.File") | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // checkTranscode replaces *callee by the results of gob-encoding and | 
|  | // then decoding it, to test that these operations are lossless. | 
|  | func checkTranscode(callee *inline.Callee) error { | 
|  | // Perform Gob transcoding so that it is exercised by the test. | 
|  | var enc bytes.Buffer | 
|  | if err := gob.NewEncoder(&enc).Encode(callee); err != nil { | 
|  | return fmt.Errorf("internal error: gob encoding failed: %v", err) | 
|  | } | 
|  | *callee = inline.Callee{} | 
|  | if err := gob.NewDecoder(&enc).Decode(callee); err != nil { | 
|  | return fmt.Errorf("internal error: gob decoding failed: %v", err) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // TODO(adonovan): publish this a helper (#61386). | 
|  | func extractTxtar(ar *txtar.Archive, dir string) error { | 
|  | for _, file := range ar.Files { | 
|  | name := filepath.Join(dir, file.Name) | 
|  | if err := os.MkdirAll(filepath.Dir(name), 0777); err != nil { | 
|  | return err | 
|  | } | 
|  | if err := os.WriteFile(name, file.Data, 0666); err != nil { | 
|  | return err | 
|  | } | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // deepHash computes a cryptographic hash of an ast.Node so that | 
|  | // if the data structure is mutated, the hash changes. | 
|  | // It assumes Go variables do not change address. | 
|  | // | 
|  | // TODO(adonovan): consider publishing this in the astutil package. | 
|  | // | 
|  | // TODO(adonovan): consider a variant that reports where in the tree | 
|  | // the mutation occurred (obviously at a cost in space). | 
|  | func deepHash(n ast.Node) any { | 
|  | seen := make(map[unsafe.Pointer]bool) // to break cycles | 
|  |  | 
|  | hasher := sha256.New() | 
|  | le := binary.LittleEndian | 
|  | writeUint64 := func(v uint64) { | 
|  | var bs [8]byte | 
|  | le.PutUint64(bs[:], v) | 
|  | hasher.Write(bs[:]) | 
|  | } | 
|  |  | 
|  | var visit func(reflect.Value) | 
|  | visit = func(v reflect.Value) { | 
|  | switch v.Kind() { | 
|  | case reflect.Ptr: | 
|  | ptr := v.UnsafePointer() | 
|  | writeUint64(uint64(uintptr(ptr))) | 
|  | if !v.IsNil() { | 
|  | if !seen[ptr] { | 
|  | seen[ptr] = true | 
|  | // Skip types we don't handle yet, but don't care about. | 
|  | switch v.Interface().(type) { | 
|  | case *ast.Scope: | 
|  | return // involves a map | 
|  | } | 
|  |  | 
|  | visit(v.Elem()) | 
|  | } | 
|  | } | 
|  |  | 
|  | case reflect.Struct: | 
|  | for i := 0; i < v.Type().NumField(); i++ { | 
|  | visit(v.Field(i)) | 
|  | } | 
|  |  | 
|  | case reflect.Slice: | 
|  | ptr := v.UnsafePointer() | 
|  | // We may encounter different slices at the same address, | 
|  | // so don't mark ptr as "seen". | 
|  | writeUint64(uint64(uintptr(ptr))) | 
|  | writeUint64(uint64(v.Len())) | 
|  | writeUint64(uint64(v.Cap())) | 
|  | for i := 0; i < v.Len(); i++ { | 
|  | visit(v.Index(i)) | 
|  | } | 
|  |  | 
|  | case reflect.Interface: | 
|  | if v.IsNil() { | 
|  | writeUint64(0) | 
|  | } else { | 
|  | rtype := reflect.ValueOf(v.Type()).UnsafePointer() | 
|  | writeUint64(uint64(uintptr(rtype))) | 
|  | visit(v.Elem()) | 
|  | } | 
|  |  | 
|  | case reflect.Array, reflect.Chan, reflect.Func, reflect.Map, reflect.UnsafePointer: | 
|  | panic(v) // unreachable in AST | 
|  |  | 
|  | default: // bool, string, number | 
|  | if v.Kind() == reflect.String { // proper framing | 
|  | writeUint64(uint64(v.Len())) | 
|  | } | 
|  | binary.Write(hasher, le, v.Interface()) | 
|  | } | 
|  | } | 
|  | visit(reflect.ValueOf(n)) | 
|  |  | 
|  | var hash [sha256.Size]byte | 
|  | hasher.Sum(hash[:0]) | 
|  | return hash | 
|  | } |