// 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

// This file defines a hash function for Types.

import (
	"fmt"
	"hash/maphash"
)

type (
	// Hasher defines a hash function and equivalence relation
	// for [Types] that is consistent with [Identical].
	// Hashers are stateless.
	Hasher struct{}

	// HasherIgnoreTags is a variant of [Hasher] that is
	// consistent with [IdenticalIgnoreTags].
	HasherIgnoreTags struct{}
)

var (
	_ maphash.Hasher[Type] = Hasher{}
	_ maphash.Hasher[Type] = HasherIgnoreTags{}
)

func (Hasher) Hash(h *maphash.Hash, t Type) {
	// The two hashers use essentially the same hash function,
	// which ignores tags; only the Equal methods vary.
	// But for future-proofing we gratuitously force them
	// to differ by one byte.
	h.WriteByte(0)
	hasher{inGenericSig: false}.hash(h, t)
}
func (HasherIgnoreTags) Hash(h *maphash.Hash, t Type) {
	h.WriteByte(1)
	hasher{inGenericSig: false}.hash(h, t)
}

func (Hasher) Equal(x, y Type) bool           { return Identical(x, y) }
func (HasherIgnoreTags) Equal(x, y Type) bool { return IdenticalIgnoreTags(x, y) }

// hasher holds the state of a single hash traversal, namely,
// whether we are inside the signature of a generic function.
// This is used to optimize [hasher.hashTypeParam].
type hasher struct{ inGenericSig bool }

func (hr hasher) hash(h *maphash.Hash, t Type) {
	// See [Identical] for rationale.
	switch t := t.(type) {
	case *Alias:
		hr.hash(h, Unalias(t))

	case *Array:
		h.WriteByte('A')
		maphash.WriteComparable(h, t.Len())
		hr.hash(h, t.Elem())

	case *Basic:
		h.WriteByte('B')
		h.WriteByte(byte(t.Kind()))

	case *Chan:
		h.WriteByte('C')
		h.WriteByte(byte(t.Dir()))
		hr.hash(h, t.Elem())

	case *Interface:
		h.WriteByte('I')
		h.WriteByte(byte(t.NumMethods()))

		// 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 [Identical] for more details.

		// Hash the methods.
		//
		// Because [Identical] treats Methods as an unordered set,
		// we must either:
		// (a) sort the methods into some canonical order; or
		// (b) hash them each in parallel, combine them with a
		//     commutative operation such as + or ^, and then
		//     write this value into the primary hasher.
		// Since (a) requires allocation, we choose (b).
		var hash uint64
		for m := range t.Methods() {
			var subh maphash.Hash
			subh.SetSeed(h.Seed())
			// Ignore m.Pkg().
			// Use shallow hash on method signature to
			// avoid anonymous interface cycles.
			subh.WriteString(m.Name())
			hr.shallowHash(&subh, m.Type())
			hash ^= subh.Sum64()
		}
		maphash.WriteComparable(h, hash)

		// Hash type restrictions.
		// TODO(adonovan): call (fork of) InterfaceTermSet from
		// golang.org/x/tools/internal/typeparams/normalize.go.
		// hr.hashTermSet(h, terms)

	case *Map:
		h.WriteByte('M')
		hr.hash(h, t.Key())
		hr.hash(h, t.Elem())

	case *Named:
		h.WriteByte('N')
		hr.hashTypeName(h, t.Obj())
		for targ := range t.TypeArgs().Types() {
			hr.hash(h, targ)
		}

	case *Pointer:
		h.WriteByte('P')
		hr.hash(h, t.Elem())

	case *Signature:
		h.WriteByte('F')
		maphash.WriteComparable(h, t.Variadic())
		tparams := t.TypeParams()
		if n := tparams.Len(); n > 0 {
			hr.inGenericSig = true // affects constraints, params, and results

			maphash.WriteComparable(h, n)
			for tparam := range tparams.TypeParams() {
				hr.hash(h, tparam.Constraint())
			}
		}
		hr.hashTuple(h, t.Params())
		hr.hashTuple(h, t.Results())

	case *Slice:
		h.WriteByte('S')
		hr.hash(h, t.Elem())

	case *Struct:
		h.WriteByte('R') // mnemonic: a struct is a record type
		n := t.NumFields()
		h.WriteByte(byte(n))
		for i := range n {
			f := t.Field(i)
			maphash.WriteComparable(h, f.Anonymous())
			// Ignore t.Tag(i), so that a single hash function
			// can be used with both [Identical] and [IdenticalIgnoreTags].
			h.WriteString(f.Name()) // (ignore f.Pkg)
			hr.hash(h, f.Type())
		}

	case *Tuple:
		hr.hashTuple(h, t)

	case *TypeParam:
		hr.hashTypeParam(h, t)

	case *Union:
		h.WriteByte('U')
		// TODO(adonovan): opt: call (fork of) UnionTermSet from
		// golang.org/x/tools/internal/typeparams/normalize.go.
		// hr.hashTermSet(h, terms)

	default:
		panic(fmt.Sprintf("%T: %v", t, t))
	}
}

func (hr hasher) hashTuple(h *maphash.Hash, t *Tuple) {
	h.WriteByte('T')
	h.WriteByte(byte(t.Len()))
	for v := range t.Variables() {
		hr.hash(h, v.Type())
	}
}

// func (hr hasher) hashTermSet(h *maphash.Hash, terms []*Term) {
// 	h.WriteByte(byte(len(terms)))
// 	for _, term := range terms {
// 		// term order is not significant.
// 		h.WriteByte(byte(btoi(term.Tilde())))
// 		hr.hash(h, term.Type())
// 	}
// }

// hashTypeParam encodes a type parameter into hasher h.
func (hr hasher) hashTypeParam(h *maphash.Hash, t *TypeParam) {
	h.WriteByte('P')
	// 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, free TypeParams are
	// identical iff they are the same object, so we can use a
	// more discriminating hash consistent with object identity.
	// This optimization saves [Map] about 4% when hashing all the
	// Info.Types in the forward closure of net/http.
	if !hr.inGenericSig {
		// Optimization: outside a generic function signature,
		// use a more discrimating hash consistent with object identity.
		hr.hashTypeName(h, t.Obj())
	} else {
		h.WriteByte(byte(t.Index()))
	}
}

// hashTypeName hashes the pointer of tname.
func (hasher) hashTypeName(h *maphash.Hash, tname *TypeName) {
	h.WriteByte('N')
	// Since Identical uses == to compare TypeNames,
	// the hash function uses maphash.Comparable.
	maphash.WriteComparable(h, tname)
}

// 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 [Identical], case *Interface, for more.)
func (hr hasher) shallowHash(h *maphash.Hash, t Type) {
	// 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 *Alias:
		hr.shallowHash(h, Unalias(t))

	case *Array:
		h.WriteByte('A')
		maphash.WriteComparable(h, t.Len())
		// ignore t.Elem()

	case *Basic:
		h.WriteByte('B')
		h.WriteByte(byte(t.Kind()))

	case *Chan:
		h.WriteByte('C')
		// ignore Dir(), Elem()

	case *Interface:
		h.WriteByte('I')
		// no recursion here

	case *Map:
		h.WriteByte('M')
		// ignore Key(), Elem()

	case *Named:
		hr.hashTypeName(h, t.Obj())

	case *Pointer:
		h.WriteByte('P')
		// ignore t.Elem()

	case *Signature:
		h.WriteByte(byte(btoi(t.Variadic())))
		// The Signature/Tuple recursion is always
		// finite and invariably shallow.
		hr.shallowHash(h, t.Params())
		hr.shallowHash(h, t.Results())

	case *Slice:
		h.WriteByte('S')
		// ignore t.Elem()

	case *Struct:
		h.WriteByte('R') // mnemonic: a struct is a record type
		h.WriteByte(byte(t.NumFields()))
		// ignore t.Fields()

	case *Tuple:
		h.WriteByte('T')
		h.WriteByte(byte(t.Len()))
		for v := range t.Variables() {
			hr.shallowHash(h, v.Type())
		}

	case *TypeParam:
		hr.hashTypeParam(h, t)

	case *Union:
		h.WriteByte('U')
		// ignore term set

	default:
		panic(fmt.Sprintf("shallowHash: %T: %v", t, t))
	}
}

func btoi(b bool) int {
	if b {
		return 1
	} else {
		return 0
	}
}
