blob: d1f13fa7f5362beae4408529ee38a87d5d40d1d6 [file] [log] [blame]
// Copyright 2021 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 typeparams_test
import (
"go/ast"
"go/parser"
"go/token"
"go/types"
"testing"
"golang.org/x/tools/internal/testenv"
. "golang.org/x/tools/internal/typeparams"
)
func TestGetIndexExprData(t *testing.T) {
x := &ast.Ident{}
i := &ast.Ident{}
want := &IndexListExpr{X: x, Lbrack: 1, Indices: []ast.Expr{i}, Rbrack: 2}
tests := map[ast.Node]bool{
&ast.IndexExpr{X: x, Lbrack: 1, Index: i, Rbrack: 2}: true,
want: true,
&ast.Ident{}: false,
}
for n, isIndexExpr := range tests {
X, lbrack, indices, rbrack := UnpackIndexExpr(n)
if got := X != nil; got != isIndexExpr {
t.Errorf("UnpackIndexExpr(%v) = %v, _, _, _; want nil: %t", n, x, !isIndexExpr)
}
if X == nil {
continue
}
if X != x || lbrack != 1 || indices[0] != i || rbrack != 2 {
t.Errorf("UnpackIndexExprData(%v) = %v, %v, %v, %v; want %+v", n, x, lbrack, indices, rbrack, want)
}
}
}
func TestOriginMethodRecursive(t *testing.T) {
testenv.NeedsGo1Point(t, 18)
src := `package p
type N[A any] int
func (r N[B]) m() { r.m(); r.n() }
func (r *N[C]) n() { }
`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", src, 0)
if err != nil {
t.Fatal(err)
}
info := types.Info{
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
}
var conf types.Config
if _, err := conf.Check("p", fset, []*ast.File{f}, &info); err != nil {
t.Fatal(err)
}
// Collect objects from types.Info.
var m, n *types.Func // the 'origin' methods in Info.Defs
var mm, mn *types.Func // the methods used in the body of m
for _, decl := range f.Decls {
fdecl, ok := decl.(*ast.FuncDecl)
if !ok {
continue
}
def := info.Defs[fdecl.Name].(*types.Func)
switch fdecl.Name.Name {
case "m":
m = def
ast.Inspect(fdecl.Body, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
sel := call.Fun.(*ast.SelectorExpr)
use := info.Uses[sel.Sel].(*types.Func)
switch sel.Sel.Name {
case "m":
mm = use
case "n":
mn = use
}
}
return true
})
case "n":
n = def
}
}
tests := []struct {
name string
input, want *types.Func
}{
{"declared m", m, m},
{"declared n", n, n},
{"used m", mm, m},
{"used n", mn, n},
}
for _, test := range tests {
if got := OriginMethod(test.input); got != test.want {
t.Errorf("OriginMethod(%q) = %v, want %v", test.name, test.input, test.want)
}
}
}
func TestOriginMethodUses(t *testing.T) {
testenv.NeedsGo1Point(t, 18)
tests := []string{
`type T interface { m() }; func _(t T) { t.m() }`,
`type T[P any] interface { m() P }; func _[A any](t T[A]) { t.m() }`,
`type T[P any] interface { m() P }; func _(t T[int]) { t.m() }`,
`type T[P any] int; func (r T[A]) m() { r.m() }`,
`type T[P any] int; func (r *T[A]) m() { r.m() }`,
`type T[P any] int; func (r *T[A]) m() {}; func _(t T[int]) { t.m() }`,
`type T[P any] int; func (r *T[A]) m() {}; func _[A any](t T[A]) { t.m() }`,
}
for _, src := range tests {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", "package p; "+src, 0)
if err != nil {
t.Fatal(err)
}
info := types.Info{
Uses: make(map[*ast.Ident]types.Object),
}
var conf types.Config
pkg, err := conf.Check("p", fset, []*ast.File{f}, &info)
if err != nil {
t.Fatal(err)
}
// Look up func T.m.
T := pkg.Scope().Lookup("T").Type()
obj, _, _ := types.LookupFieldOrMethod(T, true, pkg, "m")
m := obj.(*types.Func)
// Assert that the origin of each t.m() call is p.T.m.
ast.Inspect(f, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
sel := call.Fun.(*ast.SelectorExpr)
use := info.Uses[sel.Sel].(*types.Func)
orig := OriginMethod(use)
if orig != m {
t.Errorf("%s:\nUses[%v] = %v, want %v", src, types.ExprString(sel), use, m)
}
}
return true
})
}
}
// Issue #60628 was a crash in gopls caused by inconsistency (#60634) between
// LookupFieldOrMethod and NewFileSet for methods with an illegal
// *T receiver type, where T itself is a pointer.
// This is a regression test for the workaround in OriginMethod.
func TestOriginMethod60628(t *testing.T) {
const src = `package p; type T[P any] *int; func (r *T[A]) f() {}`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", src, 0)
if err != nil {
t.Fatal(err)
}
// Expect type error: "invalid receiver type T[A] (pointer or interface type)".
info := types.Info{
Uses: make(map[*ast.Ident]types.Object),
}
var conf types.Config
pkg, _ := conf.Check("p", fset, []*ast.File{f}, &info) // error expected
if pkg == nil {
t.Fatal("no package")
}
// Look up methodset of *T.
T := pkg.Scope().Lookup("T").Type()
mset := types.NewMethodSet(types.NewPointer(T))
if mset.Len() == 0 {
t.Errorf("NewMethodSet(*T) is empty")
}
for i := 0; i < mset.Len(); i++ {
sel := mset.At(i)
m := sel.Obj().(*types.Func)
// TODO(adonovan): check the consistency property required to fix #60634.
if false {
m2, _, _ := types.LookupFieldOrMethod(T, true, m.Pkg(), m.Name())
if m2 != m {
t.Errorf("LookupFieldOrMethod(%v, indirect=true, %v) = %v, want %v",
T, m, m2, m)
}
}
// Check the workaround.
if OriginMethod(m) == nil {
t.Errorf("OriginMethod(%v) = nil", m)
}
}
}
func TestGenericAssignableTo(t *testing.T) {
testenv.NeedsGo1Point(t, 18)
tests := []struct {
src string
want bool
}{
// The inciting issue: golang/go#50887.
{`
type T[P any] interface {
Accept(P)
}
type V[Q any] struct {
Element Q
}
func (c V[Q]) Accept(q Q) { c.Element = q }
`, true},
// Various permutations on constraints and signatures.
{`type T[P ~int] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true},
{`type T[P int] interface{ A(P) }; type V[Q ~int] int; func (V[Q]) A(Q) {}`, false},
{`type T[P int|string] interface{ A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, true},
{`type T[P any] interface{ A(P) }; type V[Q any] int; func (V[Q]) A(Q, Q) {}`, false},
{`type T[P any] interface{ int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, false},
// Various structural restrictions on T.
{`type T[P any] interface{ ~int; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true},
{`type T[P any] interface{ ~int|string; A(P) }; type V[Q any] int; func (V[Q]) A(Q) {}`, true},
{`type T[P any] interface{ int; A(P) }; type V[Q int] int; func (V[Q]) A(Q) {}`, false},
// Various recursive constraints.
{`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ f *Q }] int; func (V[Q]) A(Q) {}`, true},
{`type T[P ~struct{ f *P }] interface{ A(P) }; type V[Q ~struct{ g *Q }] int; func (V[Q]) A(Q) {}`, false},
{`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
{`type T[P ~*X, X any] interface{ A(P) X }; type V[Q ~**Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false},
{`type T[P, X any] interface{ A(P) X }; type V[Q ~*Y, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
{`type T[P ~*X, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, false},
{`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Q) (y Y) { return }`, true},
// In this test case, we reverse the type parameters in the signature of V.A
{`type T[P, X any] interface{ A(P) X }; type V[Q, Y any] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false},
// It would be nice to return true here: V can only be instantiated with
// [int, int], so the identity of the type parameters should not matter.
{`type T[P, X any] interface{ A(P) X }; type V[Q, Y int] int; func (V[Q, Y]) A(Y) (y Q) { return }`, false},
}
for _, test := range tests {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", "package p; "+test.src, 0)
if err != nil {
t.Fatalf("%s:\n%v", test.src, err)
}
var conf types.Config
pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
if err != nil {
t.Fatalf("%s:\n%v", test.src, err)
}
V := pkg.Scope().Lookup("V").Type()
T := pkg.Scope().Lookup("T").Type()
if types.AssignableTo(V, T) {
t.Fatal("AssignableTo")
}
if got := GenericAssignableTo(nil, V, T); got != test.want {
t.Fatalf("%s:\nGenericAssignableTo(%v, %v) = %v, want %v", test.src, V, T, got, test.want)
}
}
}