blob: e875727d1a55faf9dea2e44118a7c2c7e04a227e [file] [log] [blame]
// Copyright 2018 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 typesinternal_test
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/importer"
"go/parser"
"go/token"
"go/types"
"testing"
ti "golang.org/x/tools/internal/typesinternal"
)
func TestClassifyCallAndUsed(t *testing.T) {
const src = `
package p
func g(int)
type A[T any] *T
func F[T any](T) {}
type S struct{ f func(int) }
func (S) g(int)
type I interface{ m(int) }
var (
z S
a struct{b struct{c S}}
f = g
m map[int]func()
n []func()
p *int
)
func tests[T int]() {
var zt T
g(1)
f(1)
println()
z.g(1) // a concrete method
a.b.c.g(1) // same
S.g(z, 1) // method expression
z.f(1) // struct field
I(nil).m(1) // interface method, then type conversion (preorder traversal)
m[0]() // a map
n[0]() // a slice
F[int](1) // instantiated function
F[T](zt) // generic function
func() {}() // function literal
_=[]byte("") // type expression
_=A[int](p) // instantiated type
_=T(1) // type param
// parenthesized forms
(z.g)(1)
(z).g(1)
// A[T](1) // generic type: illegal
}
`
fset := token.NewFileSet()
cfg := &types.Config{
Error: func(err error) { t.Fatal(err) },
Importer: importer.Default(),
}
info := ti.NewTypesInfo()
// parse
f, err := parser.ParseFile(fset, "classify.go", src, 0)
if err != nil {
t.Fatal(err)
}
// type-check
pkg, err := cfg.Check(f.Name.Name, fset, []*ast.File{f}, info)
if err != nil {
t.Fatal(err)
}
lookup := func(sym string) types.Object {
return pkg.Scope().Lookup(sym)
}
member := func(sym, fieldOrMethod string) types.Object {
obj, _, _ := types.LookupFieldOrMethod(lookup(sym).Type(), false, pkg, fieldOrMethod)
return obj
}
printlnObj := types.Universe.Lookup("println")
typeParam := lookup("tests").Type().(*types.Signature).TypeParams().At(0).Obj()
// Expected Calls are in the order of CallExprs at the end of src, above.
wants := []struct {
kind ti.CallKind
usedObj types.Object // the object obtained from the result of UsedIdent
}{
{ti.CallStatic, lookup("g")}, // g
{ti.CallDynamic, lookup("f")}, // f
{ti.CallBuiltin, printlnObj}, // println
{ti.CallStatic, member("S", "g")}, // z.g
{ti.CallStatic, member("S", "g")}, // a.b.c.g
{ti.CallStatic, member("S", "g")}, // S.g(z, 1)
{ti.CallDynamic, member("z", "f")}, // z.f
{ti.CallInterface, member("I", "m")}, // I(nil).m
{ti.CallConversion, lookup("I")}, // I(nil)
{ti.CallDynamic, nil}, // m[0]
{ti.CallDynamic, nil}, // n[0]
{ti.CallStatic, lookup("F")}, // F[int]
{ti.CallStatic, lookup("F")}, // F[T]
{ti.CallDynamic, nil}, // f(){}
{ti.CallConversion, nil}, // []byte
{ti.CallConversion, lookup("A")}, // A[int]
{ti.CallConversion, typeParam}, // T
{ti.CallStatic, member("S", "g")}, // (z.g)
{ti.CallStatic, member("S", "g")}, // (z).g
}
i := 0
ast.Inspect(f, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
if i >= len(wants) {
t.Fatal("more calls than wants")
}
var buf bytes.Buffer
if err := format.Node(&buf, fset, n); err != nil {
t.Fatal(err)
}
prefix := fmt.Sprintf("%s (#%d)", buf.String(), i)
gotKind := ti.ClassifyCall(info, call)
want := wants[i]
if gotKind != want.kind {
t.Errorf("%s kind: got %s, want %s", prefix, gotKind, want.kind)
}
w := want.usedObj
if g := info.Uses[ti.UsedIdent(info, call.Fun)]; g != w {
t.Errorf("%s used obj: got %v (%[2]T), want %v", prefix, g, w)
}
i++
}
return true
})
if i != len(wants) {
t.Fatal("more wants than calls")
}
}