| // Copyright 2022 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 ssa_test | |
| import ( | |
| "go/ast" | |
| "go/parser" | |
| "go/token" | |
| "go/types" | |
| "testing" | |
| "golang.org/x/tools/go/ssa" | |
| "golang.org/x/tools/go/ssa/ssautil" | |
| ) | |
| // Tests that MethodValue returns the expected method. | |
| func TestMethodValue(t *testing.T) { | |
| input := ` | |
| package p | |
| type I interface{ M() } | |
| type S int | |
| func (S) M() {} | |
| type R[T any] struct{ S } | |
| var i I | |
| var s S | |
| var r R[string] | |
| func selections[T any]() { | |
| _ = i.M | |
| _ = s.M | |
| _ = r.M | |
| var v R[T] | |
| _ = v.M | |
| } | |
| ` | |
| // Parse the file. | |
| fset := token.NewFileSet() | |
| f, err := parser.ParseFile(fset, "input.go", input, 0) | |
| if err != nil { | |
| t.Error(err) | |
| return | |
| } | |
| // Build an SSA program from the parsed file. | |
| p, info, err := ssautil.BuildPackage(&types.Config{}, fset, | |
| types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions) | |
| if err != nil { | |
| t.Error(err) | |
| return | |
| } | |
| // Collect all of the *types.Selection in the function "selections". | |
| var selections []*types.Selection | |
| for _, decl := range f.Decls { | |
| if fn, ok := decl.(*ast.FuncDecl); ok && fn.Name.Name == "selections" { | |
| for _, stmt := range fn.Body.List { | |
| if assign, ok := stmt.(*ast.AssignStmt); ok { | |
| sel := assign.Rhs[0].(*ast.SelectorExpr) | |
| selections = append(selections, info.Selections[sel]) | |
| } | |
| } | |
| } | |
| } | |
| wants := map[string]string{ | |
| "method (p.S) M()": "(p.S).M", | |
| "method (p.R[string]) M()": "(p.R[string]).M", | |
| "method (p.I) M()": "nil", // interface | |
| "method (p.R[T]) M()": "nil", // parameterized | |
| } | |
| if len(wants) != len(selections) { | |
| t.Fatalf("Wanted %d selections. got %d", len(wants), len(selections)) | |
| } | |
| for _, selection := range selections { | |
| var got string | |
| if m := p.Prog.MethodValue(selection); m != nil { | |
| got = m.String() | |
| } else { | |
| got = "nil" | |
| } | |
| if want := wants[selection.String()]; want != got { | |
| t.Errorf("p.Prog.MethodValue(%s) expected %q. got %q", selection, want, got) | |
| } | |
| } | |
| } | |
| // TestMethodValueNoAlloc checks that MethodValue and LookupMethod | |
| // allocate nothing once the method has been created. | |
| func TestMethodValueNoAlloc(t *testing.T) { | |
| input := ` | |
| package p | |
| type S int | |
| func (S) Exported() {} | |
| func (S) unexported() {} | |
| func (*S) PtrExported() {} | |
| ` | |
| fset := token.NewFileSet() | |
| f, err := parser.ParseFile(fset, "input.go", input, 0) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| p, _, err := ssautil.BuildPackage(&types.Config{}, fset, | |
| types.NewPackage("p", ""), []*ast.File{f}, ssa.SanityCheckFunctions) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| prog := p.Prog | |
| S := p.Pkg.Scope().Lookup("S").Type() | |
| // Cover a value method, an unexported method (whose Id would | |
| // need a new string), and a wrapper (value method via *S). | |
| for _, tc := range []struct { | |
| T types.Type | |
| name string | |
| }{ | |
| {S, "Exported"}, | |
| {S, "unexported"}, | |
| {types.NewPointer(S), "Exported"}, | |
| {types.NewPointer(S), "PtrExported"}, | |
| } { | |
| sel := prog.MethodSets.MethodSet(tc.T).Lookup(p.Pkg, tc.name) | |
| if sel == nil { | |
| t.Fatalf("no method %s.%s", tc.T, tc.name) | |
| } | |
| fn := prog.MethodValue(sel) // create (and build) the method | |
| if fn == nil { | |
| t.Fatalf("MethodValue(%s.%s) = nil", tc.T, tc.name) | |
| } | |
| if allocs := testing.AllocsPerRun(100, func() { | |
| if prog.MethodValue(sel) != fn { | |
| t.Errorf("MethodValue(%s.%s) changed", tc.T, tc.name) | |
| } | |
| }); allocs > 0 { | |
| t.Errorf("MethodValue(%s.%s) allocated %v times per call", tc.T, tc.name, allocs) | |
| } | |
| if allocs := testing.AllocsPerRun(100, func() { | |
| if prog.LookupMethod(tc.T, p.Pkg, tc.name) != fn { | |
| t.Errorf("LookupMethod(%s.%s) changed", tc.T, tc.name) | |
| } | |
| }); allocs > 0 { | |
| t.Errorf("LookupMethod(%s.%s) allocated %v times per call", tc.T, tc.name, allocs) | |
| } | |
| } | |
| } |