Deterministic sorting for all map key types.
R=dnj@chromium.org
BUG=
Review URL: https://chromiumcodereview.appspot.com/1716443004 .
diff --git a/render/render.go b/render/render.go
index bdb307c..23b7a58 100644
--- a/render/render.go
+++ b/render/render.go
@@ -330,40 +330,148 @@
}
}
+type cmpFn func(a, b reflect.Value) int
+
type sortableValueSlice struct {
- kind reflect.Kind
+ cmp cmpFn
elements []reflect.Value
}
-func (s *sortableValueSlice) Len() int {
+func (s sortableValueSlice) Len() int {
return len(s.elements)
}
-func (s *sortableValueSlice) Less(i, j int) bool {
- switch s.kind {
- case reflect.String:
- return s.elements[i].String() < s.elements[j].String()
-
- case reflect.Int:
- return s.elements[i].Int() < s.elements[j].Int()
-
- default:
- panic(fmt.Errorf("unsupported sort kind: %s", s.kind))
- }
+func (s sortableValueSlice) Less(i, j int) bool {
+ return s.cmp(s.elements[i], s.elements[j]) < 0
}
-func (s *sortableValueSlice) Swap(i, j int) {
+func (s sortableValueSlice) Swap(i, j int) {
s.elements[i], s.elements[j] = s.elements[j], s.elements[i]
}
-func tryAndSortMapKeys(mt reflect.Type, k []reflect.Value) {
- // Try our stock sortable values.
- switch mt.Key().Kind() {
- case reflect.String, reflect.Int:
- vs := &sortableValueSlice{
- kind: mt.Key().Kind(),
- elements: k,
+// cmpForType returns a cmpFn which sorts the data for some type t in the same
+// order that a go-native map key is compared for equality.
+func cmpForType(t reflect.Type) cmpFn {
+ switch t.Kind() {
+ case reflect.String:
+ return func(av, bv reflect.Value) int {
+ a, b := av.String(), bv.String()
+ if a < b {
+ return -1
+ } else if a > b {
+ return 1
+ }
+ return 0
}
- sort.Sort(vs)
+
+ case reflect.Bool:
+ return func(av, bv reflect.Value) int {
+ a, b := av.Bool(), bv.Bool()
+ if !a && b {
+ return -1
+ } else if a && !b {
+ return 1
+ }
+ return 0
+ }
+
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return func(av, bv reflect.Value) int {
+ a, b := av.Int(), bv.Int()
+ if a < b {
+ return -1
+ } else if a > b {
+ return 1
+ }
+ return 0
+ }
+
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
+ reflect.Uint64, reflect.Uintptr, reflect.UnsafePointer:
+ return func(av, bv reflect.Value) int {
+ a, b := av.Uint(), bv.Uint()
+ if a < b {
+ return -1
+ } else if a > b {
+ return 1
+ }
+ return 0
+ }
+
+ case reflect.Float32, reflect.Float64:
+ return func(av, bv reflect.Value) int {
+ a, b := av.Float(), bv.Float()
+ if a < b {
+ return -1
+ } else if a > b {
+ return 1
+ }
+ return 0
+ }
+
+ case reflect.Interface:
+ return func(av, bv reflect.Value) int {
+ a, b := av.InterfaceData(), bv.InterfaceData()
+ if a[0] < b[0] {
+ return -1
+ } else if a[0] > b[0] {
+ return 1
+ }
+ if a[1] < b[1] {
+ return -1
+ } else if a[1] > b[1] {
+ return 1
+ }
+ return 0
+ }
+
+ case reflect.Complex64, reflect.Complex128:
+ return func(av, bv reflect.Value) int {
+ a, b := av.Complex(), bv.Complex()
+ if real(a) < real(b) {
+ return -1
+ } else if real(a) > real(b) {
+ return 1
+ }
+ if imag(a) < imag(b) {
+ return -1
+ } else if imag(a) > imag(b) {
+ return 1
+ }
+ return 0
+ }
+
+ case reflect.Ptr, reflect.Chan:
+ return func(av, bv reflect.Value) int {
+ a, b := av.Pointer(), bv.Pointer()
+ if a < b {
+ return -1
+ } else if a > b {
+ return 1
+ }
+ return 0
+ }
+
+ case reflect.Struct:
+ cmpLst := make([]cmpFn, t.NumField())
+ for i := range cmpLst {
+ cmpLst[i] = cmpForType(t.Field(i).Type)
+ }
+ return func(a, b reflect.Value) int {
+ for i, cmp := range cmpLst {
+ if rslt := cmp(a.Field(i), b.Field(i)); rslt != 0 {
+ return rslt
+ }
+ }
+ return 0
+ }
+ }
+
+ return nil
+}
+
+func tryAndSortMapKeys(mt reflect.Type, k []reflect.Value) {
+ if cmp := cmpForType(mt.Key()); cmp != nil {
+ sort.Sort(sortableValueSlice{cmp, k})
}
}
diff --git a/render/render_test.go b/render/render_test.go
index bb2af4b..6e7c92d 100644
--- a/render/render_test.go
+++ b/render/render_test.go
@@ -203,3 +203,71 @@
func sanitizePointer(s string) string {
return pointerRE.ReplaceAllString(s, "(0x600dd065)")
}
+
+type chanList []chan int
+
+func (c chanList) Len() int { return len(c) }
+func (c chanList) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
+func (c chanList) Less(i, j int) bool {
+ return reflect.ValueOf(c[i]).Pointer() < reflect.ValueOf(c[j]).Pointer()
+}
+
+func TestMapSortRendering(t *testing.T) {
+ type namedMapType map[int]struct{ a int }
+ type mapKey struct{ a, b int }
+
+ chans := make(chanList, 5)
+ for i := range chans {
+ chans[i] = make(chan int)
+ }
+
+ tcs := []struct {
+ in interface{}
+ expect string
+ }{
+ {
+ map[uint32]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}},
+ "map[uint32]struct {}{1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}, 8:{}}",
+ },
+ {
+ map[int8]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}},
+ "map[int8]struct {}{1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}, 8:{}}",
+ },
+ {
+ map[uintptr]struct{}{1: {}, 2: {}, 3: {}, 4: {}, 5: {}, 6: {}, 7: {}, 8: {}},
+ "map[uintptr]struct {}{1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}, 8:{}}",
+ },
+ {
+ namedMapType{10: struct{ a int }{20}},
+ "render.namedMapType{10:struct { a int }{20}}",
+ },
+ {
+ map[mapKey]struct{}{mapKey{3, 1}: {}, mapKey{1, 3}: {}, mapKey{1, 2}: {}, mapKey{2, 1}: {}},
+ "map[render.mapKey]struct {}{render.mapKey{a:1, b:2}:{}, render.mapKey{a:1, b:3}:{}, render.mapKey{a:2, b:1}:{}, render.mapKey{a:3, b:1}:{}}",
+ },
+ {
+ map[float64]struct{}{10.5: {}, 10.15: {}, 1203: {}, 1: {}, 2: {}},
+ "map[float64]struct {}{1:{}, 2:{}, 10.15:{}, 10.5:{}, 1203:{}}",
+ },
+ {
+ map[bool]struct{}{true: {}, false: {}},
+ "map[bool]struct {}{false:{}, true:{}}",
+ },
+ {
+ map[interface{}]struct{}{1: {}, 2: {}, 3: {}, "foo": {}},
+ `map[interface{}]struct {}{1:{}, 2:{}, 3:{}, "foo":{}}`,
+ },
+ {
+ map[complex64]struct{}{1 + 2i: {}, 2 + 1i: {}, 3 + 1i: {}, 1 + 3i: {}},
+ "map[complex64]struct {}{(1+2i):{}, (1+3i):{}, (2+1i):{}, (3+1i):{}}",
+ },
+ {
+ map[chan int]string{nil: "a", chans[0]: "b", chans[1]: "c", chans[2]: "d", chans[3]: "e", chans[4]: "f"},
+ `map[(chan int)]string{(chan int)(PTR):"a", (chan int)(PTR):"b", (chan int)(PTR):"c", (chan int)(PTR):"d", (chan int)(PTR):"e", (chan int)(PTR):"f"}`,
+ },
+ }
+
+ for _, tc := range tcs {
+ assertRendersLike(t, reflect.TypeOf(tc.in).Name(), tc.in, tc.expect)
+ }
+}