diff --git a/cmp/compare.go b/cmp/compare.go index 2762733..e95a0f7 100644 --- a/cmp/compare.go +++ b/cmp/compare.go @@ -123,8 +123,7 @@ func Equal(x, y interface{}, opts ...Option) bool { // Do not depend on this output being stable. 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") @@ -155,9 +154,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 } @@ -209,8 +206,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() @@ -234,67 +229,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{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{pathStep{vx.Type(), vx, vy}}) - return + s.compareInterface(t, vx, vy) default: panic(fmt.Sprintf("%v kind not handled", t.Kind())) } @@ -422,6 +384,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{pathStep: pathStep{typ: t.Elem()}} withIndexes := func(ix, iy int) *sliceIndex { if ix >= 0 { @@ -491,7 +461,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) { @@ -500,6 +469,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{pathStep: pathStep{typ: t.Elem()}} @@ -529,6 +500,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{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{pathStep{vx.Type(), vx, vy}}) +} + func (s *state) report(eq bool, rf reportFlags) { if rf&reportIgnored == 0 { if eq {