mirror of
https://github.com/Luzifer/cloudkeys-go.git
synced 2024-11-13 00:12:43 +00:00
341 lines
9 KiB
Go
341 lines
9 KiB
Go
// Copied with small adaptations from the reflect package in the
|
|
// Go source tree.
|
|
|
|
// Copyright 2009 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-golang file.
|
|
|
|
package checkers
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"time"
|
|
"unsafe"
|
|
)
|
|
|
|
var timeType = reflect.TypeOf(time.Time{})
|
|
|
|
// During deepValueEqual, must keep track of checks that are
|
|
// in progress. The comparison algorithm assumes that all
|
|
// checks in progress are true when it reencounters them.
|
|
// Visited comparisons are stored in a map indexed by visit.
|
|
type visit struct {
|
|
a1 uintptr
|
|
a2 uintptr
|
|
typ reflect.Type
|
|
}
|
|
|
|
type mismatchError struct {
|
|
v1, v2 reflect.Value
|
|
path string
|
|
how string
|
|
}
|
|
|
|
func (err *mismatchError) Error() string {
|
|
path := err.path
|
|
if path == "" {
|
|
path = "top level"
|
|
}
|
|
return fmt.Sprintf("mismatch at %s: %s; obtained %#v; expected %#v", path, err.how, printable(err.v1), printable(err.v2))
|
|
}
|
|
|
|
func printable(v reflect.Value) interface{} {
|
|
vi := interfaceOf(v)
|
|
switch vi := vi.(type) {
|
|
case time.Time:
|
|
return vi.UTC().Format(time.RFC3339Nano)
|
|
default:
|
|
return vi
|
|
}
|
|
}
|
|
|
|
// Tests for deep equality using reflected types. The map argument tracks
|
|
// comparisons that have already been seen, which allows short circuiting on
|
|
// recursive types.
|
|
func deepValueEqual(path string, v1, v2 reflect.Value, visited map[visit]bool, depth int) (ok bool, err error) {
|
|
errorf := func(f string, a ...interface{}) error {
|
|
return &mismatchError{
|
|
v1: v1,
|
|
v2: v2,
|
|
path: path,
|
|
how: fmt.Sprintf(f, a...),
|
|
}
|
|
}
|
|
if !v1.IsValid() || !v2.IsValid() {
|
|
if v1.IsValid() == v2.IsValid() {
|
|
return true, nil
|
|
}
|
|
return false, errorf("validity mismatch")
|
|
}
|
|
if v1.Type() != v2.Type() {
|
|
return false, errorf("type mismatch %s vs %s", v1.Type(), v2.Type())
|
|
}
|
|
|
|
// if depth > 10 { panic("deepValueEqual") } // for debugging
|
|
hard := func(k reflect.Kind) bool {
|
|
switch k {
|
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.Struct:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
if v1.CanAddr() && v2.CanAddr() && hard(v1.Kind()) {
|
|
addr1 := v1.UnsafeAddr()
|
|
addr2 := v2.UnsafeAddr()
|
|
if addr1 > addr2 {
|
|
// Canonicalize order to reduce number of entries in visited.
|
|
addr1, addr2 = addr2, addr1
|
|
}
|
|
|
|
// Short circuit if references are identical ...
|
|
if addr1 == addr2 {
|
|
return true, nil
|
|
}
|
|
|
|
// ... or already seen
|
|
typ := v1.Type()
|
|
v := visit{addr1, addr2, typ}
|
|
if visited[v] {
|
|
return true, nil
|
|
}
|
|
|
|
// Remember for later.
|
|
visited[v] = true
|
|
}
|
|
|
|
switch v1.Kind() {
|
|
case reflect.Array:
|
|
if v1.Len() != v2.Len() {
|
|
// can't happen!
|
|
return false, errorf("length mismatch, %d vs %d", v1.Len(), v2.Len())
|
|
}
|
|
for i := 0; i < v1.Len(); i++ {
|
|
if ok, err := deepValueEqual(
|
|
fmt.Sprintf("%s[%d]", path, i),
|
|
v1.Index(i), v2.Index(i), visited, depth+1); !ok {
|
|
return false, err
|
|
}
|
|
}
|
|
return true, nil
|
|
case reflect.Slice:
|
|
// We treat a nil slice the same as an empty slice.
|
|
if v1.Len() != v2.Len() {
|
|
return false, errorf("length mismatch, %d vs %d", v1.Len(), v2.Len())
|
|
}
|
|
if v1.Pointer() == v2.Pointer() {
|
|
return true, nil
|
|
}
|
|
for i := 0; i < v1.Len(); i++ {
|
|
if ok, err := deepValueEqual(
|
|
fmt.Sprintf("%s[%d]", path, i),
|
|
v1.Index(i), v2.Index(i), visited, depth+1); !ok {
|
|
return false, err
|
|
}
|
|
}
|
|
return true, nil
|
|
case reflect.Interface:
|
|
if v1.IsNil() || v2.IsNil() {
|
|
if v1.IsNil() != v2.IsNil() {
|
|
return false, errorf("nil vs non-nil interface mismatch")
|
|
}
|
|
return true, nil
|
|
}
|
|
return deepValueEqual(path, v1.Elem(), v2.Elem(), visited, depth+1)
|
|
case reflect.Ptr:
|
|
return deepValueEqual("(*"+path+")", v1.Elem(), v2.Elem(), visited, depth+1)
|
|
case reflect.Struct:
|
|
if v1.Type() == timeType {
|
|
// Special case for time - we ignore the time zone.
|
|
t1 := interfaceOf(v1).(time.Time)
|
|
t2 := interfaceOf(v2).(time.Time)
|
|
if t1.Equal(t2) {
|
|
return true, nil
|
|
}
|
|
return false, errorf("unequal")
|
|
}
|
|
for i, n := 0, v1.NumField(); i < n; i++ {
|
|
path := path + "." + v1.Type().Field(i).Name
|
|
if ok, err := deepValueEqual(path, v1.Field(i), v2.Field(i), visited, depth+1); !ok {
|
|
return false, err
|
|
}
|
|
}
|
|
return true, nil
|
|
case reflect.Map:
|
|
if v1.IsNil() != v2.IsNil() {
|
|
return false, errorf("nil vs non-nil mismatch")
|
|
}
|
|
if v1.Len() != v2.Len() {
|
|
return false, errorf("length mismatch, %d vs %d", v1.Len(), v2.Len())
|
|
}
|
|
if v1.Pointer() == v2.Pointer() {
|
|
return true, nil
|
|
}
|
|
for _, k := range v1.MapKeys() {
|
|
var p string
|
|
if k.CanInterface() {
|
|
p = path + "[" + fmt.Sprintf("%#v", k.Interface()) + "]"
|
|
} else {
|
|
p = path + "[someKey]"
|
|
}
|
|
if ok, err := deepValueEqual(p, v1.MapIndex(k), v2.MapIndex(k), visited, depth+1); !ok {
|
|
return false, err
|
|
}
|
|
}
|
|
return true, nil
|
|
case reflect.Func:
|
|
if v1.IsNil() && v2.IsNil() {
|
|
return true, nil
|
|
}
|
|
// Can't do better than this:
|
|
return false, errorf("non-nil functions")
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if v1.Int() != v2.Int() {
|
|
return false, errorf("unequal")
|
|
}
|
|
return true, nil
|
|
case reflect.Uint, reflect.Uintptr, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
if v1.Uint() != v2.Uint() {
|
|
return false, errorf("unequal")
|
|
}
|
|
return true, nil
|
|
case reflect.Float32, reflect.Float64:
|
|
if v1.Float() != v2.Float() {
|
|
return false, errorf("unequal")
|
|
}
|
|
return true, nil
|
|
case reflect.Complex64, reflect.Complex128:
|
|
if v1.Complex() != v2.Complex() {
|
|
return false, errorf("unequal")
|
|
}
|
|
return true, nil
|
|
case reflect.Bool:
|
|
if v1.Bool() != v2.Bool() {
|
|
return false, errorf("unequal")
|
|
}
|
|
return true, nil
|
|
case reflect.String:
|
|
if v1.String() != v2.String() {
|
|
return false, errorf("unequal")
|
|
}
|
|
return true, nil
|
|
case reflect.Chan, reflect.UnsafePointer:
|
|
if v1.Pointer() != v2.Pointer() {
|
|
return false, errorf("unequal")
|
|
}
|
|
return true, nil
|
|
default:
|
|
panic("unexpected type " + v1.Type().String())
|
|
}
|
|
}
|
|
|
|
// DeepEqual tests for deep equality. It uses normal == equality where
|
|
// possible but will scan elements of arrays, slices, maps, and fields
|
|
// of structs. In maps, keys are compared with == but elements use deep
|
|
// equality. DeepEqual correctly handles recursive types. Functions are
|
|
// equal only if they are both nil.
|
|
//
|
|
// DeepEqual differs from reflect.DeepEqual in two ways:
|
|
// - an empty slice is considered equal to a nil slice.
|
|
// - two time.Time values that represent the same instant
|
|
// but with different time zones are considered equal.
|
|
//
|
|
// If the two values compare unequal, the resulting error holds the
|
|
// first difference encountered.
|
|
func DeepEqual(a1, a2 interface{}) (bool, error) {
|
|
errorf := func(f string, a ...interface{}) error {
|
|
return &mismatchError{
|
|
v1: reflect.ValueOf(a1),
|
|
v2: reflect.ValueOf(a2),
|
|
path: "",
|
|
how: fmt.Sprintf(f, a...),
|
|
}
|
|
}
|
|
if a1 == nil || a2 == nil {
|
|
if a1 == a2 {
|
|
return true, nil
|
|
}
|
|
return false, errorf("nil vs non-nil mismatch")
|
|
}
|
|
v1 := reflect.ValueOf(a1)
|
|
v2 := reflect.ValueOf(a2)
|
|
if v1.Type() != v2.Type() {
|
|
return false, errorf("type mismatch %s vs %s", v1.Type(), v2.Type())
|
|
}
|
|
return deepValueEqual("", v1, v2, make(map[visit]bool), 0)
|
|
}
|
|
|
|
// interfaceOf returns v.Interface() even if v.CanInterface() == false.
|
|
// This enables us to call fmt.Printf on a value even if it's derived
|
|
// from inside an unexported field.
|
|
// See https://code.google.com/p/go/issues/detail?id=8965
|
|
// for a possible future alternative to this hack.
|
|
func interfaceOf(v reflect.Value) interface{} {
|
|
if !v.IsValid() {
|
|
return nil
|
|
}
|
|
return bypassCanInterface(v).Interface()
|
|
}
|
|
|
|
type flag uintptr
|
|
|
|
var flagRO flag
|
|
|
|
// constants copied from reflect/value.go
|
|
const (
|
|
// The value of flagRO up to and including Go 1.3.
|
|
flagRO1p3 = 1 << 0
|
|
|
|
// The value of flagRO from Go 1.4.
|
|
flagRO1p4 = 1 << 5
|
|
)
|
|
|
|
var flagValOffset = func() uintptr {
|
|
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
|
if !ok {
|
|
panic("reflect.Value has no flag field")
|
|
}
|
|
return field.Offset
|
|
}()
|
|
|
|
func flagField(v *reflect.Value) *flag {
|
|
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
|
}
|
|
|
|
// bypassCanInterface returns a version of v that
|
|
// bypasses the CanInterface check.
|
|
func bypassCanInterface(v reflect.Value) reflect.Value {
|
|
if !v.IsValid() || v.CanInterface() {
|
|
return v
|
|
}
|
|
*flagField(&v) &^= flagRO
|
|
return v
|
|
}
|
|
|
|
// Sanity checks against future reflect package changes
|
|
// to the type or semantics of the Value.flag field.
|
|
func init() {
|
|
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
|
if !ok {
|
|
panic("reflect.Value has no flag field")
|
|
}
|
|
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
|
panic("reflect.Value flag field has changed kind")
|
|
}
|
|
var t struct {
|
|
a int
|
|
A int
|
|
}
|
|
vA := reflect.ValueOf(t).FieldByName("A")
|
|
va := reflect.ValueOf(t).FieldByName("a")
|
|
flagA := *flagField(&vA)
|
|
flaga := *flagField(&va)
|
|
|
|
// Infer flagRO from the difference between the flags
|
|
// for the (otherwise identical) fields in t.
|
|
flagRO = flagA ^ flaga
|
|
if flagRO != flagRO1p3 && flagRO != flagRO1p4 {
|
|
panic("reflect.Value read-only flag has changed semantics")
|
|
}
|
|
}
|