blob: 94dac3567df3ef215e9acd046ac82e7b38d218fd [file] [edit]
// Copyright 2026 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 (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"testing"
"golang.org/x/tools/go/ssa"
"golang.org/x/tools/internal/testenv"
)
// generic
const g = `
package p
type G[P any] struct {
x P
}
func (g G[P]) M[Q any](q Q) (P, Q) {
return g.x, q
}
func (g *G[P]) N[Q any](q Q) (P, Q) {
return g.x, q
}
func f() {
g := G[int]{ x: 42 }
%s
}
`
// non-generic
const n = `
package p
type N struct{}
func (N) M[P any](p P) P {
return p
}
func (*N) N[P any](p P) P {
return p
}
func f() {
n := N{}
%s
}
`
// TestGenericMethods ensures that generic methods are properly instantiated.
func TestGenericMethods(t *testing.T) {
testenv.NeedsGoCommand1Point(t, 27)
// TODO(mark): include implicit type arguments for direct callees?
// TODO(mark): ensure tpars == targs?
testCalls := []struct {
prog string
stmts string
callee string
tparams int // number of type parameters
targs int // number of type arguments
}{
// value receivers on generic types
{
prog: g,
stmts: "g.M[bool](true)",
callee: "(p.G[int]).M[bool]",
tparams: 2,
targs: 2,
},
{
// same as previous but with inferred type arguments
prog: g,
stmts: "g.M(true)",
callee: "(p.G[int]).M[bool]",
tparams: 2,
targs: 2,
},
{
prog: g,
stmts: "f := g.M[bool]; f(true)",
callee: "(p.G[int]).M[bool]$bound",
tparams: 0, // not propagated
targs: 1, // receiver eagerly specialized
},
{
// same as previous but with inferred type arguments
prog: g,
stmts: "var f func(bool) (int, bool); f = g.M; f(true)",
callee: "(p.G[int]).M[bool]$bound",
tparams: 0,
targs: 1,
},
{
prog: g,
stmts: "f := G[int].M[bool]; f(g, true)",
callee: "(p.G[int]).M[bool]$thunk",
tparams: 0, // not propagated
targs: 1, // receiver eagerly specialized
},
{
// same as previous but with inferred type arguments
prog: g,
stmts: "var f func(G[int], bool) (int, bool); f = (G[int]).M; f(g, true)",
callee: "(p.G[int]).M[bool]$thunk",
tparams: 0,
targs: 1,
},
// pointer receivers on generic types
{
prog: g,
stmts: "g.N[bool](true)",
callee: "(*p.G[int]).N[bool]",
tparams: 2,
targs: 2,
},
{
// same as previous but with inferred type arguments
prog: g,
stmts: "g.N(true)",
callee: "(*p.G[int]).N[bool]",
tparams: 2,
targs: 2,
},
{
prog: g,
stmts: "f := g.N[bool]; f(true)",
callee: "(*p.G[int]).N[bool]$bound",
tparams: 0, // not propagated
targs: 1, // receiver eagerly specialized
},
{
// same as previous but with inferred type arguments
prog: g,
stmts: "var f func(bool) (int, bool); f = g.N; f(true)",
callee: "(*p.G[int]).N[bool]$bound",
tparams: 0,
targs: 1,
},
{
prog: g,
stmts: "f := (*G[int]).N[bool]; f(&g, true)",
callee: "(*p.G[int]).N[bool]$thunk",
tparams: 0, // not propagated
targs: 1, // receiver eagerly specialized
},
{
// same as previous but with inferred type arguments
prog: g,
stmts: "var f func(*G[int], bool) (int, bool); f = (*G[int]).N[bool]; f(&g, true)",
callee: "(*p.G[int]).N[bool]$thunk",
tparams: 0,
targs: 1,
},
// value receivers on non-generic types
{
prog: n,
stmts: "n.M[bool](true)",
callee: "(p.N).M",
tparams: 1,
targs: 0,
},
{
// same as previous but with inferred type arguments
prog: n,
stmts: "n.M(true)",
callee: "(p.N).M",
tparams: 1,
targs: 0,
},
{
prog: n,
stmts: "f := n.M[bool]; f(true)",
callee: "(p.N).M[bool]$bound",
tparams: 0, // not propagated
targs: 1,
},
{
// same as previous but with inferred type arguments
prog: n,
stmts: "var f func(bool) bool; f = n.M; f(true)",
callee: "(p.N).M[bool]$bound",
tparams: 0,
targs: 1,
},
{
prog: n,
stmts: "f := N.M[bool]; f(n, true)",
callee: "(p.N).M[bool]$thunk",
tparams: 0, // not propagated
targs: 1,
},
{
// same as previous but with inferred type arguments
prog: n,
stmts: "var f func(N, bool) bool; f = N.M; f(n, true)",
callee: "(p.N).M[bool]$thunk",
tparams: 0,
targs: 1,
},
// pointer receivers on non-generic types
{
prog: n,
stmts: "n.N[bool](true)",
callee: "(*p.N).N",
tparams: 1,
targs: 0,
},
{
// same as previous but with inferred type arguments
prog: n,
stmts: "n.N(true)",
callee: "(*p.N).N",
tparams: 1,
targs: 0,
},
{
prog: n,
stmts: "f := n.N[bool]; f(true)",
callee: "(*p.N).N[bool]$bound",
tparams: 0, // not propagated
targs: 1,
},
{
// same as previous but with inferred type arguments
prog: n,
stmts: "var f func(bool) bool; f = n.N[bool]; f(true)",
callee: "(*p.N).N[bool]$bound",
tparams: 0,
targs: 1,
},
{
prog: n,
stmts: "f := (*N).N[bool]; f(&n, true)",
callee: "(*p.N).N[bool]$thunk",
tparams: 0, // not propagated
targs: 1,
},
{
// same as previous but with inferred type arguments
prog: n,
stmts: "var f func(*N, bool) bool; f = (*N).N; f(&n, true)",
callee: "(*p.N).N[bool]$thunk",
tparams: 0,
targs: 1,
},
// bounds of instantiated signatures which only differ by receiver pointerness
{
prog: g,
stmts: "f := g.M[bool]; _ = g.N[bool]; f(true)",
callee: "(p.G[int]).M[bool]$bound",
tparams: 0, // not propagated
targs: 1, // receiver eagerly specialized
},
{
prog: g,
stmts: "_ = g.M[bool]; f := g.N[bool]; f(true)",
callee: "(*p.G[int]).N[bool]$bound",
tparams: 0, // not propagated
targs: 1, // receiver eagerly specialized
},
{
prog: n,
stmts: "f := n.M[bool]; _ = n.N[bool]; f(true)",
callee: "(p.N).M[bool]$bound",
tparams: 0, // not propagated
targs: 1,
},
{
prog: n,
stmts: "_ = n.M[bool]; f := n.N[bool]; f(true)",
callee: "(*p.N).N[bool]$bound",
tparams: 0, // not propagated
targs: 1,
},
// thunks of instantiated signatures which only differ by receiver pointerness
{
prog: g,
stmts: "f := G[int].M[bool]; _ = (*G[int]).N[bool]; f(g, true)",
callee: "(p.G[int]).M[bool]$thunk",
tparams: 0, // not propagated
targs: 1, // receiver eagerly specialized
},
{
prog: g,
stmts: "_ = G[int].M[bool]; f := (*G[int]).N[bool]; f(&g, true)",
callee: "(*p.G[int]).N[bool]$thunk",
tparams: 0, // not propagated
targs: 1, // receiver eagerly specialized
},
{
prog: n,
stmts: "f := N.M[bool]; _ = (*N).N[bool]; f(n, true)",
callee: "(p.N).M[bool]$thunk",
tparams: 0, // not propagated
targs: 1,
},
{
prog: n,
stmts: "_ = N.M[bool]; f := (*N).N[bool]; f(&n, true)",
callee: "(*p.N).N[bool]$thunk",
tparams: 0,
targs: 1,
},
}
for _, want := range testCalls {
prog := fmt.Sprintf(want.prog, want.stmts)
calls := getCalls(t, build(t, prog))
if len(calls) != 1 {
t.Fatalf("too many direct calls for %s: got %d, want 1", prog, len(calls))
}
got := calls[0]
if got.callee != want.callee {
t.Errorf("wrong callee for %s: got %s, want %s", prog, got.callee, want.callee)
}
if got.tparams != want.tparams {
t.Errorf("wrong number of tparams for %s: got %d, want %d", prog, got.tparams, want.tparams)
}
if got.targs != want.targs {
t.Errorf("wrong number of targs for %s: got %d, want %d", prog, got.targs, want.targs)
}
}
testBuilds := []string{
// instantiate same signature with value / pointer receivers
fmt.Sprintf(g, "_ = g.M[bool]; _ = g.N[bool]"),
fmt.Sprintf(n, "_ = n.M[bool]; _ = n.N[bool]"),
}
for _, prog := range testBuilds {
build(t, prog)
}
}
type call struct {
callee string
tparams int
targs int
}
func build(t *testing.T, src string) *ssa.Package {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", src, 0)
if err != nil {
t.Fatal(err)
}
conf := types.Config{Importer: importer.Default()}
info := &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Instances: make(map[*ast.Ident]types.Instance),
}
pkg, err := conf.Check("p", fset, []*ast.File{f}, info)
if err != nil {
t.Fatal(err)
}
prog := ssa.NewProgram(fset, ssa.SanityCheckFunctions|ssa.InstantiateGenerics)
p := prog.CreatePackage(pkg, []*ast.File{f}, info, true)
prog.Build()
return p
}
func getCalls(t *testing.T, p *ssa.Package) []*call {
fun := p.Func("f")
if fun == nil {
t.Fatal("f not found")
}
var calls []*call
for _, block := range fun.Blocks {
for _, instr := range block.Instrs {
if c, ok := instr.(*ssa.Call); ok {
fn := c.Call.StaticCallee()
calls = append(calls, &call{
callee: fn.String(),
tparams: fn.TypeParams().Len(),
targs: len(fn.TypeArgs()),
})
}
// don't care about the extra call for MakeClosure
}
}
return calls
}