From a343f4017b501358c202154f074f54d710df6698 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Thu, 3 Nov 2022 18:15:42 -0700 Subject: [PATCH] reflect: rewrite value.Equal to avoid allocations For #46746 Change-Id: I75ddb9ce24cd3394186562dae156fef9fe2d55d3 Reviewed-on: https://go-review.googlesource.com/c/go/+/447798 Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor Auto-Submit: Ian Lance Taylor Reviewed-by: Bryan Mills TryBot-Result: Gopher Robot --- src/reflect/all_test.go | 2 +- src/reflect/value.go | 71 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/src/reflect/all_test.go b/src/reflect/all_test.go index e5d0b2c07c..28a7640323 100644 --- a/src/reflect/all_test.go +++ b/src/reflect/all_test.go @@ -8335,7 +8335,7 @@ func TestValue_EqualNonComparable(t *testing.T) { } for _, value := range values { // Panic when reflect.Value.Equal using two valid non-comparable values. - shouldPanic("reflect.Value.Equal using two non-comparable values", func() { value.Equal(value) }) + shouldPanic("are not comparable", func() { value.Equal(value) }) // If one is non-comparable and the other is invalid, the expected result is always false. if r := value.Equal(invalid); r != false { diff --git a/src/reflect/value.go b/src/reflect/value.go index 7fa45b62e9..4acd3238b4 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -3320,22 +3320,77 @@ func (v Value) Comparable() bool { } // Equal reports true if v is equal to u. -// For valid values, if one of them is non-comparable, and the other is comparable, -// Equal reports false; if v and u are both non-comparable, Equal will panic. +// For two invalid values, Equal will report true. +// For an interface value, Equal will compare the value within the interface. +// Otherwise, If the values have different types, Equal will report false. +// Otherwise, for arrays and structs Equal will compare each element in order, +// and report false if it finds non-equal elements. +// During all comparisons, if values of the same type are compared, +// and the type is not comparable, Equal will panic. func (v Value) Equal(u Value) bool { + if v.Kind() == Interface { + v = v.Elem() + } + if u.Kind() == Interface { + u = u.Elem() + } + if !v.IsValid() || !u.IsValid() { return v.IsValid() == u.IsValid() } - if v.Comparable() || u.Comparable() { - return valueInterface(v, false) == valueInterface(u, false) + if v.Kind() != u.Kind() || v.Type() != u.Type() { + return false } - if u.Kind() == Interface && v.kind() == Interface { // this case is for nil interface value - return v.Elem().Equal(u.Elem()) + // Handle ach Kind directly rather than calling valueInterface + // to avoid allocating. + switch v.Kind() { + default: + panic("reflect.Value.Equal: invalid Kind") + case Bool: + return v.Bool() == u.Bool() + case Int, Int8, Int16, Int32, Int64: + return v.Int() == u.Int() + case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: + return v.Uint() == u.Uint() + case Float32, Float64: + return v.Float() == u.Float() + case Complex64, Complex128: + return v.Complex() == u.Complex() + case String: + return v.String() == u.String() + case Chan, Pointer, UnsafePointer: + return v.Pointer() == u.Pointer() + case Array: + // u and v have the same type so they have the same length + vl := v.Len() + if vl == 0 { + // panic on [0]func() + if !v.Type().Elem().Comparable() { + break + } + return true + } + for i := 0; i < vl; i++ { + if !v.Index(i).Equal(u.Index(i)) { + return false + } + } + return true + case Struct: + // u and v have the same type so they have the same fields + nf := v.NumField() + for i := 0; i < nf; i++ { + if !v.Field(i).Equal(u.Field(i)) { + return false + } + } + return true + case Func, Map, Slice: + break } - - panic("reflect.Value.Equal using two non-comparable values") + panic("reflect.Value.Equal: values of type " + v.Type().String() + " are not comparable") } // convertOp returns the function to convert a value of type src -- 2.50.0