| // 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. |
| |
| //go:build go1.22 |
| // +build go1.22 |
| |
| package ssa_test |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/parser" |
| "go/token" |
| "go/types" |
| "testing" |
| |
| "golang.org/x/tools/go/expect" |
| "golang.org/x/tools/go/ssa" |
| "golang.org/x/tools/go/ssa/ssautil" |
| "golang.org/x/tools/internal/testenv" |
| ) |
| |
| // TestMultipleGoversions tests that globals initialized to equivalent |
| // function literals are compiled based on the different GoVersion in each file. |
| func TestMultipleGoversions(t *testing.T) { |
| var contents = map[string]string{ |
| "post.go": ` |
| //go:build go1.22 |
| package p |
| |
| var distinct = func(l []int) { |
| for i := range l { |
| print(&i) |
| } |
| } |
| `, |
| "pre.go": ` |
| package p |
| |
| var same = func(l []int) { |
| for i := range l { |
| print(&i) |
| } |
| } |
| `, |
| } |
| |
| fset := token.NewFileSet() |
| var files []*ast.File |
| for _, fname := range []string{"post.go", "pre.go"} { |
| file, err := parser.ParseFile(fset, fname, contents[fname], 0) |
| if err != nil { |
| t.Fatal(err) |
| } |
| files = append(files, file) |
| } |
| |
| pkg := types.NewPackage("p", "") |
| conf := &types.Config{Importer: nil, GoVersion: "go1.21"} |
| p, _, err := ssautil.BuildPackage(conf, fset, pkg, files, ssa.SanityCheckFunctions) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Test that global is initialized to a function literal that was |
| // compiled to have the expected for loop range variable lifetime for i. |
| for _, test := range []struct { |
| global *ssa.Global |
| want string // basic block to []*ssa.Alloc. |
| }{ |
| {p.Var("same"), "map[entry:[new int (i)]]"}, // i is allocated in the entry block. |
| {p.Var("distinct"), "map[rangeindex.body:[new int (i)]]"}, // i is allocated in the body block. |
| } { |
| // Find the function the test.name global is initialized to. |
| var fn *ssa.Function |
| for _, b := range p.Func("init").Blocks { |
| for _, instr := range b.Instrs { |
| if s, ok := instr.(*ssa.Store); ok && s.Addr == test.global { |
| fn, _ = s.Val.(*ssa.Function) |
| } |
| } |
| } |
| if fn == nil { |
| t.Fatalf("Failed to find *ssa.Function for initial value of global %s", test.global) |
| } |
| |
| allocs := make(map[string][]string) // block comments -> []Alloc |
| for _, b := range fn.Blocks { |
| for _, instr := range b.Instrs { |
| if a, ok := instr.(*ssa.Alloc); ok { |
| allocs[b.Comment] = append(allocs[b.Comment], a.String()) |
| } |
| } |
| } |
| if got := fmt.Sprint(allocs); got != test.want { |
| t.Errorf("[%s:=%s] expected the allocations to be in the basic blocks %q, got %q", test.global, fn, test.want, got) |
| } |
| } |
| } |
| |
| const rangeOverIntSrc = ` |
| package p |
| |
| type I uint8 |
| |
| func noKey(x int) { |
| for range x { |
| // does not crash |
| } |
| } |
| |
| func untypedConstantOperand() { |
| for i := range 10 { |
| print(i) /*@ types("int")*/ |
| } |
| } |
| |
| func unsignedOperand(x uint64) { |
| for i := range x { |
| print(i) /*@ types("uint64")*/ |
| } |
| } |
| |
| func namedOperand(x I) { |
| for i := range x { |
| print(i) /*@ types("p.I")*/ |
| } |
| } |
| |
| func typeparamOperand[T int](x T) { |
| for i := range x { |
| print(i) /*@ types("T")*/ |
| } |
| } |
| |
| func assignment(x I) { |
| var k I |
| for k = range x { |
| print(k) /*@ types("p.I")*/ |
| } |
| } |
| ` |
| |
| // TestRangeOverInt tests that, in a range-over-int (#61405), |
| // the type of each range var v (identified by print(v) calls) |
| // has the expected type. |
| func TestRangeOverInt(t *testing.T) { |
| testenv.NeedsGoExperiment(t, "range") |
| |
| fset := token.NewFileSet() |
| f, err := parser.ParseFile(fset, "p.go", rangeOverIntSrc, parser.ParseComments) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| pkg := types.NewPackage("p", "") |
| conf := &types.Config{} |
| p, _, err := ssautil.BuildPackage(conf, fset, pkg, []*ast.File{f}, ssa.SanityCheckFunctions) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Collect all notes in f, i.e. comments starting with "//@ types". |
| notes, err := expect.ExtractGo(fset, f) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| // Collect calls to the built-in print function. |
| probes := callsTo(p, "print") |
| expectations := matchNotes(fset, notes, probes) |
| |
| for call := range probes { |
| if expectations[call] == nil { |
| t.Errorf("Unmatched call: %v @ %s", call, fset.Position(call.Pos())) |
| } |
| } |
| |
| // Check each expectation. |
| for call, note := range expectations { |
| var args []string |
| for _, a := range call.Args { |
| args = append(args, a.Type().String()) |
| } |
| if got, want := fmt.Sprint(args), fmt.Sprint(note.Args); got != want { |
| at := fset.Position(call.Pos()) |
| t.Errorf("%s: arguments to print had types %s, want %s", at, got, want) |
| logFunction(t, probes[call]) |
| } |
| } |
| } |