Source file src/go/types/hash_test.go

     1  // Copyright 2026 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  // This file defines a test of using [types.Hasher].
     8  
     9  import (
    10  	"go/ast"
    11  	"go/parser"
    12  	"go/token"
    13  	"go/types"
    14  	"hash/maphash"
    15  	"testing"
    16  )
    17  
    18  func TestHasher(t *testing.T) {
    19  	const src = `
    20  package p
    21  
    22  // Basic defined types.
    23  type T1 int
    24  type T2 int
    25  
    26  // Identical methods.
    27  func (T1) M(int) {}
    28  func (T2) M(int) {}
    29  
    30  // A constraint interface.
    31  type C interface {
    32  	~int | string
    33  }
    34  
    35  type I interface {
    36  }
    37  
    38  // A generic type.
    39  type G[P C] int
    40  
    41  // Generic functions with identical signature.
    42  func Fa1[P C](p P) {}
    43  func Fa2[Q C](q Q) {}
    44  
    45  // Fb1 and Fb2 are identical and should be mapped to the same entry, even if we
    46  // map their arguments first.
    47  func Fb1[P any](x *P) {
    48  	var y *P // Map this first.
    49  	_ = y
    50  }
    51  func Fb2[Q any](x *Q) {
    52  }
    53  
    54  // G1 and G2 are mutally recursive, and have identical methods.
    55  type G1[P any] struct{
    56  	Field *G2[P]
    57  }
    58  func (G1[P]) M(G1[P], G2[P]) {}
    59  type G2[Q any] struct{
    60  	Field *G1[Q]
    61  }
    62  func (G2[P]) M(G1[P], G2[P]) {}
    63  
    64  // Method type expressions on different generic types are different.
    65  var ME1 = G1[int].M
    66  var ME2 = G2[int].M
    67  
    68  // ME1Type should have identical type as ME1.
    69  var ME1Type func(G1[int], G1[int], G2[int])
    70  
    71  // Examples from issue #51314
    72  type Constraint[T any] any
    73  func Foo[T Constraint[T]]() {}
    74  func Fn[T1 ~*T2, T2 ~*T1](t1 T1, t2 T2) {}
    75  
    76  // Bar and Baz are identical to Foo.
    77  func Bar[P Constraint[P]]() {}
    78  func Baz[Q any]() {} // The underlying type of Constraint[P] is any.
    79  // But Quux is not.
    80  func Quux[Q interface{ quux() }]() {}
    81  
    82  type Issue56048_I interface{ m() interface { Issue56048_I } }
    83  var Issue56048 = Issue56048_I.m
    84  
    85  type Issue56048_Ib interface{ m() chan []*interface { Issue56048_Ib } }
    86  var Issue56048b = Issue56048_Ib.m
    87  
    88  // Non-generic alias
    89  type NonAlias int
    90  type Alias1 = NonAlias
    91  type Alias2 = NonAlias
    92  
    93  type Tagged1 struct { F int "tag1" }
    94  type Tagged2 struct { F int "tag2" }
    95  `
    96  
    97  	fset := token.NewFileSet()
    98  	file, err := parser.ParseFile(fset, "p.go", src, 0)
    99  	if err != nil {
   100  		t.Fatal(err)
   101  	}
   102  
   103  	var conf types.Config
   104  	pkg, err := conf.Check("", fset, []*ast.File{file}, nil)
   105  	if err != nil {
   106  		t.Fatal(err)
   107  	}
   108  
   109  	instantiate := func(origin types.Type, targs ...types.Type) types.Type {
   110  		inst, err := types.Instantiate(nil, origin, targs, true)
   111  		if err != nil {
   112  			t.Fatal(err)
   113  		}
   114  		return inst
   115  	}
   116  
   117  	scope := pkg.Scope()
   118  	var (
   119  		tInt    = types.Typ[types.Int]
   120  		tString = types.Typ[types.String]
   121  
   122  		T1      = scope.Lookup("T1").Type().(*types.Named)
   123  		T2      = scope.Lookup("T2").Type().(*types.Named)
   124  		T1M     = T1.Method(0).Type()
   125  		T2M     = T2.Method(0).Type()
   126  		G       = scope.Lookup("G").Type()
   127  		GInt1   = instantiate(G, tInt)
   128  		GInt2   = instantiate(G, tInt)
   129  		GStr    = instantiate(G, tString)
   130  		C       = scope.Lookup("C").Type()
   131  		CI      = C.Underlying().(*types.Interface)
   132  		I       = scope.Lookup("I").Type()
   133  		II      = I.Underlying().(*types.Interface)
   134  		U       = CI.EmbeddedType(0).(*types.Union)
   135  		Fa1     = scope.Lookup("Fa1").Type().(*types.Signature)
   136  		Fa2     = scope.Lookup("Fa2").Type().(*types.Signature)
   137  		Fa1P    = Fa1.TypeParams().At(0)
   138  		Fa2Q    = Fa2.TypeParams().At(0)
   139  		Fb1     = scope.Lookup("Fb1").Type().(*types.Signature)
   140  		Fb1x    = Fb1.Params().At(0).Type()
   141  		Fb1y    = scope.Lookup("Fb1").(*types.Func).Scope().Lookup("y").Type()
   142  		Fb2     = scope.Lookup("Fb2").Type().(*types.Signature)
   143  		Fb2x    = Fb2.Params().At(0).Type()
   144  		G1      = scope.Lookup("G1").Type().(*types.Named)
   145  		G1M     = G1.Method(0).Type()
   146  		G1IntM1 = instantiate(G1, tInt).(*types.Named).Method(0).Type()
   147  		G1IntM2 = instantiate(G1, tInt).(*types.Named).Method(0).Type()
   148  		G1StrM  = instantiate(G1, tString).(*types.Named).Method(0).Type()
   149  		G2      = scope.Lookup("G2").Type()
   150  		G2IntM  = instantiate(G2, tInt).(*types.Named).Method(0).Type()
   151  		ME1     = scope.Lookup("ME1").Type()
   152  		ME1Type = scope.Lookup("ME1Type").Type()
   153  		ME2     = scope.Lookup("ME2").Type()
   154  
   155  		Constraint  = scope.Lookup("Constraint").Type()
   156  		Foo         = scope.Lookup("Foo").Type()
   157  		Fn          = scope.Lookup("Fn").Type()
   158  		Bar         = scope.Lookup("Bar").Type()
   159  		Baz         = scope.Lookup("Baz").Type()
   160  		Quux        = scope.Lookup("Quux").Type()
   161  		Issue56048  = scope.Lookup("Issue56048").Type()
   162  		Issue56048b = scope.Lookup("Issue56048b").Type()
   163  
   164  		NonAlias = scope.Lookup("NonAlias").Type()
   165  		Alias1   = scope.Lookup("Alias1").Type()
   166  		Alias2   = scope.Lookup("Alias2").Type()
   167  
   168  		Tagged1 = scope.Lookup("Tagged1").Type().Underlying().(*types.Struct)
   169  		Tagged2 = scope.Lookup("Tagged2").Type().Underlying().(*types.Struct)
   170  	)
   171  
   172  	// eqclasses groups the above types into types.Identical equivalence classes.
   173  	eqclasses := [][]types.Type{
   174  		{T1},
   175  		{T2},
   176  		{G},
   177  		{C},
   178  		{CI},
   179  		{U},
   180  		{I},
   181  		{II}, // should not be identical to CI
   182  		{T1M, T2M},
   183  		{GInt1, GInt2},
   184  		{GStr},
   185  		{Fa1, Fa2},
   186  		{Fa1P},
   187  		{Fa2Q},
   188  		{Fb1y, Fb1x},
   189  		{Fb2x},
   190  		{Fb1, Fb2},
   191  		{G1},
   192  		{G1M},
   193  		{G2},
   194  		{G1IntM1, G1IntM2, G2IntM},
   195  		{G1StrM},
   196  		{ME1, ME1Type},
   197  		{ME2},
   198  		{Constraint},
   199  		{Foo, Bar},
   200  		{Baz},
   201  		{Fn},
   202  		{Quux},
   203  		{Issue56048},
   204  		{Issue56048b},
   205  		{NonAlias, Alias1, Alias2},
   206  	}
   207  
   208  	run := func(t *testing.T, hasher maphash.Hasher[types.Type], eq func(x, y types.Type) bool, classes [][]types.Type) {
   209  		seed := maphash.MakeSeed()
   210  
   211  		hash := func(t types.Type) uint64 {
   212  			var h maphash.Hash
   213  			h.SetSeed(seed)
   214  			hasher.Hash(&h, t)
   215  			return h.Sum64()
   216  		}
   217  
   218  		for xi, class := range classes {
   219  			tx := class[0] // arbitrary representative of class
   220  
   221  			for yi := range classes {
   222  				if xi == yi {
   223  					// Within a class, each element is equivalent to first
   224  					// and has the same hash.
   225  					for i, ty := range class {
   226  						hx, hy := hash(tx), hash(ty)
   227  						if !eq(tx, ty) || hx != hy {
   228  							t.Fatalf("class[%d][%d] (%v, hash %x) is not equivalent to class[%d][%d] (%v, hash %x)",
   229  								xi, 0, tx, hx,
   230  								yi, i, ty, hy)
   231  						}
   232  					}
   233  				} else {
   234  					// Across classes, no element is equivalent to first.
   235  					// (We can't say for sure that the hashes are unequal.)
   236  					for k, typ := range classes[yi] {
   237  						if eq(tx, typ) {
   238  							t.Fatalf("class[%d][%d] (%v) is equivalent to class[%d][%d] (%v)",
   239  								xi, 0, tx,
   240  								yi, k, typ)
   241  						}
   242  					}
   243  				}
   244  			}
   245  		}
   246  	}
   247  
   248  	// Hasher considers the two Tagged{1,2} types distinct.
   249  	t.Run("Hasher", func(t *testing.T) {
   250  		run(t, types.Hasher{}, types.Identical, append(
   251  			eqclasses,
   252  			[]types.Type{Tagged1},
   253  			[]types.Type{Tagged2},
   254  		))
   255  	})
   256  
   257  	// HasherIgnoreTags considers the two Tagged{1,2} types equal.
   258  	t.Run("HasherIgnoreTags", func(t *testing.T) {
   259  		run(t, types.HasherIgnoreTags{}, types.IdenticalIgnoreTags, append(
   260  			eqclasses,
   261  			[]types.Type{Tagged1, Tagged2},
   262  		))
   263  	})
   264  }
   265  

View as plain text