reflect: add support for DeepEqual
The implementation has been mostly copied from the Go reference
implementation with some small changes to fit TinyGo.
Source: 77a11c05d6/src/reflect/deepequal.go
In addition, this commit also contains the following:
- A set of tests copied from the Go reflect package.
- An increased stack size for the riscv-qemu and hifive1-qemu targets
(because they otherwise fail to run the tests). Because these
targets are only used for testing, this seems fine to me.
Этот коммит содержится в:
родитель
5866a47e77
коммит
335fb71d2f
7 изменённых файлов: 432 добавлений и 7 удалений
4
Makefile
4
Makefile
|
@ -210,11 +210,13 @@ TEST_PACKAGES = \
|
|||
index/suffixarray \
|
||||
math \
|
||||
math/cmplx \
|
||||
net/mail \
|
||||
reflect \
|
||||
testing \
|
||||
testing/iotest \
|
||||
text/scanner \
|
||||
text/scanner \
|
||||
unicode \
|
||||
unicode/utf16 \
|
||||
unicode/utf8 \
|
||||
|
||||
# Test known-working standard library packages.
|
||||
|
|
188
src/reflect/all_test.go
Обычный файл
188
src/reflect/all_test.go
Обычный файл
|
@ -0,0 +1,188 @@
|
|||
// 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 file.
|
||||
|
||||
package reflect_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
. "reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Basic struct {
|
||||
x int
|
||||
y float32
|
||||
}
|
||||
|
||||
type NotBasic Basic
|
||||
|
||||
type DeepEqualTest struct {
|
||||
a, b interface{}
|
||||
eq bool
|
||||
}
|
||||
|
||||
// Simple functions for DeepEqual tests.
|
||||
var (
|
||||
fn1 func() // nil.
|
||||
fn2 func() // nil.
|
||||
fn3 = func() { fn1() } // Not nil.
|
||||
)
|
||||
|
||||
type self struct{}
|
||||
|
||||
type Loopy interface{}
|
||||
|
||||
var loopy1, loopy2 Loopy
|
||||
var cycleMap1, cycleMap2, cycleMap3 map[string]interface{}
|
||||
|
||||
type structWithSelfPtr struct {
|
||||
p *structWithSelfPtr
|
||||
s string
|
||||
}
|
||||
|
||||
func init() {
|
||||
loopy1 = &loopy2
|
||||
loopy2 = &loopy1
|
||||
|
||||
cycleMap1 = map[string]interface{}{}
|
||||
cycleMap1["cycle"] = cycleMap1
|
||||
cycleMap2 = map[string]interface{}{}
|
||||
cycleMap2["cycle"] = cycleMap2
|
||||
cycleMap3 = map[string]interface{}{}
|
||||
cycleMap3["different"] = cycleMap3
|
||||
}
|
||||
|
||||
// Note: all tests involving maps have been commented out because they aren't
|
||||
// supported yet.
|
||||
var deepEqualTests = []DeepEqualTest{
|
||||
// Equalities
|
||||
{nil, nil, true},
|
||||
{1, 1, true},
|
||||
{int32(1), int32(1), true},
|
||||
{0.5, 0.5, true},
|
||||
{float32(0.5), float32(0.5), true},
|
||||
{"hello", "hello", true},
|
||||
{make([]int, 10), make([]int, 10), true},
|
||||
{&[3]int{1, 2, 3}, &[3]int{1, 2, 3}, true},
|
||||
{Basic{1, 0.5}, Basic{1, 0.5}, true},
|
||||
{error(nil), error(nil), true},
|
||||
//{map[int]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, true},
|
||||
{fn1, fn2, true},
|
||||
{[]byte{1, 2, 3}, []byte{1, 2, 3}, true},
|
||||
{[]MyByte{1, 2, 3}, []MyByte{1, 2, 3}, true},
|
||||
{MyBytes{1, 2, 3}, MyBytes{1, 2, 3}, true},
|
||||
|
||||
// Inequalities
|
||||
{1, 2, false},
|
||||
{int32(1), int32(2), false},
|
||||
{0.5, 0.6, false},
|
||||
{float32(0.5), float32(0.6), false},
|
||||
{"hello", "hey", false},
|
||||
{make([]int, 10), make([]int, 11), false},
|
||||
{&[3]int{1, 2, 3}, &[3]int{1, 2, 4}, false},
|
||||
{Basic{1, 0.5}, Basic{1, 0.6}, false},
|
||||
{Basic{1, 0}, Basic{2, 0}, false},
|
||||
//{map[int]string{1: "one", 3: "two"}, map[int]string{2: "two", 1: "one"}, false},
|
||||
//{map[int]string{1: "one", 2: "txo"}, map[int]string{2: "two", 1: "one"}, false},
|
||||
//{map[int]string{1: "one"}, map[int]string{2: "two", 1: "one"}, false},
|
||||
//{map[int]string{2: "two", 1: "one"}, map[int]string{1: "one"}, false},
|
||||
{nil, 1, false},
|
||||
{1, nil, false},
|
||||
{fn1, fn3, false},
|
||||
{fn3, fn3, false},
|
||||
{[][]int{{1}}, [][]int{{2}}, false},
|
||||
{&structWithSelfPtr{p: &structWithSelfPtr{s: "a"}}, &structWithSelfPtr{p: &structWithSelfPtr{s: "b"}}, false},
|
||||
|
||||
// Fun with floating point.
|
||||
{math.NaN(), math.NaN(), false},
|
||||
{&[1]float64{math.NaN()}, &[1]float64{math.NaN()}, false},
|
||||
{&[1]float64{math.NaN()}, self{}, true},
|
||||
{[]float64{math.NaN()}, []float64{math.NaN()}, false},
|
||||
{[]float64{math.NaN()}, self{}, true},
|
||||
//{map[float64]float64{math.NaN(): 1}, map[float64]float64{1: 2}, false},
|
||||
//{map[float64]float64{math.NaN(): 1}, self{}, true},
|
||||
|
||||
// Nil vs empty: not the same.
|
||||
{[]int{}, []int(nil), false},
|
||||
{[]int{}, []int{}, true},
|
||||
{[]int(nil), []int(nil), true},
|
||||
//{map[int]int{}, map[int]int(nil), false},
|
||||
//{map[int]int{}, map[int]int{}, true},
|
||||
//{map[int]int(nil), map[int]int(nil), true},
|
||||
|
||||
// Mismatched types
|
||||
{1, 1.0, false},
|
||||
{int32(1), int64(1), false},
|
||||
{0.5, "hello", false},
|
||||
{[]int{1, 2, 3}, [3]int{1, 2, 3}, false},
|
||||
{&[3]interface{}{1, 2, 4}, &[3]interface{}{1, 2, "s"}, false},
|
||||
{Basic{1, 0.5}, NotBasic{1, 0.5}, false},
|
||||
{map[uint]string{1: "one", 2: "two"}, map[int]string{2: "two", 1: "one"}, false},
|
||||
{[]byte{1, 2, 3}, []MyByte{1, 2, 3}, false},
|
||||
{[]MyByte{1, 2, 3}, MyBytes{1, 2, 3}, false},
|
||||
{[]byte{1, 2, 3}, MyBytes{1, 2, 3}, false},
|
||||
|
||||
// Possible loops.
|
||||
{&loopy1, &loopy1, true},
|
||||
{&loopy1, &loopy2, true},
|
||||
//{&cycleMap1, &cycleMap2, true},
|
||||
{&cycleMap1, &cycleMap3, false},
|
||||
}
|
||||
|
||||
func TestDeepEqual(t *testing.T) {
|
||||
for _, test := range deepEqualTests {
|
||||
if test.b == (self{}) {
|
||||
test.b = test.a
|
||||
}
|
||||
if r := DeepEqual(test.a, test.b); r != test.eq {
|
||||
t.Errorf("DeepEqual(%#v, %#v) = %v, want %v", test.a, test.b, r, test.eq)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type Recursive struct {
|
||||
x int
|
||||
r *Recursive
|
||||
}
|
||||
|
||||
func TestDeepEqualRecursiveStruct(t *testing.T) {
|
||||
a, b := new(Recursive), new(Recursive)
|
||||
*a = Recursive{12, a}
|
||||
*b = Recursive{12, b}
|
||||
if !DeepEqual(a, b) {
|
||||
t.Error("DeepEqual(recursive same) = false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
type _Complex struct {
|
||||
a int
|
||||
b [3]*_Complex
|
||||
c *string
|
||||
d map[float64]float64
|
||||
}
|
||||
|
||||
func TestDeepEqualComplexStruct(t *testing.T) {
|
||||
m := make(map[float64]float64)
|
||||
stra, strb := "hello", "hello"
|
||||
a, b := new(_Complex), new(_Complex)
|
||||
*a = _Complex{5, [3]*_Complex{a, b, a}, &stra, m}
|
||||
*b = _Complex{5, [3]*_Complex{b, a, a}, &strb, m}
|
||||
if !DeepEqual(a, b) {
|
||||
t.Error("DeepEqual(complex same) = false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeepEqualComplexStructInequality(t *testing.T) {
|
||||
m := make(map[float64]float64)
|
||||
stra, strb := "hello", "helloo" // Difference is here
|
||||
a, b := new(_Complex), new(_Complex)
|
||||
*a = _Complex{5, [3]*_Complex{a, b, a}, &stra, m}
|
||||
*b = _Complex{5, [3]*_Complex{b, a, a}, &strb, m}
|
||||
if DeepEqual(a, b) {
|
||||
t.Error("DeepEqual(complex different) = true, want false")
|
||||
}
|
||||
}
|
||||
|
||||
type MyBytes []byte
|
||||
type MyByte byte
|
|
@ -1,9 +1,196 @@
|
|||
// 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 file.
|
||||
|
||||
// Deep equality test via reflection
|
||||
|
||||
package reflect
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// 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 unsafe.Pointer
|
||||
a2 unsafe.Pointer
|
||||
typ rawType
|
||||
}
|
||||
|
||||
// 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(v1, v2 Value, visited map[visit]struct{}) bool {
|
||||
if !v1.IsValid() || !v2.IsValid() {
|
||||
return v1.IsValid() == v2.IsValid()
|
||||
}
|
||||
if v1.typecode != v2.typecode {
|
||||
return false
|
||||
}
|
||||
|
||||
// We want to avoid putting more in the visited map than we need to.
|
||||
// For any possible reference cycle that might be encountered,
|
||||
// hard(v1, v2) needs to return true for at least one of the types in the cycle,
|
||||
// and it's safe and valid to get Value's internal pointer.
|
||||
hard := func(v1, v2 Value) bool {
|
||||
switch v1.Kind() {
|
||||
case Map, Slice, Ptr, Interface:
|
||||
// Nil pointers cannot be cyclic. Avoid putting them in the visited map.
|
||||
return !v1.IsNil() && !v2.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if hard(v1, v2) {
|
||||
addr1 := v1.pointer()
|
||||
addr2 := v2.pointer()
|
||||
if uintptr(addr1) > uintptr(addr2) {
|
||||
// Canonicalize order to reduce number of entries in visited.
|
||||
// Assumes non-moving garbage collector.
|
||||
addr1, addr2 = addr2, addr1
|
||||
}
|
||||
|
||||
// Short circuit if references are already seen.
|
||||
v := visit{addr1, addr2, v1.typecode}
|
||||
if _, ok := visited[v]; ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Remember for later.
|
||||
visited[v] = struct{}{}
|
||||
}
|
||||
|
||||
switch v1.Kind() {
|
||||
case Array:
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case Slice:
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
return false
|
||||
}
|
||||
if v1.Len() != v2.Len() {
|
||||
return false
|
||||
}
|
||||
if v1.Pointer() == v2.Pointer() {
|
||||
return true
|
||||
}
|
||||
for i := 0; i < v1.Len(); i++ {
|
||||
if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case Interface:
|
||||
if v1.IsNil() || v2.IsNil() {
|
||||
return v1.IsNil() == v2.IsNil()
|
||||
}
|
||||
return deepValueEqual(v1.Elem(), v2.Elem(), visited)
|
||||
case Ptr:
|
||||
if v1.Pointer() == v2.Pointer() {
|
||||
return true
|
||||
}
|
||||
return deepValueEqual(v1.Elem(), v2.Elem(), visited)
|
||||
case Struct:
|
||||
for i, n := 0, v1.NumField(); i < n; i++ {
|
||||
if !deepValueEqual(v1.Field(i), v2.Field(i), visited) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case Map:
|
||||
if v1.IsNil() != v2.IsNil() {
|
||||
return false
|
||||
}
|
||||
if v1.Len() != v2.Len() {
|
||||
return false
|
||||
}
|
||||
if v1.Pointer() == v2.Pointer() {
|
||||
return true
|
||||
}
|
||||
for _, k := range v1.MapKeys() {
|
||||
val1 := v1.MapIndex(k)
|
||||
val2 := v2.MapIndex(k)
|
||||
if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case Func:
|
||||
if v1.IsNil() && v2.IsNil() {
|
||||
return true
|
||||
}
|
||||
// Can't do better than this:
|
||||
return false
|
||||
default:
|
||||
// Normal equality suffices
|
||||
return valueInterfaceUnsafe(v1) == valueInterfaceUnsafe(v2)
|
||||
}
|
||||
}
|
||||
|
||||
// DeepEqual reports whether x and y are ``deeply equal,'' defined as follows.
|
||||
// Two values of identical type are deeply equal if one of the following cases applies.
|
||||
// Values of distinct types are never deeply equal.
|
||||
//
|
||||
// Array values are deeply equal when their corresponding elements are deeply equal.
|
||||
//
|
||||
// Struct values are deeply equal if their corresponding fields,
|
||||
// both exported and unexported, are deeply equal.
|
||||
//
|
||||
// Func values are deeply equal if both are nil; otherwise they are not deeply equal.
|
||||
//
|
||||
// Interface values are deeply equal if they hold deeply equal concrete values.
|
||||
//
|
||||
// Map values are deeply equal when all of the following are true:
|
||||
// they are both nil or both non-nil, they have the same length,
|
||||
// and either they are the same map object or their corresponding keys
|
||||
// (matched using Go equality) map to deeply equal values.
|
||||
//
|
||||
// Pointer values are deeply equal if they are equal using Go's == operator
|
||||
// or if they point to deeply equal values.
|
||||
//
|
||||
// Slice values are deeply equal when all of the following are true:
|
||||
// they are both nil or both non-nil, they have the same length,
|
||||
// and either they point to the same initial entry of the same underlying array
|
||||
// (that is, &x[0] == &y[0]) or their corresponding elements (up to length) are deeply equal.
|
||||
// Note that a non-nil empty slice and a nil slice (for example, []byte{} and []byte(nil))
|
||||
// are not deeply equal.
|
||||
//
|
||||
// Other values - numbers, bools, strings, and channels - are deeply equal
|
||||
// if they are equal using Go's == operator.
|
||||
//
|
||||
// In general DeepEqual is a recursive relaxation of Go's == operator.
|
||||
// However, this idea is impossible to implement without some inconsistency.
|
||||
// Specifically, it is possible for a value to be unequal to itself,
|
||||
// either because it is of func type (uncomparable in general)
|
||||
// or because it is a floating-point NaN value (not equal to itself in floating-point comparison),
|
||||
// or because it is an array, struct, or interface containing
|
||||
// such a value.
|
||||
// On the other hand, pointer values are always equal to themselves,
|
||||
// even if they point at or contain such problematic values,
|
||||
// because they compare equal using Go's == operator, and that
|
||||
// is a sufficient condition to be deeply equal, regardless of content.
|
||||
// DeepEqual has been defined so that the same short-cut applies
|
||||
// to slices and maps: if x and y are the same slice or the same map,
|
||||
// they are deeply equal regardless of content.
|
||||
//
|
||||
// As DeepEqual traverses the data values it may find a cycle. The
|
||||
// second and subsequent times that DeepEqual compares two pointer
|
||||
// values that have been compared before, it treats the values as
|
||||
// equal rather than examining the values to which they point.
|
||||
// This ensures that DeepEqual terminates.
|
||||
func DeepEqual(x, y interface{}) bool {
|
||||
if x == nil || y == nil {
|
||||
return x == y
|
||||
}
|
||||
|
||||
panic("unimplemented: reflect.DeepEqual()")
|
||||
v1 := ValueOf(x)
|
||||
v2 := ValueOf(y)
|
||||
if v1.typecode != v2.typecode {
|
||||
return false
|
||||
}
|
||||
return deepValueEqual(v1, v2, make(map[visit]struct{}))
|
||||
}
|
||||
|
|
|
@ -140,10 +140,7 @@ func (v Value) IsNil() bool {
|
|||
func (v Value) Pointer() uintptr {
|
||||
switch v.Kind() {
|
||||
case Chan, Map, Ptr, UnsafePointer:
|
||||
if v.isIndirect() {
|
||||
return *(*uintptr)(v.value)
|
||||
}
|
||||
return uintptr(v.value)
|
||||
return uintptr(v.pointer())
|
||||
case Slice:
|
||||
slice := (*sliceHeader)(v.value)
|
||||
return uintptr(slice.data)
|
||||
|
@ -154,6 +151,15 @@ func (v Value) Pointer() uintptr {
|
|||
}
|
||||
}
|
||||
|
||||
// pointer returns the underlying pointer represented by v.
|
||||
// v.Kind() must be Ptr, Map, Chan, or UnsafePointer
|
||||
func (v Value) pointer() unsafe.Pointer {
|
||||
if v.isIndirect() {
|
||||
return *(*unsafe.Pointer)(v.value)
|
||||
}
|
||||
return v.value
|
||||
}
|
||||
|
||||
func (v Value) IsValid() bool {
|
||||
return v.typecode != 0
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"inherits": ["fe310"],
|
||||
"build-tags": ["hifive1b", "qemu"],
|
||||
"serial": "uart",
|
||||
"default-stack-size": 4096,
|
||||
"linkerscript": "targets/hifive1-qemu.ld",
|
||||
"emulator": ["qemu-system-riscv32", "-machine", "sifive_e", "-nographic", "-kernel"]
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"inherits": ["riscv32"],
|
||||
"features": "+a,+c,+m,-relax,-save-restore",
|
||||
"build-tags": ["virt", "qemu"],
|
||||
"default-stack-size": 4096,
|
||||
"linkerscript": "targets/riscv-qemu.ld",
|
||||
"emulator": ["qemu-system-riscv32", "-machine", "virt", "-nographic", "-bios", "none", "-kernel"]
|
||||
}
|
||||
|
|
40
testdata/reflect.go
предоставленный
40
testdata/reflect.go
предоставленный
|
@ -27,6 +27,9 @@ type (
|
|||
next *linkedList `description:"chain"`
|
||||
foo int
|
||||
}
|
||||
selfref struct {
|
||||
x *selfref
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -326,6 +329,43 @@ func main() {
|
|||
|
||||
println("\nv.Interface() method")
|
||||
testInterfaceMethod()
|
||||
|
||||
// Test reflect.DeepEqual.
|
||||
var selfref1, selfref2 selfref
|
||||
selfref1.x = &selfref1
|
||||
selfref2.x = &selfref2
|
||||
for i, tc := range []struct {
|
||||
v1, v2 interface{}
|
||||
equal bool
|
||||
}{
|
||||
{int(5), int(5), true},
|
||||
{int(3), int(5), false},
|
||||
{int(5), uint(5), false},
|
||||
{struct {
|
||||
a int
|
||||
b string
|
||||
}{3, "x"}, struct {
|
||||
a int
|
||||
b string
|
||||
}{3, "x"}, true},
|
||||
{struct {
|
||||
a int
|
||||
b string
|
||||
}{3, "x"}, struct {
|
||||
a int
|
||||
b string
|
||||
}{3, "y"}, false},
|
||||
{selfref1, selfref2, true},
|
||||
} {
|
||||
result := reflect.DeepEqual(tc.v1, tc.v2)
|
||||
if result != tc.equal {
|
||||
if tc.equal {
|
||||
println("reflect.DeepEqual() test", i, "not equal while it should be")
|
||||
} else {
|
||||
println("reflect.DeepEqual() test", i, "equal while it should not be")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func emptyFunc() {
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче