blob: ee73ff9cfd5739896713fce2069267a29dec2530 [file] [log] [blame]
// Copyright 2014 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 typeutil_test
// TODO(adonovan):
// - test use of explicit hasher across two maps.
// - test hashcodes are consistent with equals for a range of types
// (e.g. all types generated by type-checking some body of real code).
import (
"go/ast"
"go/parser"
"go/token"
"go/types"
"testing"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/typeparams"
)
var (
tStr = types.Typ[types.String] // string
tPStr1 = types.NewPointer(tStr) // *string
tPStr2 = types.NewPointer(tStr) // *string, again
tInt = types.Typ[types.Int] // int
tChanInt1 = types.NewChan(types.RecvOnly, tInt) // <-chan int
tChanInt2 = types.NewChan(types.RecvOnly, tInt) // <-chan int, again
)
func checkEqualButNotIdentical(t *testing.T, x, y types.Type, comment string) {
if !types.Identical(x, y) {
t.Errorf("%s: not equal: %s, %s", comment, x, y)
}
if x == y {
t.Errorf("%s: identical: %v, %v", comment, x, y)
}
}
func TestAxioms(t *testing.T) {
checkEqualButNotIdentical(t, tPStr1, tPStr2, "tPstr{1,2}")
checkEqualButNotIdentical(t, tChanInt1, tChanInt2, "tChanInt{1,2}")
}
func TestMap(t *testing.T) {
var tmap *typeutil.Map
// All methods but Set are safe on on (*T)(nil).
tmap.Len()
tmap.At(tPStr1)
tmap.Delete(tPStr1)
tmap.KeysString()
_ = tmap.String()
tmap = new(typeutil.Map)
// Length of empty map.
if l := tmap.Len(); l != 0 {
t.Errorf("Len() on empty Map: got %d, want 0", l)
}
// At of missing key.
if v := tmap.At(tPStr1); v != nil {
t.Errorf("At() on empty Map: got %v, want nil", v)
}
// Deletion of missing key.
if tmap.Delete(tPStr1) {
t.Errorf("Delete() on empty Map: got true, want false")
}
// Set of new key.
if prev := tmap.Set(tPStr1, "*string"); prev != nil {
t.Errorf("Set() on empty Map returned non-nil previous value %s", prev)
}
// Now: {*string: "*string"}
// Length of non-empty map.
if l := tmap.Len(); l != 1 {
t.Errorf("Len(): got %d, want 1", l)
}
// At via insertion key.
if v := tmap.At(tPStr1); v != "*string" {
t.Errorf("At(): got %q, want \"*string\"", v)
}
// At via equal key.
if v := tmap.At(tPStr2); v != "*string" {
t.Errorf("At(): got %q, want \"*string\"", v)
}
// Iteration over sole entry.
tmap.Iterate(func(key types.Type, value interface{}) {
if key != tPStr1 {
t.Errorf("Iterate: key: got %s, want %s", key, tPStr1)
}
if want := "*string"; value != want {
t.Errorf("Iterate: value: got %s, want %s", value, want)
}
})
// Setion with key equal to present one.
if prev := tmap.Set(tPStr2, "*string again"); prev != "*string" {
t.Errorf("Set() previous value: got %s, want \"*string\"", prev)
}
// Setion of another association.
if prev := tmap.Set(tChanInt1, "<-chan int"); prev != nil {
t.Errorf("Set() previous value: got %s, want nil", prev)
}
// Now: {*string: "*string again", <-chan int: "<-chan int"}
want1 := "{*string: \"*string again\", <-chan int: \"<-chan int\"}"
want2 := "{<-chan int: \"<-chan int\", *string: \"*string again\"}"
if s := tmap.String(); s != want1 && s != want2 {
t.Errorf("String(): got %s, want %s", s, want1)
}
want1 = "{*string, <-chan int}"
want2 = "{<-chan int, *string}"
if s := tmap.KeysString(); s != want1 && s != want2 {
t.Errorf("KeysString(): got %s, want %s", s, want1)
}
// Keys().
I := types.Identical
switch k := tmap.Keys(); {
case I(k[0], tChanInt1) && I(k[1], tPStr1): // ok
case I(k[1], tChanInt1) && I(k[0], tPStr1): // ok
default:
t.Errorf("Keys(): got %v, want %s", k, want2)
}
if l := tmap.Len(); l != 2 {
t.Errorf("Len(): got %d, want 1", l)
}
// At via original key.
if v := tmap.At(tPStr1); v != "*string again" {
t.Errorf("At(): got %q, want \"*string again\"", v)
}
hamming := 1
tmap.Iterate(func(key types.Type, value interface{}) {
switch {
case I(key, tChanInt1):
hamming *= 2 // ok
case I(key, tPStr1):
hamming *= 3 // ok
}
})
if hamming != 6 {
t.Errorf("Iterate: hamming: got %d, want %d", hamming, 6)
}
if v := tmap.At(tChanInt2); v != "<-chan int" {
t.Errorf("At(): got %q, want \"<-chan int\"", v)
}
// Deletion with key equal to present one.
if !tmap.Delete(tChanInt2) {
t.Errorf("Delete() of existing key: got false, want true")
}
// Now: {*string: "*string again"}
if l := tmap.Len(); l != 1 {
t.Errorf("Len(): got %d, want 1", l)
}
// Deletion again.
if !tmap.Delete(tPStr2) {
t.Errorf("Delete() of existing key: got false, want true")
}
// Now: {}
if l := tmap.Len(); l != 0 {
t.Errorf("Len(): got %d, want %d", l, 0)
}
if s := tmap.String(); s != "{}" {
t.Errorf("Len(): got %q, want %q", s, "")
}
}
func TestMapGenerics(t *testing.T) {
if !typeparams.Enabled {
t.Skip("type params are not enabled at this Go version")
}
const src = `
package p
// Basic defined types.
type T1 int
type T2 int
// Identical methods.
func (T1) M(int) {}
func (T2) M(int) {}
// A constraint interface.
type C interface {
~int | string
}
type I interface {
}
// A generic type.
type G[P C] int
// Generic functions with identical signature.
func Fa1[P C](p P) {}
func Fa2[Q C](q Q) {}
// Fb1 and Fb2 are identical and should be mapped to the same entry, even if we
// map their arguments first.
func Fb1[P any](x *P) {
var y *P // Map this first.
_ = y
}
func Fb2[Q any](x *Q) {
}
// G1 and G2 are mutally recursive, and have identical methods.
type G1[P any] struct{
Field *G2[P]
}
func (G1[P]) M(G1[P], G2[P]) {}
type G2[Q any] struct{
Field *G1[Q]
}
func (G2[P]) M(G1[P], G2[P]) {}
// Method type expressions on different generic types are different.
var ME1 = G1[int].M
var ME2 = G2[int].M
// ME1Type should have identical type as ME1.
var ME1Type func(G1[int], G1[int], G2[int])
// Examples from issue #51314
type Constraint[T any] interface{}
func Foo[T Constraint[T]]() {}
func Fn[T1 ~*T2, T2 ~*T1](t1 T1, t2 T2) {}
// Bar and Baz are identical to Foo.
func Bar[P Constraint[P]]() {}
func Baz[Q any]() {} // The underlying type of Constraint[P] is any.
// But Quux is not.
func Quux[Q interface{ quux() }]() {}
type Issue56048_I interface{ m() interface { Issue56048_I } }
var Issue56048 = Issue56048_I.m
type Issue56048_Ib interface{ m() chan []*interface { Issue56048_Ib } }
var Issue56048b = Issue56048_Ib.m
`
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "p.go", src, 0)
if err != nil {
t.Fatal(err)
}
var conf types.Config
pkg, err := conf.Check("", fset, []*ast.File{file}, nil)
if err != nil {
t.Fatal(err)
}
// Collect types.
scope := pkg.Scope()
var (
T1 = scope.Lookup("T1").Type().(*types.Named)
T2 = scope.Lookup("T2").Type().(*types.Named)
T1M = T1.Method(0).Type()
T2M = T2.Method(0).Type()
G = scope.Lookup("G").Type()
GInt1 = instantiate(t, G, types.Typ[types.Int])
GInt2 = instantiate(t, G, types.Typ[types.Int])
GStr = instantiate(t, G, types.Typ[types.String])
C = scope.Lookup("C").Type()
CI = C.Underlying().(*types.Interface)
I = scope.Lookup("I").Type()
II = I.Underlying().(*types.Interface)
U = CI.EmbeddedType(0).(*typeparams.Union)
Fa1 = scope.Lookup("Fa1").Type().(*types.Signature)
Fa2 = scope.Lookup("Fa2").Type().(*types.Signature)
Fa1P = typeparams.ForSignature(Fa1).At(0)
Fa2Q = typeparams.ForSignature(Fa2).At(0)
Fb1 = scope.Lookup("Fb1").Type().(*types.Signature)
Fb1x = Fb1.Params().At(0).Type()
Fb1y = scope.Lookup("Fb1").(*types.Func).Scope().Lookup("y").Type()
Fb2 = scope.Lookup("Fb2").Type().(*types.Signature)
Fb2x = Fb2.Params().At(0).Type()
G1 = scope.Lookup("G1").Type().(*types.Named)
G1M = G1.Method(0).Type()
G1IntM1 = instantiate(t, G1, types.Typ[types.Int]).(*types.Named).Method(0).Type()
G1IntM2 = instantiate(t, G1, types.Typ[types.Int]).(*types.Named).Method(0).Type()
G1StrM = instantiate(t, G1, types.Typ[types.String]).(*types.Named).Method(0).Type()
G2 = scope.Lookup("G2").Type()
// See below.
// G2M = G2.Method(0).Type()
G2IntM = instantiate(t, G2, types.Typ[types.Int]).(*types.Named).Method(0).Type()
ME1 = scope.Lookup("ME1").Type()
ME1Type = scope.Lookup("ME1Type").Type()
ME2 = scope.Lookup("ME2").Type()
Constraint = scope.Lookup("Constraint").Type()
Foo = scope.Lookup("Foo").Type()
Fn = scope.Lookup("Fn").Type()
Bar = scope.Lookup("Foo").Type()
Baz = scope.Lookup("Foo").Type()
Quux = scope.Lookup("Quux").Type()
Issue56048 = scope.Lookup("Issue56048").Type()
Issue56048b = scope.Lookup("Issue56048b").Type()
)
tmap := new(typeutil.Map)
steps := []struct {
typ types.Type
name string
newEntry bool
}{
{T1, "T1", true},
{T2, "T2", true},
{G, "G", true},
{C, "C", true},
{CI, "CI", true},
{U, "U", true},
{I, "I", true},
{II, "II", true}, // should not be identical to CI
// Methods can be identical, even with distinct receivers.
{T1M, "T1M", true},
{T2M, "T2M", false},
// Identical instances should map to the same entry.
{GInt1, "GInt1", true},
{GInt2, "GInt2", false},
// ..but instantiating with different arguments should yield a new entry.
{GStr, "GStr", true},
// F1 and F2 should have identical signatures.
{Fa1, "F1", true},
{Fa2, "F2", false},
// The identity of P and Q should not have been affected by type parameter
// masking during signature hashing.
{Fa1P, "F1P", true},
{Fa2Q, "F2Q", true},
{Fb1y, "Fb1y", true},
{Fb1x, "Fb1x", false},
{Fb2x, "Fb2x", true},
{Fb1, "Fb1", true},
// Mapping elements of the function scope should not affect the identity of
// Fb2 or Fb1.
{Fb2, "Fb1", false},
{G1, "G1", true},
{G1M, "G1M", true},
{G2, "G2", true},
// See golang/go#49912: receiver type parameter names should be ignored
// when comparing method identity.
// {G2M, "G2M", false},
{G1IntM1, "G1IntM1", true},
{G1IntM2, "G1IntM2", false},
{G1StrM, "G1StrM", true},
{G2IntM, "G2IntM", false}, // identical to G1IntM1
{ME1, "ME1", true},
{ME1Type, "ME1Type", false},
{ME2, "ME2", true},
// See golang/go#51314: avoid infinite recursion on cyclic type constraints.
{Constraint, "Constraint", true},
{Foo, "Foo", true},
{Fn, "Fn", true},
{Bar, "Bar", false},
{Baz, "Baz", false},
{Quux, "Quux", true},
{Issue56048, "Issue56048", true}, // (not actually about generics)
{Issue56048b, "Issue56048b", true}, // (not actually about generics)
}
for _, step := range steps {
existing := tmap.At(step.typ)
if (existing == nil) != step.newEntry {
t.Errorf("At(%s) = %v, want new entry: %t", step.name, existing, step.newEntry)
}
tmap.Set(step.typ, step.name)
}
}
func instantiate(t *testing.T, origin types.Type, targs ...types.Type) types.Type {
inst, err := typeparams.Instantiate(nil, origin, targs, true)
if err != nil {
t.Fatal(err)
}
return inst
}