Skip to content

Commit

Permalink
go/types, types2: record all type instances, even duplicates
Browse files Browse the repository at this point in the history
Due to instance de-duplication, we were failing to record some type
instances in types.Info.Instances. Fix this by moving the instance
recording out of the resolver.

Fixes #51494

Change-Id: Iddd8989307d95886eedb321efa4ab98cd2b3573a
Reviewed-on: https://go-review.googlesource.com/c/go/+/390041
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
  • Loading branch information
findleyr committed Mar 7, 2022
1 parent 114d5de commit 43b09c0
Show file tree
Hide file tree
Showing 4 changed files with 286 additions and 242 deletions.
275 changes: 147 additions & 128 deletions src/cmd/compile/internal/types2/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"internal/testenv"
"reflect"
"regexp"
"sort"
"strings"
"testing"

Expand Down Expand Up @@ -403,69 +404,61 @@ func TestTypesInfo(t *testing.T) {
}

func TestInstanceInfo(t *testing.T) {
var tests = []struct {
src string
const lib = `package lib
func F[P any](P) {}
type T[P any] []P
`

type testInst struct {
name string
targs []string
typ string
}

var tests = []struct {
src string
instances []testInst // recorded instances in source order
}{
{`package p0; func f[T any](T) {}; func _() { f(42) }`,
`f`,
[]string{`int`},
`func(int)`,
[]testInst{{`f`, []string{`int`}, `func(int)`}},
},
{`package p1; func f[T any](T) T { panic(0) }; func _() { f('@') }`,
`f`,
[]string{`rune`},
`func(rune) rune`,
[]testInst{{`f`, []string{`rune`}, `func(rune) rune`}},
},
{`package p2; func f[T any](...T) T { panic(0) }; func _() { f(0i) }`,
`f`,
[]string{`complex128`},
`func(...complex128) complex128`,
[]testInst{{`f`, []string{`complex128`}, `func(...complex128) complex128`}},
},
{`package p3; func f[A, B, C any](A, *B, []C) {}; func _() { f(1.2, new(string), []byte{}) }`,
`f`,
[]string{`float64`, `string`, `byte`},
`func(float64, *string, []byte)`,
[]testInst{{`f`, []string{`float64`, `string`, `byte`}, `func(float64, *string, []byte)`}},
},
{`package p4; func f[A, B any](A, *B, ...[]B) {}; func _() { f(1.2, new(byte)) }`,
`f`,
[]string{`float64`, `byte`},
`func(float64, *byte, ...[]byte)`,
[]testInst{{`f`, []string{`float64`, `byte`}, `func(float64, *byte, ...[]byte)`}},
},

// we don't know how to translate these but we can type-check them
{`package q0; type T struct{}; func (T) m[P any](P) {}; func _(x T) { x.m(42) }`,
`m`,
[]string{`int`},
`func(int)`,
[]testInst{{`m`, []string{`int`}, `func(int)`}},
},
{`package q1; type T struct{}; func (T) m[P any](P) P { panic(0) }; func _(x T) { x.m(42) }`,
`m`,
[]string{`int`},
`func(int) int`,
[]testInst{{`m`, []string{`int`}, `func(int) int`}},
},
{`package q2; type T struct{}; func (T) m[P any](...P) P { panic(0) }; func _(x T) { x.m(42) }`,
`m`,
[]string{`int`},
`func(...int) int`,
[]testInst{{`m`, []string{`int`}, `func(...int) int`}},
},
{`package q3; type T struct{}; func (T) m[A, B, C any](A, *B, []C) {}; func _(x T) { x.m(1.2, new(string), []byte{}) }`,
`m`,
[]string{`float64`, `string`, `byte`},
`func(float64, *string, []byte)`,
[]testInst{{`m`, []string{`float64`, `string`, `byte`}, `func(float64, *string, []byte)`}},
},
{`package q4; type T struct{}; func (T) m[A, B any](A, *B, ...[]B) {}; func _(x T) { x.m(1.2, new(byte)) }`,
`m`,
[]string{`float64`, `byte`},
`func(float64, *byte, ...[]byte)`,
[]testInst{{`m`, []string{`float64`, `byte`}, `func(float64, *byte, ...[]byte)`}},
},

{`package r0; type T[P any] struct{}; func (_ T[P]) m[Q any](Q) {}; func _[P any](x T[P]) { x.m(42) }`,
`m`,
[]string{`int`},
`func(int)`,
{`package r0; type T[P1 any] struct{}; func (_ T[P2]) m[Q any](Q) {}; func _[P3 any](x T[P3]) { x.m(42) }`,
[]testInst{
{`T`, []string{`P2`}, `struct{}`},
{`T`, []string{`P3`}, `struct{}`},
{`m`, []string{`int`}, `func(int)`},
},
},
// TODO(gri) record method type parameters in syntax.FuncType so we can check this
// {`package r1; type T interface{ m[P any](P) }; func _(x T) { x.m(4.2) }`,
Expand All @@ -475,98 +468,112 @@ func TestInstanceInfo(t *testing.T) {
// },

{`package s1; func f[T any, P interface{*T}](x T) {}; func _(x string) { f(x) }`,
`f`,
[]string{`string`, `*string`},
`func(x string)`,
[]testInst{{`f`, []string{`string`, `*string`}, `func(x string)`}},
},
{`package s2; func f[T any, P interface{*T}](x []T) {}; func _(x []int) { f(x) }`,
`f`,
[]string{`int`, `*int`},
`func(x []int)`,
[]testInst{{`f`, []string{`int`, `*int`}, `func(x []int)`}},
},
{`package s3; type C[T any] interface{chan<- T}; func f[T any, P C[T]](x []T) {}; func _(x []int) { f(x) }`,
`f`,
[]string{`int`, `chan<- int`},
`func(x []int)`,
[]testInst{
{`C`, []string{`T`}, `interface{chan<- T}`},
{`f`, []string{`int`, `chan<- int`}, `func(x []int)`},
},
},
{`package s4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]](x []T) {}; func _(x []int) { f(x) }`,
`f`,
[]string{`int`, `chan<- int`, `chan<- []*chan<- int`},
`func(x []int)`,
[]testInst{
{`C`, []string{`T`}, `interface{chan<- T}`},
{`C`, []string{`[]*P`}, `interface{chan<- []*P}`},
{`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func(x []int)`},
},
},

{`package t1; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = f[string] }`,
`f`,
[]string{`string`, `*string`},
`func() string`,
[]testInst{{`f`, []string{`string`, `*string`}, `func() string`}},
},
{`package t2; func f[T any, P interface{*T}]() T { panic(0) }; func _() { _ = (f[string]) }`,
`f`,
[]string{`string`, `*string`},
`func() string`,
[]testInst{{`f`, []string{`string`, `*string`}, `func() string`}},
},
{`package t3; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = f[int] }`,
`f`,
[]string{`int`, `chan<- int`, `chan<- []*chan<- int`},
`func() []int`,
[]testInst{
{`C`, []string{`T`}, `interface{chan<- T}`},
{`C`, []string{`[]*P`}, `interface{chan<- []*P}`},
{`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func() []int`},
},
},
{`package t4; type C[T any] interface{chan<- T}; func f[T any, P C[T], Q C[[]*P]]() []T { return nil }; func _() { _ = (f[int]) }`,
`f`,
[]string{`int`, `chan<- int`, `chan<- []*chan<- int`},
`func() []int`,
[]testInst{
{`C`, []string{`T`}, `interface{chan<- T}`},
{`C`, []string{`[]*P`}, `interface{chan<- []*P}`},
{`f`, []string{`int`, `chan<- int`, `chan<- []*chan<- int`}, `func() []int`},
},
},

{`package i0; import "lib"; func _() { lib.F(42) }`,
`F`,
[]string{`int`},
`func(int)`,
[]testInst{{`F`, []string{`int`}, `func(int)`}},
},

{`package duplfunc0; func f[T any](T) {}; func _() { f(42); f("foo"); f[int](3) }`,
[]testInst{
{`f`, []string{`int`}, `func(int)`},
{`f`, []string{`string`}, `func(string)`},
{`f`, []string{`int`}, `func(int)`},
},
},
{`package duplfunc1; import "lib"; func _() { lib.F(42); lib.F("foo"); lib.F(3) }`,
[]testInst{
{`F`, []string{`int`}, `func(int)`},
{`F`, []string{`string`}, `func(string)`},
{`F`, []string{`int`}, `func(int)`},
},
},

{`package type0; type T[P interface{~int}] struct{ x P }; var _ T[int]`,
`T`,
[]string{`int`},
`struct{x int}`,
[]testInst{{`T`, []string{`int`}, `struct{x int}`}},
},
{`package type1; type T[P interface{~int}] struct{ x P }; var _ (T[int])`,
`T`,
[]string{`int`},
`struct{x int}`,
[]testInst{{`T`, []string{`int`}, `struct{x int}`}},
},
{`package type2; type T[P interface{~int}] struct{ x P }; var _ T[(int)]`,
`T`,
[]string{`int`},
`struct{x int}`,
[]testInst{{`T`, []string{`int`}, `struct{x int}`}},
},
{`package type3; type T[P1 interface{~[]P2}, P2 any] struct{ x P1; y P2 }; var _ T[[]int, int]`,
`T`,
[]string{`[]int`, `int`},
`struct{x []int; y int}`,
[]testInst{{`T`, []string{`[]int`, `int`}, `struct{x []int; y int}`}},
},
{`package type4; import "lib"; var _ lib.T[int]`,
`T`,
[]string{`int`},
`[]int`,
[]testInst{{`T`, []string{`int`}, `[]int`}},
},

{`package dupltype0; type T[P interface{~int}] struct{ x P }; var x T[int]; var y T[int]`,
[]testInst{
{`T`, []string{`int`}, `struct{x int}`},
{`T`, []string{`int`}, `struct{x int}`},
},
},
{`package dupltype1; type T[P ~int] struct{ x P }; func (r *T[Q]) add(z T[Q]) { r.x += z.x }`,
[]testInst{
{`T`, []string{`Q`}, `struct{x Q}`},
{`T`, []string{`Q`}, `struct{x Q}`},
},
},
{`package dupltype1; import "lib"; var x lib.T[int]; var y lib.T[int]; var z lib.T[string]`,
[]testInst{
{`T`, []string{`int`}, `[]int`},
{`T`, []string{`int`}, `[]int`},
{`T`, []string{`string`}, `[]string`},
},
},
}

for _, test := range tests {
const lib = `package lib
func F[P any](P) {}
type T[P any] []P
`

imports := make(testImporter)
conf := Config{Importer: imports}
instances := make(map[*syntax.Name]Instance)
uses := make(map[*syntax.Name]Object)
instMap := make(map[*syntax.Name]Instance)
useMap := make(map[*syntax.Name]Object)
makePkg := func(src string) *Package {
f, err := parseSrc("p.go", src)
if err != nil {
t.Fatal(err)
}
pkg, err := conf.Check("", []*syntax.File{f}, &Info{Instances: instances, Uses: uses})
pkg, err := conf.Check("", []*syntax.File{f}, &Info{Instances: instMap, Uses: useMap})
if err != nil {
t.Fatal(err)
}
Expand All @@ -576,58 +583,70 @@ type T[P any] []P
makePkg(lib)
pkg := makePkg(test.src)

// look for instance information
var targs []Type
var typ Type
for ident, inst := range instances {
if syntax.String(ident) == test.name {
for i := 0; i < inst.TypeArgs.Len(); i++ {
targs = append(targs, inst.TypeArgs.At(i))
t.Run(pkg.Name(), func(t *testing.T) {
// Sort instances in source order for stability.
instances := sortedInstances(instMap)
if got, want := len(instances), len(test.instances); got != want {
t.Fatalf("got %d instances, want %d", got, want)
}

// Pairwise compare with the expected instances.
for ii, inst := range instances {
var targs []Type
for i := 0; i < inst.Inst.TypeArgs.Len(); i++ {
targs = append(targs, inst.Inst.TypeArgs.At(i))
}
typ := inst.Inst.Type

testInst := test.instances[ii]
if got := inst.Name.Value; got != testInst.name {
t.Fatalf("got name %s, want %s", got, testInst.name)
}

if len(targs) != len(testInst.targs) {
t.Fatalf("got %d type arguments; want %d", len(targs), len(testInst.targs))
}
for i, targ := range targs {
if got := targ.String(); got != testInst.targs[i] {
t.Errorf("type argument %d: got %s; want %s", i, got, testInst.targs[i])
}
}
if got := typ.Underlying().String(); got != testInst.typ {
t.Errorf("package %s: got %s; want %s", pkg.Name(), got, testInst.typ)
}
typ = inst.Type

// Check that we can find the corresponding parameterized type.
ptype := uses[ident].Type()
// Verify the invariant that re-instantiating the corresponding generic
// type with TypeArgs results in an identical instance.
ptype := useMap[inst.Name].Type()
lister, _ := ptype.(interface{ TypeParams() *TypeParamList })
if lister == nil || lister.TypeParams().Len() == 0 {
t.Errorf("package %s: info.Types[%v] = %v, want parameterized type", pkg.Name(), ident, ptype)
continue
t.Fatalf("info.Types[%v] = %v, want parameterized type", inst.Name, ptype)
}

// Verify the invariant that re-instantiating the generic type with
// TypeArgs results in an equivalent type.
inst2, err := Instantiate(nil, ptype, targs, true)
if err != nil {
t.Errorf("Instantiate(%v, %v) failed: %v", ptype, targs, err)
}
if !Identical(inst.Type, inst2) {
t.Errorf("%v and %v are not identical", inst.Type, inst2)
if !Identical(inst.Inst.Type, inst2) {
t.Errorf("%v and %v are not identical", inst.Inst.Type, inst2)
}
break
}
}
if targs == nil {
t.Errorf("package %s: no instance information found for %s", pkg.Name(), test.name)
continue
}
})
}
}

// check that type arguments are correct
if len(targs) != len(test.targs) {
t.Errorf("package %s: got %d type arguments; want %d", pkg.Name(), len(targs), len(test.targs))
continue
}
for i, targ := range targs {
if got := targ.String(); got != test.targs[i] {
t.Errorf("package %s, %d. type argument: got %s; want %s", pkg.Name(), i, got, test.targs[i])
continue
}
}
type recordedInstance struct {
Name *syntax.Name
Inst Instance
}

// check that the types match
if got := typ.Underlying().String(); got != test.typ {
t.Errorf("package %s: got %s; want %s", pkg.Name(), got, test.typ)
}
func sortedInstances(m map[*syntax.Name]Instance) (instances []recordedInstance) {
for id, inst := range m {
instances = append(instances, recordedInstance{id, inst})
}
sort.Slice(instances, func(i, j int) bool {
return instances[i].Name.Pos().Cmp(instances[j].Name.Pos()) < 0
})
return instances
}

func TestDefsInfo(t *testing.T) {
Expand Down
Loading

0 comments on commit 43b09c0

Please sign in to comment.