Files
garble/bundled_typeutil.go
T
Daniel Martí e693cd0632 stabilize generic type param hashing for anonymous struct fields
Avoid hashing free type parameters by object/name identity in type
hashing and use traversal-local canonical IDs instead.
This keeps obfuscated field names stable across alpha-renamed generic
signatures (e.g. T vs newT), fixing interface/method mismatches.

The added test case fails without the fix:

    > exec garble build
    [stderr]
    # test/main
    YiaBFlNH.go:48: cannot use bT6psGs5O (variable of type *QdwRyqV[aEqmF4M, zpotHfQdnNB]) as E_OKfdHoPH[zpotHfQdnNB] value in return statement: *QdwRyqV[aEqmF4M, zpotHfQdnNB] does not implement E_OKfdHoPH[zpotHfQdnNB] (wrong type for method Redirect)
    		have Redirect(struct{palTavb zpotHfQdnNB})
    		want Redirect(struct{g3N8A_R zpotHfQdnNB})

The fix is Paul's; this is rebased on an updated version of typeutil,
and the test is now part of typeparams.txtar and much smaller.

Fixes #991.

Co-authored-by: Paul Scheduikat <lu4p@pm.me>
2026-02-28 01:22:11 +01:00

307 lines
8.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// NOTE(garble): bundled as of golang.org/x/tools v0.42.0; see CONTRIBUTING.md.
package main
import (
"fmt"
"go/types"
_ "unsafe"
)
// -- Hasher --
// hash returns the hash of type t.
// TODO(adonovan): replace by types.Hash when Go proposal #69420 is accepted.
func typeutil_hash(t types.Type) uint32 {
return typeutil_theHasher.Hash(t)
}
// A Hasher provides a [Hasher.Hash] method to map a type to its hash value.
// Hashers are stateless, and all are equivalent.
type typeutil_Hasher struct{}
var typeutil_theHasher typeutil_Hasher
// Hash computes a hash value for the given type t such that
// Identical(t, t') => Hash(t) == Hash(t').
func (h typeutil_Hasher) Hash(t types.Type) uint32 {
return typeutil_hasher{
inGenericSig: false,
typeParamIDs: make(map[*types.TypeParam]uint32),
}.hash(t)
}
// hasher holds the state of a single Hash traversal: whether we are
// inside the signature of a generic function; this is used to
// optimize [hasher.hashTypeParam].
//
// typeParamIDs assigns deterministic traversal-local IDs to free type params,
// making hashes stable across alpha-renaming in equivalent generic contexts.
type typeutil_hasher struct {
inGenericSig bool
typeParamIDs map[*types.TypeParam]uint32
}
// hashString computes the FowlerNollVo hash of s.
func typeutil_hashString(s string) uint32 {
var h uint32
for i := 0; i < len(s); i++ {
h ^= uint32(s[i])
h *= 16777619
}
return h
}
// hash computes the hash of t.
func (h typeutil_hasher) hash(t types.Type) uint32 {
// See Identical for rationale.
switch t := t.(type) {
case *types.Basic:
return uint32(t.Kind())
case *types.Alias:
return h.hash(types.Unalias(t))
case *types.Array:
return 9043 + 2*uint32(t.Len()) + 3*h.hash(t.Elem())
case *types.Slice:
return 9049 + 2*h.hash(t.Elem())
case *types.Struct:
var hash uint32 = 9059
for i, n := 0, t.NumFields(); i < n; i++ {
f := t.Field(i)
if f.Anonymous() {
hash += 8861
}
// NOTE(garble): we must not hash struct field tags, as they do not affect type identity.
// hash += typeutil_hashString(t.Tag(i))
hash += typeutil_hashString(f.Name()) // (ignore f.Pkg)
hash += h.hash(f.Type())
}
return hash
case *types.Pointer:
return 9067 + 2*h.hash(t.Elem())
case *types.Signature:
var hash uint32 = 9091
if t.Variadic() {
hash *= 8863
}
tparams := t.TypeParams()
if n := tparams.Len(); n > 0 {
h.inGenericSig = true // affects constraints, params, and results
for i := range n {
tparam := tparams.At(i)
hash += 7 * h.hash(tparam.Constraint())
}
}
return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results())
case *types.Union:
return h.hashUnion(t)
case *types.Interface:
// Interfaces are identical if they have the same set of methods, with
// identical names and types, and they have the same set of type
// restrictions. See go/types.identical for more details.
var hash uint32 = 9103
// Hash methods.
for i, n := 0, t.NumMethods(); i < n; i++ {
// Method order is not significant.
// Ignore m.Pkg().
m := t.Method(i)
// Use shallow hash on method signature to
// avoid anonymous interface cycles.
hash += 3*typeutil_hashString(m.Name()) + 5*h.shallowHash(m.Type())
}
// Hash type restrictions.
terms, err := typeparams_InterfaceTermSet(t)
// if err != nil t has invalid type restrictions.
if err == nil {
hash += h.hashTermSet(terms)
}
return hash
case *types.Map:
return 9109 + 2*h.hash(t.Key()) + 3*h.hash(t.Elem())
case *types.Chan:
return 9127 + 2*uint32(t.Dir()) + 3*h.hash(t.Elem())
case *types.Named:
hash := h.hashTypeName(t.Obj())
targs := t.TypeArgs()
for targ := range targs.Types() {
hash += 2 * h.hash(targ)
}
return hash
case *types.TypeParam:
return h.hashTypeParam(t)
case *types.Tuple:
return h.hashTuple(t)
}
panic(fmt.Sprintf("%T: %v", t, t))
}
func (h typeutil_hasher) hashTuple(tuple *types.Tuple) uint32 {
// See go/types.identicalTypes for rationale.
n := tuple.Len()
hash := 9137 + 2*uint32(n)
for i := range n {
hash += 3 * h.hash(tuple.At(i).Type())
}
return hash
}
func (h typeutil_hasher) hashUnion(t *types.Union) uint32 {
// Hash type restrictions.
terms, err := typeparams_UnionTermSet(t)
// if err != nil t has invalid type restrictions. Fall back on a non-zero
// hash.
if err != nil {
return 9151
}
return h.hashTermSet(terms)
}
func (h typeutil_hasher) hashTermSet(terms []*types.Term) uint32 {
hash := 9157 + 2*uint32(len(terms))
for _, term := range terms {
// term order is not significant.
termHash := h.hash(term.Type())
if term.Tilde() {
termHash *= 9161
}
hash += 3 * termHash
}
return hash
}
// hashTypeParam returns the hash of a type parameter.
func (h typeutil_hasher) hashTypeParam(t *types.TypeParam) uint32 {
// Within the signature of a generic function, TypeParams are
// identical if they have the same index and constraint, so we
// hash them based on index.
//
// When we are outside a generic function signature, free TypeParams can
// come from equivalent generic contexts that only differ by alpha-renaming
// (e.g. T vs newT). Object identity/name would make those hash differently.
//
// We therefore assign each free TypeParam a traversal-local canonical ID.
// We intentionally keep the same "9173 + 3*x" shape used elsewhere for
// TypeParams, only swapping x from Index/object-derived info to this ID.
if !h.inGenericSig {
// Outside generic function signatures, object identity depends on the
// specific binder and does not survive alpha-renaming between equivalent
// type expressions (e.g. T vs newT). Use a deterministic traversal-local
// ID to keep hashes stable in those equivalent cases.
if id, ok := h.typeParamIDs[t]; ok {
// 9173 marks "type param", 3*id matches this hasher's mixing style.
return 9173 + 3*id
}
id := uint32(len(h.typeParamIDs))
h.typeParamIDs[t] = id
return 9173 + 3*id
}
return 9173 + 3*uint32(t.Index())
}
// hashTypeName hashes the pointer of tname.
func (typeutil_hasher) hashTypeName(tname *types.TypeName) uint32 {
// NOTE(garble): we must not hash any pointers,
// as garble is a toolexec tool so by nature it uses multiple processes.
return typeutil_hashString(tname.Name())
// Since types.Identical uses == to compare TypeNames,
// the Hash function uses maphash.Comparable.
// hash := maphash.Comparable(typeutil_theSeed, tname)
// return uint32(hash ^ (hash >> 32))
}
// shallowHash computes a hash of t without looking at any of its
// element Types, to avoid potential anonymous cycles in the types of
// interface methods.
//
// When an unnamed non-empty interface type appears anywhere among the
// arguments or results of an interface method, there is a potential
// for endless recursion. Consider:
//
// type X interface { m() []*interface { X } }
//
// The problem is that the Methods of the interface in m's result type
// include m itself; there is no mention of the named type X that
// might help us break the cycle.
// (See comment in go/types.identical, case *Interface, for more.)
func (h typeutil_hasher) shallowHash(t types.Type) uint32 {
// t is the type of an interface method (Signature),
// its params or results (Tuples), or their immediate
// elements (mostly Slice, Pointer, Basic, Named),
// so there's no need to optimize anything else.
switch t := t.(type) {
case *types.Alias:
return h.shallowHash(types.Unalias(t))
case *types.Signature:
var hash uint32 = 604171
if t.Variadic() {
hash *= 971767
}
// The Signature/Tuple recursion is always finite
// and invariably shallow.
return hash + 1062599*h.shallowHash(t.Params()) + 1282529*h.shallowHash(t.Results())
case *types.Tuple:
n := t.Len()
hash := 9137 + 2*uint32(n)
for i := range n {
hash += 53471161 * h.shallowHash(t.At(i).Type())
}
return hash
case *types.Basic:
return 45212177 * uint32(t.Kind())
case *types.Array:
return 1524181 + 2*uint32(t.Len())
case *types.Slice:
return 2690201
case *types.Struct:
return 3326489
case *types.Pointer:
return 4393139
case *types.Union:
return 562448657
case *types.Interface:
return 2124679 // no recursion here
case *types.Map:
return 9109
case *types.Chan:
return 9127
case *types.Named:
return h.hashTypeName(t.Obj())
case *types.TypeParam:
return h.hashTypeParam(t)
}
panic(fmt.Sprintf("shallowHash: %T: %v", t, t))
}