named_test.go raw

   1  // Copyright 2022 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package types_test
   6  
   7  import (
   8  	"go/ast"
   9  	"go/token"
  10  	"testing"
  11  
  12  	. "go/types"
  13  )
  14  
  15  func BenchmarkNamed(b *testing.B) {
  16  	const src = `
  17  package p
  18  
  19  type T struct {
  20  	P int
  21  }
  22  
  23  func (T) M(int) {}
  24  func (T) N() (i int) { return }
  25  
  26  type G[P any] struct {
  27  	F P
  28  }
  29  
  30  func (G[P]) M(P) {}
  31  func (G[P]) N() (p P) { return }
  32  
  33  type Inst = G[int]
  34  	`
  35  	pkg := mustTypecheck(src, nil, nil)
  36  
  37  	var (
  38  		T        = pkg.Scope().Lookup("T").Type()
  39  		G        = pkg.Scope().Lookup("G").Type()
  40  		SrcInst  = pkg.Scope().Lookup("Inst").Type()
  41  		UserInst = mustInstantiate(b, G, Typ[Int])
  42  	)
  43  
  44  	tests := []struct {
  45  		name string
  46  		typ  Type
  47  	}{
  48  		{"nongeneric", T},
  49  		{"generic", G},
  50  		{"src instance", SrcInst},
  51  		{"user instance", UserInst},
  52  	}
  53  
  54  	b.Run("Underlying", func(b *testing.B) {
  55  		for _, test := range tests {
  56  			b.Run(test.name, func(b *testing.B) {
  57  				// Access underlying once, to trigger any lazy calculation.
  58  				_ = test.typ.Underlying()
  59  				b.ResetTimer()
  60  				for i := 0; i < b.N; i++ {
  61  					_ = test.typ.Underlying()
  62  				}
  63  			})
  64  		}
  65  	})
  66  
  67  	b.Run("NewMethodSet", func(b *testing.B) {
  68  		for _, test := range tests {
  69  			b.Run(test.name, func(b *testing.B) {
  70  				// Access underlying once, to trigger any lazy calculation.
  71  				_ = NewMethodSet(test.typ)
  72  				b.ResetTimer()
  73  				for i := 0; i < b.N; i++ {
  74  					_ = NewMethodSet(test.typ)
  75  				}
  76  			})
  77  		}
  78  	})
  79  }
  80  
  81  func mustInstantiate(tb testing.TB, orig Type, targs ...Type) Type {
  82  	inst, err := Instantiate(nil, orig, targs, true)
  83  	if err != nil {
  84  		tb.Fatal(err)
  85  	}
  86  	return inst
  87  }
  88  
  89  // Test that types do not expand infinitely, as in go.dev/issue/52715.
  90  func TestFiniteTypeExpansion(t *testing.T) {
  91  	const src = `
  92  package p
  93  
  94  type Tree[T any] struct {
  95  	*Node[T]
  96  }
  97  
  98  func (*Tree[R]) N(r R) R { return r }
  99  
 100  type Node[T any] struct {
 101  	*Tree[T]
 102  }
 103  
 104  func (Node[Q]) M(Q) {}
 105  
 106  type Inst = *Tree[int]
 107  `
 108  
 109  	fset := token.NewFileSet()
 110  	f := mustParse(fset, src)
 111  	pkg := NewPackage("p", f.Name.Name)
 112  	if err := NewChecker(nil, fset, pkg, nil).Files([]*ast.File{f}); err != nil {
 113  		t.Fatal(err)
 114  	}
 115  
 116  	firstFieldType := func(n *Named) *Named {
 117  		return n.Underlying().(*Struct).Field(0).Type().(*Pointer).Elem().(*Named)
 118  	}
 119  
 120  	Inst := Unalias(pkg.Scope().Lookup("Inst").Type()).(*Pointer).Elem().(*Named)
 121  	Node := firstFieldType(Inst)
 122  	Tree := firstFieldType(Node)
 123  	if !Identical(Inst, Tree) {
 124  		t.Fatalf("Not a cycle: got %v, want %v", Tree, Inst)
 125  	}
 126  	if Inst != Tree {
 127  		t.Errorf("Duplicate instances in cycle: %s (%p) -> %s (%p) -> %s (%p)", Inst, Inst, Node, Node, Tree, Tree)
 128  	}
 129  }
 130  
 131  // TestMethodOrdering is a simple test verifying that the indices of methods of
 132  // a named type remain the same as long as the same source and AddMethod calls
 133  // are presented to the type checker in the same order (go.dev/issue/61298).
 134  func TestMethodOrdering(t *testing.T) {
 135  	const src = `
 136  package p
 137  
 138  type T struct{}
 139  
 140  func (T) a() {}
 141  func (T) c() {}
 142  func (T) b() {}
 143  `
 144  	// should get the same method order each time
 145  	var methods []string
 146  	for i := 0; i < 5; i++ {
 147  		// collect T methods as provided in src
 148  		pkg := mustTypecheck(src, nil, nil)
 149  		T := pkg.Scope().Lookup("T").Type().(*Named)
 150  
 151  		// add a few more methods manually
 152  		for _, name := range []string{"foo", "bar", "bal"} {
 153  			m := NewFunc(nopos, pkg, name, nil /* don't care about signature */)
 154  			T.AddMethod(m)
 155  		}
 156  
 157  		// check method order
 158  		if i == 0 {
 159  			// first round: collect methods in given order
 160  			methods = make([]string, T.NumMethods())
 161  			for j := range methods {
 162  				methods[j] = T.Method(j).Name()
 163  			}
 164  		} else {
 165  			// successive rounds: methods must appear in the same order
 166  			if got := T.NumMethods(); got != len(methods) {
 167  				t.Errorf("got %d methods, want %d", got, len(methods))
 168  				continue
 169  			}
 170  			for j, m := range methods {
 171  				if got := T.Method(j).Name(); got != m {
 172  					t.Errorf("got method %s, want %s", got, m)
 173  				}
 174  			}
 175  		}
 176  	}
 177  }
 178