Skip to content

Commit 2ac48c6

Browse files
committed
go/types/typeutil: add support for mapping generic types
Add support to the typeutil package for hashing the new types produced when type-checking generic code. Change-Id: I05a213baee80c53c673442f3c28fddb26ad0b03f Reviewed-on: https://go-review.googlesource.com/c/tools/+/366614 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Tim King <[email protected]>
1 parent df48029 commit 2ac48c6

File tree

4 files changed

+356
-12
lines changed

4 files changed

+356
-12
lines changed

go/types/typeutil/map.go

Lines changed: 133 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"fmt"
1212
"go/types"
1313
"reflect"
14+
15+
"golang.org/x/tools/internal/typeparams"
1416
)
1517

1618
// Map is a hash-table-based mapping from types (types.Type) to
@@ -211,11 +213,29 @@ func (m *Map) KeysString() string {
211213
// Call MakeHasher to create a Hasher.
212214
type Hasher struct {
213215
memo map[types.Type]uint32
216+
217+
// ptrMap records pointer identity.
218+
ptrMap map[interface{}]uint32
219+
220+
// sigTParams holds type parameters from the signature being hashed.
221+
// Signatures are considered identical modulo renaming of type parameters, so
222+
// within the scope of a signature type the identity of the signature's type
223+
// parameters is just their index.
224+
//
225+
// Since the language does not currently support referring to uninstantiated
226+
// generic types or functions, and instantiated signatures do not have type
227+
// parameter lists, we should never encounter a second non-empty type
228+
// parameter list when hashing a generic signature.
229+
sigTParams *typeparams.TypeParamList
214230
}
215231

216232
// MakeHasher returns a new Hasher instance.
217233
func MakeHasher() Hasher {
218-
return Hasher{make(map[types.Type]uint32)}
234+
return Hasher{
235+
memo: make(map[types.Type]uint32),
236+
ptrMap: make(map[interface{}]uint32),
237+
sigTParams: nil,
238+
}
219239
}
220240

221241
// Hash computes a hash value for the given type t such that
@@ -273,17 +293,62 @@ func (h Hasher) hashFor(t types.Type) uint32 {
273293
if t.Variadic() {
274294
hash *= 8863
275295
}
296+
297+
// Use a separate hasher for types inside of the signature, where type
298+
// parameter identity is modified to be (index, constraint). We must use a
299+
// new memo for this hasher as type identity may be affected by this
300+
// masking. For example, in func[T any](*T), the identity of *T depends on
301+
// whether we are mapping the argument in isolation, or recursively as part
302+
// of hashing the signature.
303+
//
304+
// We should never encounter a generic signature while hashing another
305+
// generic signature, but defensively set sigTParams only if h.mask is
306+
// unset.
307+
tparams := typeparams.ForSignature(t)
308+
if h.sigTParams == nil && tparams.Len() != 0 {
309+
h = Hasher{
310+
// There may be something more efficient than discarding the existing
311+
// memo, but it would require detecting whether types are 'tainted' by
312+
// references to type parameters.
313+
memo: make(map[types.Type]uint32),
314+
// Re-using ptrMap ensures that pointer identity is preserved in this
315+
// hasher.
316+
ptrMap: h.ptrMap,
317+
sigTParams: tparams,
318+
}
319+
}
320+
321+
for i := 0; i < tparams.Len(); i++ {
322+
tparam := tparams.At(i)
323+
hash += 7 * h.Hash(tparam.Constraint())
324+
}
325+
276326
return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results())
277327

328+
case *typeparams.Union:
329+
return h.hashUnion(t)
330+
278331
case *types.Interface:
332+
// Interfaces are identical if they have the same set of methods, with
333+
// identical names and types, and they have the same set of type
334+
// restrictions. See go/types.identical for more details.
279335
var hash uint32 = 9103
336+
337+
// Hash methods.
280338
for i, n := 0, t.NumMethods(); i < n; i++ {
281-
// See go/types.identicalMethods for rationale.
282339
// Method order is not significant.
283340
// Ignore m.Pkg().
284341
m := t.Method(i)
285342
hash += 3*hashString(m.Name()) + 5*h.Hash(m.Type())
286343
}
344+
345+
// Hash type restrictions.
346+
terms, err := typeparams.InterfaceTermSet(t)
347+
// if err != nil t has invalid type restrictions.
348+
if err == nil {
349+
hash += h.hashTermSet(terms)
350+
}
351+
287352
return hash
288353

289354
case *types.Map:
@@ -293,13 +358,22 @@ func (h Hasher) hashFor(t types.Type) uint32 {
293358
return 9127 + 2*uint32(t.Dir()) + 3*h.Hash(t.Elem())
294359

295360
case *types.Named:
296-
// Not safe with a copying GC; objects may move.
297-
return uint32(reflect.ValueOf(t.Obj()).Pointer())
361+
hash := h.hashPtr(t.Obj())
362+
targs := typeparams.NamedTypeArgs(t)
363+
for i := 0; i < targs.Len(); i++ {
364+
targ := targs.At(i)
365+
hash += 2 * h.Hash(targ)
366+
}
367+
return hash
368+
369+
case *typeparams.TypeParam:
370+
return h.hashTypeParam(t)
298371

299372
case *types.Tuple:
300373
return h.hashTuple(t)
301374
}
302-
panic(t)
375+
376+
panic(fmt.Sprintf("%T: %v", t, t))
303377
}
304378

305379
func (h Hasher) hashTuple(tuple *types.Tuple) uint32 {
@@ -311,3 +385,57 @@ func (h Hasher) hashTuple(tuple *types.Tuple) uint32 {
311385
}
312386
return hash
313387
}
388+
389+
func (h Hasher) hashUnion(t *typeparams.Union) uint32 {
390+
// Hash type restrictions.
391+
terms, err := typeparams.UnionTermSet(t)
392+
// if err != nil t has invalid type restrictions. Fall back on a non-zero
393+
// hash.
394+
if err != nil {
395+
return 9151
396+
}
397+
return h.hashTermSet(terms)
398+
}
399+
400+
func (h Hasher) hashTermSet(terms []*typeparams.Term) uint32 {
401+
var hash uint32 = 9157 + 2*uint32(len(terms))
402+
for _, term := range terms {
403+
// term order is not significant.
404+
termHash := h.Hash(term.Type())
405+
if term.Tilde() {
406+
termHash *= 9161
407+
}
408+
hash += 3 * termHash
409+
}
410+
return hash
411+
}
412+
413+
// hashTypeParam returns a hash of the type parameter t, with a hash value
414+
// depending on whether t is contained in h.sigTParams.
415+
//
416+
// If h.sigTParams is set and contains t, then we are in the process of hashing
417+
// a signature, and the hash value of t must depend only on t's index and
418+
// constraint: signatures are considered identical modulo type parameter
419+
// renaming.
420+
//
421+
// Otherwise the hash of t depends only on t's pointer identity.
422+
func (h Hasher) hashTypeParam(t *typeparams.TypeParam) uint32 {
423+
if h.sigTParams != nil {
424+
i := t.Index()
425+
if i >= 0 && i < h.sigTParams.Len() && t == h.sigTParams.At(i) {
426+
return 9173 + 2*h.Hash(t.Constraint()) + 3*uint32(i)
427+
}
428+
}
429+
return h.hashPtr(t.Obj())
430+
}
431+
432+
// hashPtr hashes the pointer identity of ptr. It uses h.ptrMap to ensure that
433+
// pointers values are not dependent on the GC.
434+
func (h Hasher) hashPtr(ptr interface{}) uint32 {
435+
if hash, ok := h.ptrMap[ptr]; ok {
436+
return hash
437+
}
438+
hash := uint32(reflect.ValueOf(ptr).Pointer())
439+
h.ptrMap[ptr] = hash
440+
return hash
441+
}

go/types/typeutil/map_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ package typeutil_test
1010
// (e.g. all types generated by type-checking some body of real code).
1111

1212
import (
13+
"go/ast"
14+
"go/parser"
15+
"go/token"
1316
"go/types"
1417
"testing"
1518

1619
"golang.org/x/tools/go/types/typeutil"
20+
"golang.org/x/tools/internal/typeparams"
1721
)
1822

1923
var (
@@ -172,3 +176,190 @@ func TestMap(t *testing.T) {
172176
t.Errorf("Len(): got %q, want %q", s, "")
173177
}
174178
}
179+
180+
func TestMapGenerics(t *testing.T) {
181+
if !typeparams.Enabled {
182+
t.Skip("type params are not enabled at this Go version")
183+
}
184+
185+
const src = `
186+
package p
187+
188+
// Basic defined types.
189+
type T1 int
190+
type T2 int
191+
192+
// Identical methods.
193+
func (T1) M(int) {}
194+
func (T2) M(int) {}
195+
196+
// A constraint interface.
197+
type C interface {
198+
~int | string
199+
}
200+
201+
type I interface {
202+
}
203+
204+
// A generic type.
205+
type G[P C] int
206+
207+
// Generic functions with identical signature.
208+
func Fa1[P C](p P) {}
209+
func Fa2[Q C](q Q) {}
210+
211+
// Fb1 and Fb2 are identical and should be mapped to the same entry, even if we
212+
// map their arguments first.
213+
func Fb1[P any](x *P) {
214+
var y *P // Map this first.
215+
_ = y
216+
}
217+
func Fb2[Q any](x *Q) {
218+
}
219+
220+
// G1 and G2 are mutally recursive, and have identical methods.
221+
type G1[P any] struct{
222+
Field *G2[P]
223+
}
224+
func (G1[P]) M(G1[P], G2[P]) {}
225+
type G2[Q any] struct{
226+
Field *G1[Q]
227+
}
228+
func (G2[P]) M(G1[P], G2[P]) {}
229+
230+
// Method type expressions on different generic types are different.
231+
var ME1 = G1[int].M
232+
var ME2 = G2[int].M
233+
234+
// ME1Type should have identical type as ME1.
235+
var ME1Type func(G1[int], G1[int], G2[int])
236+
`
237+
238+
fset := token.NewFileSet()
239+
file, err := parser.ParseFile(fset, "p.go", src, 0)
240+
if err != nil {
241+
t.Fatal(err)
242+
}
243+
244+
var conf types.Config
245+
pkg, err := conf.Check("", fset, []*ast.File{file}, nil)
246+
if err != nil {
247+
t.Fatal(err)
248+
}
249+
250+
// Collect types.
251+
scope := pkg.Scope()
252+
var (
253+
T1 = scope.Lookup("T1").Type().(*types.Named)
254+
T2 = scope.Lookup("T2").Type().(*types.Named)
255+
T1M = T1.Method(0).Type()
256+
T2M = T2.Method(0).Type()
257+
G = scope.Lookup("G").Type()
258+
GInt1 = instantiate(t, G, types.Typ[types.Int])
259+
GInt2 = instantiate(t, G, types.Typ[types.Int])
260+
GStr = instantiate(t, G, types.Typ[types.String])
261+
C = scope.Lookup("C").Type()
262+
CI = C.Underlying().(*types.Interface)
263+
I = scope.Lookup("I").Type()
264+
II = I.Underlying().(*types.Interface)
265+
U = CI.EmbeddedType(0).(*typeparams.Union)
266+
Fa1 = scope.Lookup("Fa1").Type().(*types.Signature)
267+
Fa2 = scope.Lookup("Fa2").Type().(*types.Signature)
268+
Fa1P = typeparams.ForSignature(Fa1).At(0)
269+
Fa2Q = typeparams.ForSignature(Fa2).At(0)
270+
Fb1 = scope.Lookup("Fb1").Type().(*types.Signature)
271+
Fb1x = Fb1.Params().At(0).Type()
272+
Fb1y = scope.Lookup("Fb1").(*types.Func).Scope().Lookup("y").Type()
273+
Fb2 = scope.Lookup("Fb2").Type().(*types.Signature)
274+
Fb2x = Fb2.Params().At(0).Type()
275+
G1 = scope.Lookup("G1").Type().(*types.Named)
276+
G1M = G1.Method(0).Type()
277+
G1IntM1 = instantiate(t, G1, types.Typ[types.Int]).(*types.Named).Method(0).Type()
278+
G1IntM2 = instantiate(t, G1, types.Typ[types.Int]).(*types.Named).Method(0).Type()
279+
G1StrM = instantiate(t, G1, types.Typ[types.String]).(*types.Named).Method(0).Type()
280+
G2 = scope.Lookup("G2").Type()
281+
// See below.
282+
// G2M = G2.Method(0).Type()
283+
G2IntM = instantiate(t, G2, types.Typ[types.Int]).(*types.Named).Method(0).Type()
284+
ME1 = scope.Lookup("ME1").Type()
285+
ME1Type = scope.Lookup("ME1Type").Type()
286+
ME2 = scope.Lookup("ME2").Type()
287+
)
288+
289+
tmap := new(typeutil.Map)
290+
291+
steps := []struct {
292+
typ types.Type
293+
name string
294+
newEntry bool
295+
}{
296+
{T1, "T1", true},
297+
{T2, "T2", true},
298+
{G, "G", true},
299+
{C, "C", true},
300+
{CI, "CI", true},
301+
{U, "U", true},
302+
{I, "I", true},
303+
{II, "II", true}, // should not be identical to CI
304+
305+
// Methods can be identical, even with distinct receivers.
306+
{T1M, "T1M", true},
307+
{T2M, "T2M", false},
308+
309+
// Identical instances should map to the same entry.
310+
{GInt1, "GInt1", true},
311+
{GInt2, "GInt2", false},
312+
// ..but instantiating with different arguments should yield a new entry.
313+
{GStr, "GStr", true},
314+
315+
// F1 and F2 should have identical signatures.
316+
{Fa1, "F1", true},
317+
{Fa2, "F2", false},
318+
319+
// The identity of P and Q should not have been affected by type parameter
320+
// masking during signature hashing.
321+
{Fa1P, "F1P", true},
322+
{Fa2Q, "F2Q", true},
323+
324+
{Fb1y, "Fb1y", true},
325+
{Fb1x, "Fb1x", false},
326+
{Fb2x, "Fb2x", true},
327+
{Fb1, "Fb1", true},
328+
329+
// Mapping elements of the function scope should not affect the identity of
330+
// Fb2 or Fb1.
331+
{Fb2, "Fb1", false},
332+
333+
{G1, "G1", true},
334+
{G1M, "G1M", true},
335+
{G2, "G2", true},
336+
337+
// See golang/go#49912: receiver type parameter names should be ignored
338+
// when comparing method identity.
339+
// {G2M, "G2M", false},
340+
{G1IntM1, "G1IntM1", true},
341+
{G1IntM2, "G1IntM2", false},
342+
{G1StrM, "G1StrM", true},
343+
{G2IntM, "G2IntM", false}, // identical to G1IntM1
344+
345+
{ME1, "ME1", true},
346+
{ME1Type, "ME1Type", false},
347+
{ME2, "ME2", true},
348+
}
349+
350+
for _, step := range steps {
351+
existing := tmap.At(step.typ)
352+
if (existing == nil) != step.newEntry {
353+
t.Errorf("At(%s) = %v, want new entry: %t", step.name, existing, step.newEntry)
354+
}
355+
tmap.Set(step.typ, step.name)
356+
}
357+
}
358+
359+
func instantiate(t *testing.T, origin types.Type, targs ...types.Type) types.Type {
360+
inst, err := typeparams.Instantiate(nil, origin, targs, true)
361+
if err != nil {
362+
t.Fatal(err)
363+
}
364+
return inst
365+
}

0 commit comments

Comments
 (0)