// Copyright 2022 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 types_test import ( "go/ast" "go/token" "testing" . "go/types" ) func BenchmarkNamed(b *testing.B) { const src = ` package p type T struct { P int } func (T) M(int) {} func (T) N() (i int) { return } type G[P any] struct { F P } func (G[P]) M(P) {} func (G[P]) N() (p P) { return } type Inst = G[int] ` pkg := mustTypecheck(src, nil, nil) var ( T = pkg.Scope().Lookup("T").Type() G = pkg.Scope().Lookup("G").Type() SrcInst = pkg.Scope().Lookup("Inst").Type() UserInst = mustInstantiate(b, G, Typ[Int]) ) tests := []struct { name string typ Type }{ {"nongeneric", T}, {"generic", G}, {"src instance", SrcInst}, {"user instance", UserInst}, } b.Run("Underlying", func(b *testing.B) { for _, test := range tests { b.Run(test.name, func(b *testing.B) { // Access underlying once, to trigger any lazy calculation. _ = test.typ.Underlying() b.ResetTimer() for i := 0; i < b.N; i++ { _ = test.typ.Underlying() } }) } }) b.Run("NewMethodSet", func(b *testing.B) { for _, test := range tests { b.Run(test.name, func(b *testing.B) { // Access underlying once, to trigger any lazy calculation. _ = NewMethodSet(test.typ) b.ResetTimer() for i := 0; i < b.N; i++ { _ = NewMethodSet(test.typ) } }) } }) } func mustInstantiate(tb testing.TB, orig Type, targs ...Type) Type { inst, err := Instantiate(nil, orig, targs, true) if err != nil { tb.Fatal(err) } return inst } // Test that types do not expand infinitely, as in go.dev/issue/52715. func TestFiniteTypeExpansion(t *testing.T) { const src = ` package p type Tree[T any] struct { *Node[T] } func (*Tree[R]) N(r R) R { return r } type Node[T any] struct { *Tree[T] } func (Node[Q]) M(Q) {} type Inst = *Tree[int] ` fset := token.NewFileSet() f := mustParse(fset, src) pkg := NewPackage("p", f.Name.Name) if err := NewChecker(nil, fset, pkg, nil).Files([]*ast.File{f}); err != nil { t.Fatal(err) } firstFieldType := func(n *Named) *Named { return n.Underlying().(*Struct).Field(0).Type().(*Pointer).Elem().(*Named) } Inst := Unalias(pkg.Scope().Lookup("Inst").Type()).(*Pointer).Elem().(*Named) Node := firstFieldType(Inst) Tree := firstFieldType(Node) if !Identical(Inst, Tree) { t.Fatalf("Not a cycle: got %v, want %v", Tree, Inst) } if Inst != Tree { t.Errorf("Duplicate instances in cycle: %s (%p) -> %s (%p) -> %s (%p)", Inst, Inst, Node, Node, Tree, Tree) } } // TestMethodOrdering is a simple test verifying that the indices of methods of // a named type remain the same as long as the same source and AddMethod calls // are presented to the type checker in the same order (go.dev/issue/61298). func TestMethodOrdering(t *testing.T) { const src = ` package p type T struct{} func (T) a() {} func (T) c() {} func (T) b() {} ` // should get the same method order each time var methods []string for i := 0; i < 5; i++ { // collect T methods as provided in src pkg := mustTypecheck(src, nil, nil) T := pkg.Scope().Lookup("T").Type().(*Named) // add a few more methods manually for _, name := range []string{"foo", "bar", "bal"} { m := NewFunc(nopos, pkg, name, nil /* don't care about signature */) T.AddMethod(m) } // check method order if i == 0 { // first round: collect methods in given order methods = make([]string, T.NumMethods()) for j := range methods { methods[j] = T.Method(j).Name() } } else { // successive rounds: methods must appear in the same order if got := T.NumMethods(); got != len(methods) { t.Errorf("got %d methods, want %d", got, len(methods)) continue } for j, m := range methods { if got := T.Method(j).Name(); got != m { t.Errorf("got method %s, want %s", got, m) } } } } }