From 6f77996f0c42f7b84e5a2b252227263f93432e9b Mon Sep 17 00:00:00 2001 From: Joe Tsai Date: Mon, 11 Mar 2019 20:24:27 -0700 Subject: [PATCH] Improve clarity of compareAny (#132) --- cmp/compare.go | 86 ++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/cmp/compare.go b/cmp/compare.go index c81b046..2133562 100644 --- a/cmp/compare.go +++ b/cmp/compare.go @@ -124,8 +124,7 @@ func Equal(x, y interface{}, opts ...Option) bool { // programmatically interpret the difference, consider using a custom Reporter. func Diff(x, y interface{}, opts ...Option) string { r := new(defaultReporter) - opts = Options{Options(opts), Reporter(r)} - eq := Equal(x, y, opts...) + eq := Equal(x, y, Options(opts), Reporter(r)) d := r.String() if (d == "") != eq { panic("inconsistent difference and equality results") @@ -156,9 +155,7 @@ type state struct { func newState(opts []Option) *state { // Always ensure a validator option exists to validate the inputs. s := &state{opts: Options{validator{}}} - for _, opt := range opts { - s.processOption(opt) - } + s.processOption(Options(opts)) return s } @@ -210,8 +207,6 @@ func (s *state) statelessCompare(step PathStep) diff.Result { } func (s *state) compareAny(step PathStep) { - // TODO: Support cyclic data structures. - // Update the path stack. s.curPath.push(step) defer s.curPath.pop() @@ -235,67 +230,34 @@ func (s *state) compareAny(step PathStep) { return } - // Rule 3: Recursively descend into each value's underlying kind. + // Rule 3: Compare based on the underlying kind. switch t.Kind() { case reflect.Bool: s.report(vx.Bool() == vy.Bool(), 0) - return case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: s.report(vx.Int() == vy.Int(), 0) - return case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: s.report(vx.Uint() == vy.Uint(), 0) - return case reflect.Float32, reflect.Float64: s.report(vx.Float() == vy.Float(), 0) - return case reflect.Complex64, reflect.Complex128: s.report(vx.Complex() == vy.Complex(), 0) - return case reflect.String: s.report(vx.String() == vy.String(), 0) - return case reflect.Chan, reflect.UnsafePointer: s.report(vx.Pointer() == vy.Pointer(), 0) - return case reflect.Func: s.report(vx.IsNil() && vy.IsNil(), 0) - return case reflect.Struct: s.compareStruct(t, vx, vy) - return - case reflect.Slice: - if vx.IsNil() || vy.IsNil() { - s.report(vx.IsNil() && vy.IsNil(), 0) - return - } - fallthrough - case reflect.Array: + case reflect.Slice, reflect.Array: s.compareSlice(t, vx, vy) - return case reflect.Map: s.compareMap(t, vx, vy) - return case reflect.Ptr: - if vx.IsNil() || vy.IsNil() { - s.report(vx.IsNil() && vy.IsNil(), 0) - return - } - vx, vy = vx.Elem(), vy.Elem() - s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}}) - return + s.comparePtr(t, vx, vy) case reflect.Interface: - if vx.IsNil() || vy.IsNil() { - s.report(vx.IsNil() && vy.IsNil(), 0) - return - } - vx, vy = vx.Elem(), vy.Elem() - if vx.Type() != vy.Type() { - s.report(false, 0) - return - } - s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}}) - return + s.compareInterface(t, vx, vy) default: panic(fmt.Sprintf("%v kind not handled", t.Kind())) } @@ -423,6 +385,14 @@ func (s *state) compareStruct(t reflect.Type, vx, vy reflect.Value) { } func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) { + isSlice := t.Kind() == reflect.Slice + if isSlice && (vx.IsNil() || vy.IsNil()) { + s.report(vx.IsNil() && vy.IsNil(), 0) + return + } + + // TODO: Support cyclic data structures. + step := SliceIndex{&sliceIndex{pathStep: pathStep{typ: t.Elem()}}} withIndexes := func(ix, iy int) SliceIndex { if ix >= 0 { @@ -492,7 +462,6 @@ func (s *state) compareSlice(t reflect.Type, vx, vy reflect.Value) { iy++ } } - return } func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) { @@ -501,6 +470,8 @@ func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) { return } + // TODO: Support cyclic data structures. + // We combine and sort the two map keys so that we can perform the // comparisons in a deterministic order. step := MapIndex{&mapIndex{pathStep: pathStep{typ: t.Elem()}}} @@ -530,6 +501,31 @@ func (s *state) compareMap(t reflect.Type, vx, vy reflect.Value) { } } +func (s *state) comparePtr(t reflect.Type, vx, vy reflect.Value) { + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), 0) + return + } + + // TODO: Support cyclic data structures. + + vx, vy = vx.Elem(), vy.Elem() + s.compareAny(Indirect{&indirect{pathStep{t.Elem(), vx, vy}}}) +} + +func (s *state) compareInterface(t reflect.Type, vx, vy reflect.Value) { + if vx.IsNil() || vy.IsNil() { + s.report(vx.IsNil() && vy.IsNil(), 0) + return + } + vx, vy = vx.Elem(), vy.Elem() + if vx.Type() != vy.Type() { + s.report(false, 0) + return + } + s.compareAny(TypeAssertion{&typeAssertion{pathStep{vx.Type(), vx, vy}}}) +} + func (s *state) report(eq bool, rf resultFlags) { if rf&reportByIgnore == 0 { if eq {