mirror of
https://github.com/golang/go
synced 2024-11-18 20:34:39 -07:00
go.tools/ssa/interp: fixes to equivalence relations.
- This change implements the correct type-based equivalence relation for aggregate types. e.g. comparison of struct types no longer compares the anonymous fields. We do analogous things for hash(). - equals() and eqnil() have been separated: the former panics for uncomparable types, the latter permits comparisons of slice/map/func types against a literal nil and is intended for use only by "static" ssa.BinOp(EQL), not "dynamic" slice comparisons encountered during (e.g.) interface comparisons, which should panic regardless of operand nilness. - we use a (global) typemap.Hasher to compute type hashes; hashing the Type.String() value was not sound. + tests. NB, this change unearthed a bug in defer/recover within init(); it will be fixed in a followup change. R=gri, crawshaw CC=golang-dev https://golang.org/cl/13719043
This commit is contained in:
parent
3b5de067a1
commit
5db6491e90
@ -32,9 +32,6 @@
|
|||||||
//
|
//
|
||||||
// * map iteration is asymptotically inefficient.
|
// * map iteration is asymptotically inefficient.
|
||||||
//
|
//
|
||||||
// * the equivalence relation for structs doesn't skip over blank
|
|
||||||
// fields.
|
|
||||||
//
|
|
||||||
// * the sizes of the int, uint and uintptr types in the target
|
// * the sizes of the int, uint and uintptr types in the target
|
||||||
// program are assumed to be the same as those of the interpreter
|
// program are assumed to be the same as those of the interpreter
|
||||||
// itself.
|
// itself.
|
||||||
@ -163,7 +160,7 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation {
|
|||||||
fr.env[instr] = unop(instr, fr.get(instr.X))
|
fr.env[instr] = unop(instr, fr.get(instr.X))
|
||||||
|
|
||||||
case *ssa.BinOp:
|
case *ssa.BinOp:
|
||||||
fr.env[instr] = binop(instr.Op, fr.get(instr.X), fr.get(instr.Y))
|
fr.env[instr] = binop(instr.Op, instr.X.Type(), fr.get(instr.X), fr.get(instr.Y))
|
||||||
|
|
||||||
case *ssa.Call:
|
case *ssa.Call:
|
||||||
fn, args := prepareCall(fr, &instr.Call)
|
fn, args := prepareCall(fr, &instr.Call)
|
||||||
|
@ -184,7 +184,7 @@ func run(t *testing.T, dir, input string) bool {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump; ./ssadump -build=CFP %s\n", input)
|
hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input)
|
||||||
mainInfo := imp.LoadMainPackage(files...)
|
mainInfo := imp.LoadMainPackage(files...)
|
||||||
|
|
||||||
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
|
||||||
@ -197,7 +197,7 @@ func run(t *testing.T, dir, input string) bool {
|
|||||||
mainPkg := prog.Package(mainInfo.Pkg)
|
mainPkg := prog.Package(mainInfo.Pkg)
|
||||||
mainPkg.CreateTestMainFunction() // (no-op if main already exists)
|
mainPkg.CreateTestMainFunction() // (no-op if main already exists)
|
||||||
|
|
||||||
hint = fmt.Sprintf("To trace execution, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump; ./ssadump -build=C -run --interp=T %s\n", input)
|
hint = fmt.Sprintf("To trace execution, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=C -run --interp=T %s\n", input)
|
||||||
if exitCode := interp.Interpret(mainPkg, 0, inputs[0], []string{}); exitCode != 0 {
|
if exitCode := interp.Interpret(mainPkg, 0, inputs[0], []string{}); exitCode != 0 {
|
||||||
t.Errorf("interp.Interpret(%s) exited with code %d, want zero", inputs, exitCode)
|
t.Errorf("interp.Interpret(%s) exited with code %d, want zero", inputs, exitCode)
|
||||||
return false
|
return false
|
||||||
|
@ -16,8 +16,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type hashable interface {
|
type hashable interface {
|
||||||
hash() int
|
hash(t types.Type) int
|
||||||
eq(x interface{}) bool
|
eq(t types.Type, x interface{}) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type entry struct {
|
type entry struct {
|
||||||
@ -31,6 +31,7 @@ type entry struct {
|
|||||||
// tests when walking the linked list. Rehashing is done by the
|
// tests when walking the linked list. Rehashing is done by the
|
||||||
// underlying map.
|
// underlying map.
|
||||||
type hashmap struct {
|
type hashmap struct {
|
||||||
|
keyType types.Type
|
||||||
table map[int]*entry
|
table map[int]*entry
|
||||||
length int // number of entries in map
|
length int // number of entries in map
|
||||||
}
|
}
|
||||||
@ -41,23 +42,23 @@ func makeMap(kt types.Type, reserve int) value {
|
|||||||
if usesBuiltinMap(kt) {
|
if usesBuiltinMap(kt) {
|
||||||
return make(map[value]value, reserve)
|
return make(map[value]value, reserve)
|
||||||
}
|
}
|
||||||
return &hashmap{table: make(map[int]*entry, reserve)}
|
return &hashmap{keyType: kt, table: make(map[int]*entry, reserve)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete removes the association for key k, if any.
|
// delete removes the association for key k, if any.
|
||||||
func (m *hashmap) delete(k hashable) {
|
func (m *hashmap) delete(k hashable) {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
hash := k.hash()
|
hash := k.hash(m.keyType)
|
||||||
head := m.table[hash]
|
head := m.table[hash]
|
||||||
if head != nil {
|
if head != nil {
|
||||||
if k.eq(head.key) {
|
if k.eq(m.keyType, head.key) {
|
||||||
m.table[hash] = head.next
|
m.table[hash] = head.next
|
||||||
m.length--
|
m.length--
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
prev := head
|
prev := head
|
||||||
for e := head.next; e != nil; e = e.next {
|
for e := head.next; e != nil; e = e.next {
|
||||||
if k.eq(e.key) {
|
if k.eq(m.keyType, e.key) {
|
||||||
prev.next = e.next
|
prev.next = e.next
|
||||||
m.length--
|
m.length--
|
||||||
return
|
return
|
||||||
@ -72,9 +73,9 @@ func (m *hashmap) delete(k hashable) {
|
|||||||
// value(nil) otherwise.
|
// value(nil) otherwise.
|
||||||
func (m *hashmap) lookup(k hashable) value {
|
func (m *hashmap) lookup(k hashable) value {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
hash := k.hash()
|
hash := k.hash(m.keyType)
|
||||||
for e := m.table[hash]; e != nil; e = e.next {
|
for e := m.table[hash]; e != nil; e = e.next {
|
||||||
if k.eq(e.key) {
|
if k.eq(m.keyType, e.key) {
|
||||||
return e.value
|
return e.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,10 +88,10 @@ func (m *hashmap) lookup(k hashable) value {
|
|||||||
// k, the previous key remains in the map and its associated value is
|
// k, the previous key remains in the map and its associated value is
|
||||||
// updated.
|
// updated.
|
||||||
func (m *hashmap) insert(k hashable, v value) {
|
func (m *hashmap) insert(k hashable, v value) {
|
||||||
hash := k.hash()
|
hash := k.hash(m.keyType)
|
||||||
head := m.table[hash]
|
head := m.table[hash]
|
||||||
for e := head; e != nil; e = e.next {
|
for e := head; e != nil; e = e.next {
|
||||||
if k.eq(e.key) {
|
if k.eq(m.keyType, e.key) {
|
||||||
e.value = v
|
e.value = v
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -301,7 +301,7 @@ func lookup(instr *ssa.Lookup, x, idx value) value {
|
|||||||
// numeric datatypes and strings. Both operands must have identical
|
// numeric datatypes and strings. Both operands must have identical
|
||||||
// dynamic type.
|
// dynamic type.
|
||||||
//
|
//
|
||||||
func binop(op token.Token, x, y value) value {
|
func binop(op token.Token, t types.Type, x, y value) value {
|
||||||
switch op {
|
switch op {
|
||||||
case token.ADD:
|
case token.ADD:
|
||||||
switch x.(type) {
|
switch x.(type) {
|
||||||
@ -690,10 +690,10 @@ func binop(op token.Token, x, y value) value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case token.EQL:
|
case token.EQL:
|
||||||
return equals(x, y)
|
return eqnil(t, x, y)
|
||||||
|
|
||||||
case token.NEQ:
|
case token.NEQ:
|
||||||
return !equals(x, y)
|
return !eqnil(t, x, y)
|
||||||
|
|
||||||
case token.GTR:
|
case token.GTR:
|
||||||
switch x.(type) {
|
switch x.(type) {
|
||||||
@ -762,6 +762,39 @@ func binop(op token.Token, x, y value) value {
|
|||||||
panic(fmt.Sprintf("invalid binary op: %T %s %T", x, op, y))
|
panic(fmt.Sprintf("invalid binary op: %T %s %T", x, op, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eqnil returns the comparison x == y using the equivalence relation
|
||||||
|
// appropriate for type t.
|
||||||
|
// If t is a reference type, at most one of x or y may be a nil value
|
||||||
|
// of that type.
|
||||||
|
//
|
||||||
|
func eqnil(t types.Type, x, y value) bool {
|
||||||
|
switch t.Underlying().(type) {
|
||||||
|
case *types.Map, *types.Signature, *types.Slice:
|
||||||
|
// Since these types don't support comparison,
|
||||||
|
// one of the operands must be a literal nil.
|
||||||
|
switch x := x.(type) {
|
||||||
|
case *hashmap:
|
||||||
|
return (x != nil) == (y.(*hashmap) != nil)
|
||||||
|
case map[value]value:
|
||||||
|
return (x != nil) == (y.(map[value]value) != nil)
|
||||||
|
case *ssa.Function:
|
||||||
|
switch y := y.(type) {
|
||||||
|
case *ssa.Function:
|
||||||
|
return (x != nil) == (y != nil)
|
||||||
|
case *closure:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case *closure:
|
||||||
|
return (x != nil) == (y.(*ssa.Function) != nil)
|
||||||
|
case []value:
|
||||||
|
return (x != nil) == (y.([]value) != nil)
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("eqnil(%s): illegal dynamic type: %T", t, x))
|
||||||
|
}
|
||||||
|
|
||||||
|
return equals(t, x, y)
|
||||||
|
}
|
||||||
|
|
||||||
func unop(instr *ssa.UnOp, x value) value {
|
func unop(instr *ssa.UnOp, x value) value {
|
||||||
switch instr.Op {
|
switch instr.Op {
|
||||||
case token.ARROW: // receive
|
case token.ARROW: // receive
|
||||||
|
53
ssa/interp/testdata/coverage.go
vendored
53
ssa/interp/testdata/coverage.go
vendored
@ -377,14 +377,15 @@ func init() {
|
|||||||
|
|
||||||
// An I->I type-assert fails iff the value is nil.
|
// An I->I type-assert fails iff the value is nil.
|
||||||
func init() {
|
func init() {
|
||||||
defer func() {
|
// TODO(adonovan): temporarily disabled; see comment at bottom of file.
|
||||||
r := fmt.Sprint(recover())
|
// defer func() {
|
||||||
if r != "interface conversion: interface is nil, not main.I" {
|
// r := fmt.Sprint(recover())
|
||||||
panic("I->I type assertion succeeed for nil value")
|
// if r != "interface conversion: interface is nil, not main.I" {
|
||||||
}
|
// panic("I->I type assertion succeeed for nil value")
|
||||||
}()
|
// }
|
||||||
var x I
|
// }()
|
||||||
_ = x.(I)
|
// var x I
|
||||||
|
// _ = x.(I)
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
@ -503,3 +504,39 @@ func init() {
|
|||||||
panic(deferCount) // defer call has not run!
|
panic(deferCount) // defer call has not run!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Struct equivalence ignores blank fields.
|
||||||
|
type s struct{ x, _, z int }
|
||||||
|
s1 := s{x: 1, z: 3}
|
||||||
|
s2 := s{x: 1, z: 3}
|
||||||
|
if s1 != s2 {
|
||||||
|
panic("not equal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// A slice var can be compared to const []T nil.
|
||||||
|
var i interface{} = []string{"foo"}
|
||||||
|
var j interface{} = []string(nil)
|
||||||
|
if i.([]string) == nil {
|
||||||
|
panic("expected i non-nil")
|
||||||
|
}
|
||||||
|
if j.([]string) != nil {
|
||||||
|
panic("expected j nil")
|
||||||
|
}
|
||||||
|
// But two slices cannot be compared, even if one is nil.
|
||||||
|
defer func() {
|
||||||
|
r := fmt.Sprint(recover())
|
||||||
|
if r != "runtime error: comparing uncomparable type []string" {
|
||||||
|
panic("want panic from slice comparison, got " + r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
_ = i == j // interface comparison recurses on types
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(adonovan): fix: the interpreter doesn't correctly implement
|
||||||
|
// defer/recover in an init function concatenated from many parts: the
|
||||||
|
// first recover causes the entire init() to return, not jump to the
|
||||||
|
// next part. This will be fixed in a follow-up CL. Until then,
|
||||||
|
// beware: adding new init() functions here will have no effect!
|
||||||
|
@ -21,7 +21,7 @@ package interp
|
|||||||
// - array --- arrays.
|
// - array --- arrays.
|
||||||
// - *value --- pointers. Careful: *value is a distinct type from *array etc.
|
// - *value --- pointers. Careful: *value is a distinct type from *array etc.
|
||||||
// - *ssa.Function \
|
// - *ssa.Function \
|
||||||
// *ssa.Builtin } --- functions.
|
// *ssa.Builtin } --- functions. A nil 'func' is always of type *ssa.Function.
|
||||||
// *closure /
|
// *closure /
|
||||||
// - tuple --- as returned by Ret, Next, "value,ok" modes, etc.
|
// - tuple --- as returned by Ret, Next, "value,ok" modes, etc.
|
||||||
// - iter --- iterators from 'range' over map or string.
|
// - iter --- iterators from 'range' over map or string.
|
||||||
@ -39,9 +39,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"code.google.com/p/go.tools/go/types"
|
"code.google.com/p/go.tools/go/types"
|
||||||
|
"code.google.com/p/go.tools/go/types/typemap"
|
||||||
"code.google.com/p/go.tools/ssa"
|
"code.google.com/p/go.tools/ssa"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -88,13 +90,18 @@ func hashString(s string) int {
|
|||||||
return int(h)
|
return int(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.Mutex
|
||||||
|
hasher = typemap.MakeHasher()
|
||||||
|
)
|
||||||
|
|
||||||
// hashType returns a hash for t such that
|
// hashType returns a hash for t such that
|
||||||
// types.IsIdentical(x, y) => hashType(x) == hashType(y).
|
// types.IsIdentical(x, y) => hashType(x) == hashType(y).
|
||||||
func hashType(t types.Type) int {
|
func hashType(t types.Type) int {
|
||||||
// TODO(adonovan): fix: not sound! "IsIdentical" Signatures
|
mu.Lock()
|
||||||
// may have different parameter names; use typemap.Hasher when
|
h := int(hasher.Hash(t))
|
||||||
// available.
|
mu.Unlock()
|
||||||
return hashString(t.String()) // TODO(gri): provide a better hash
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
// usesBuiltinMap returns true if the built-in hash function and
|
// usesBuiltinMap returns true if the built-in hash function and
|
||||||
@ -118,67 +125,72 @@ func usesBuiltinMap(t types.Type) bool {
|
|||||||
panic(fmt.Sprintf("invalid map key type: %T", t))
|
panic(fmt.Sprintf("invalid map key type: %T", t))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x array) eq(_y interface{}) bool {
|
func (x array) eq(t types.Type, _y interface{}) bool {
|
||||||
y := _y.(array)
|
y := _y.(array)
|
||||||
|
tElt := t.Underlying().(*types.Array).Elem()
|
||||||
for i, xi := range x {
|
for i, xi := range x {
|
||||||
if !equals(xi, y[i]) {
|
if !equals(tElt, xi, y[i]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x array) hash() int {
|
func (x array) hash(t types.Type) int {
|
||||||
h := 0
|
h := 0
|
||||||
|
tElt := t.Underlying().(*types.Array).Elem()
|
||||||
for _, xi := range x {
|
for _, xi := range x {
|
||||||
h += hash(xi)
|
h += hash(tElt, xi)
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x structure) eq(_y interface{}) bool {
|
func (x structure) eq(t types.Type, _y interface{}) bool {
|
||||||
y := _y.(structure)
|
y := _y.(structure)
|
||||||
// TODO(adonovan): fix: only non-blank fields should be
|
tStruct := t.Underlying().(*types.Struct)
|
||||||
// compared. This requires that we have type information
|
for i, n := 0, tStruct.NumFields(); i < n; i++ {
|
||||||
// available from the enclosing == operation or map access;
|
if f := tStruct.Field(i); !f.Anonymous() {
|
||||||
// the value is not sufficient.
|
if !equals(f.Type(), x[i], y[i]) {
|
||||||
for i, xi := range x {
|
|
||||||
if !equals(xi, y[i]) {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x structure) hash() int {
|
func (x structure) hash(t types.Type) int {
|
||||||
|
tStruct := t.Underlying().(*types.Struct)
|
||||||
h := 0
|
h := 0
|
||||||
for _, xi := range x {
|
for i, n := 0, tStruct.NumFields(); i < n; i++ {
|
||||||
h += hash(xi)
|
if f := tStruct.Field(i); !f.Anonymous() {
|
||||||
|
h += hash(f.Type(), x[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return h
|
return h
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x iface) eq(_y interface{}) bool {
|
func (x iface) eq(t types.Type, _y interface{}) bool {
|
||||||
y := _y.(iface)
|
y := _y.(iface)
|
||||||
return types.IsIdentical(x.t, y.t) && (x.t == nil || equals(x.v, y.v))
|
return types.IsIdentical(x.t, y.t) && (x.t == nil || equals(x.t, x.v, y.v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x iface) hash() int {
|
func (x iface) hash(_ types.Type) int {
|
||||||
return hashType(x.t)*8581 + hash(x.v)
|
return hashType(x.t)*8581 + hash(x.t, x.v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x rtype) hash() int {
|
func (x rtype) hash(_ types.Type) int {
|
||||||
return hashType(x.t)
|
return hashType(x.t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x rtype) eq(y interface{}) bool {
|
func (x rtype) eq(_ types.Type, y interface{}) bool {
|
||||||
return types.IsIdentical(x.t, y.(rtype).t)
|
return types.IsIdentical(x.t, y.(rtype).t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// equals returns true iff x and y are equal according to Go's
|
// equals returns true iff x and y are equal according to Go's
|
||||||
// linguistic equivalence relation. In a well-typed program, the
|
// linguistic equivalence relation for type t.
|
||||||
// types of x and y are guaranteed equal.
|
// In a well-typed program, the dynamic types of x and y are
|
||||||
func equals(x, y value) bool {
|
// guaranteed equal.
|
||||||
|
func equals(t types.Type, x, y value) bool {
|
||||||
switch x := x.(type) {
|
switch x := x.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
return x == y.(bool)
|
return x == y.(bool)
|
||||||
@ -219,31 +231,23 @@ func equals(x, y value) bool {
|
|||||||
case chan value:
|
case chan value:
|
||||||
return x == y.(chan value)
|
return x == y.(chan value)
|
||||||
case structure:
|
case structure:
|
||||||
return x.eq(y)
|
return x.eq(t, y)
|
||||||
case array:
|
case array:
|
||||||
return x.eq(y)
|
return x.eq(t, y)
|
||||||
case iface:
|
case iface:
|
||||||
return x.eq(y)
|
return x.eq(t, y)
|
||||||
case rtype:
|
case rtype:
|
||||||
return x.eq(y)
|
return x.eq(t, y)
|
||||||
|
|
||||||
// Since the following types don't support comparison,
|
|
||||||
// these cases are only reachable if one of x or y is
|
|
||||||
// (literally) nil.
|
|
||||||
case *hashmap:
|
|
||||||
return x == y.(*hashmap)
|
|
||||||
case map[value]value:
|
|
||||||
return (x != nil) == (y.(map[value]value) != nil)
|
|
||||||
case *ssa.Function, *closure:
|
|
||||||
return x == y
|
|
||||||
case []value:
|
|
||||||
return (x != nil) == (y.([]value) != nil)
|
|
||||||
}
|
}
|
||||||
panic(fmt.Sprintf("comparing incomparable type %T", x))
|
|
||||||
|
// Since map, func and slice don't support comparison, this
|
||||||
|
// case is only reachable if one of x or y is literally nil
|
||||||
|
// (handled in eqnil) or via interface{} values.
|
||||||
|
panic(fmt.Sprintf("runtime error: comparing uncomparable type %s", t))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns an integer hash of x such that equals(x, y) => hash(x) == hash(y).
|
// Returns an integer hash of x such that equals(x, y) => hash(x) == hash(y).
|
||||||
func hash(x value) int {
|
func hash(t types.Type, x value) int {
|
||||||
switch x := x.(type) {
|
switch x := x.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
if x {
|
if x {
|
||||||
@ -287,13 +291,13 @@ func hash(x value) int {
|
|||||||
case chan value:
|
case chan value:
|
||||||
return int(uintptr(reflect.ValueOf(x).Pointer()))
|
return int(uintptr(reflect.ValueOf(x).Pointer()))
|
||||||
case structure:
|
case structure:
|
||||||
return x.hash()
|
return x.hash(t)
|
||||||
case array:
|
case array:
|
||||||
return x.hash()
|
return x.hash(t)
|
||||||
case iface:
|
case iface:
|
||||||
return x.hash()
|
return x.hash(t)
|
||||||
case rtype:
|
case rtype:
|
||||||
return x.hash()
|
return x.hash(t)
|
||||||
}
|
}
|
||||||
panic(fmt.Sprintf("%T is unhashable", x))
|
panic(fmt.Sprintf("%T is unhashable", x))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user