| // Copyright 2026 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 |
| |
| // This file defines a test of using [types.Hasher]. |
| |
| import ( |
| "go/ast" |
| "go/token" |
| "go/types" |
| "hash/maphash" |
| "testing" |
| ) |
| |
| func TestHasher(t *testing.T) { |
| 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] any |
| 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 |
| |
| // Non-generic alias |
| type NonAlias int |
| type Alias1 = NonAlias |
| type Alias2 = NonAlias |
| |
| type Tagged1 struct { F int "tag1" } |
| type Tagged2 struct { F int "tag2" } |
| ` |
| |
| fset := token.NewFileSet() |
| file := mustParse(fset, src) |
| |
| var conf types.Config |
| pkg, err := conf.Check("", fset, []*ast.File{file}, nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| instantiate := func(origin types.Type, targs ...types.Type) types.Type { |
| inst, err := types.Instantiate(nil, origin, targs, true) |
| if err != nil { |
| t.Fatal(err) |
| } |
| return inst |
| } |
| |
| scope := pkg.Scope() |
| var ( |
| tInt = types.Typ[types.Int] |
| tString = types.Typ[types.String] |
| |
| 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(G, tInt) |
| GInt2 = instantiate(G, tInt) |
| GStr = instantiate(G, tString) |
| C = scope.Lookup("C").Type() |
| CI = C.Underlying().(*types.Interface) |
| I = scope.Lookup("I").Type() |
| II = I.Underlying().(*types.Interface) |
| U = CI.EmbeddedType(0).(*types.Union) |
| Fa1 = scope.Lookup("Fa1").Type().(*types.Signature) |
| Fa2 = scope.Lookup("Fa2").Type().(*types.Signature) |
| Fa1P = Fa1.TypeParams().At(0) |
| Fa2Q = Fa2.TypeParams().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(G1, tInt).(*types.Named).Method(0).Type() |
| G1IntM2 = instantiate(G1, tInt).(*types.Named).Method(0).Type() |
| G1StrM = instantiate(G1, tString).(*types.Named).Method(0).Type() |
| G2 = scope.Lookup("G2").Type() |
| G2IntM = instantiate(G2, tInt).(*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("Bar").Type() |
| Baz = scope.Lookup("Baz").Type() |
| Quux = scope.Lookup("Quux").Type() |
| Issue56048 = scope.Lookup("Issue56048").Type() |
| Issue56048b = scope.Lookup("Issue56048b").Type() |
| |
| NonAlias = scope.Lookup("NonAlias").Type() |
| Alias1 = scope.Lookup("Alias1").Type() |
| Alias2 = scope.Lookup("Alias2").Type() |
| |
| Tagged1 = scope.Lookup("Tagged1").Type().Underlying().(*types.Struct) |
| Tagged2 = scope.Lookup("Tagged2").Type().Underlying().(*types.Struct) |
| ) |
| |
| // eqclasses groups the above types into types.Identical equivalence classes. |
| eqclasses := [][]types.Type{ |
| {T1}, |
| {T2}, |
| {G}, |
| {C}, |
| {CI}, |
| {U}, |
| {I}, |
| {II}, // should not be identical to CI |
| {T1M, T2M}, |
| {GInt1, GInt2}, |
| {GStr}, |
| {Fa1, Fa2}, |
| {Fa1P}, |
| {Fa2Q}, |
| {Fb1y, Fb1x}, |
| {Fb2x}, |
| {Fb1, Fb2}, |
| {G1}, |
| {G1M}, |
| {G2}, |
| {G1IntM1, G1IntM2, G2IntM}, |
| {G1StrM}, |
| {ME1, ME1Type}, |
| {ME2}, |
| {Constraint}, |
| {Foo, Bar}, |
| {Baz}, |
| {Fn}, |
| {Quux}, |
| {Issue56048}, |
| {Issue56048b}, |
| {NonAlias, Alias1, Alias2}, |
| } |
| |
| run := func(t *testing.T, hasher maphash.Hasher[types.Type], eq func(x, y types.Type) bool, classes [][]types.Type) { |
| seed := maphash.MakeSeed() |
| |
| hash := func(t types.Type) uint64 { |
| var h maphash.Hash |
| h.SetSeed(seed) |
| hasher.Hash(&h, t) |
| return h.Sum64() |
| } |
| |
| for xi, class := range classes { |
| tx := class[0] // arbitrary representative of class |
| |
| for yi := range classes { |
| if xi == yi { |
| // Within a class, each element is equivalent to first |
| // and has the same hash. |
| for i, ty := range class { |
| hx, hy := hash(tx), hash(ty) |
| if !eq(tx, ty) || hx != hy { |
| t.Fatalf("class[%d][%d] (%v, hash %x) is not equivalent to class[%d][%d] (%v, hash %x)", |
| xi, 0, tx, hx, |
| yi, i, ty, hy) |
| } |
| } |
| } else { |
| // Across classes, no element is equivalent to first. |
| // (We can't say for sure that the hashes are unequal.) |
| for k, typ := range classes[yi] { |
| if eq(tx, typ) { |
| t.Fatalf("class[%d][%d] (%v) is equivalent to class[%d][%d] (%v)", |
| xi, 0, tx, |
| yi, k, typ) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // Hasher considers the two Tagged{1,2} types distinct. |
| t.Run("Hasher", func(t *testing.T) { |
| run(t, types.Hasher{}, types.Identical, append( |
| eqclasses, |
| []types.Type{Tagged1}, |
| []types.Type{Tagged2}, |
| )) |
| }) |
| |
| // HasherIgnoreTags considers the two Tagged{1,2} types equal. |
| t.Run("HasherIgnoreTags", func(t *testing.T) { |
| run(t, types.HasherIgnoreTags{}, types.IdenticalIgnoreTags, append( |
| eqclasses, |
| []types.Type{Tagged1, Tagged2}, |
| )) |
| }) |
| } |