diff --git a/internal/go-render/render/render.go b/internal/go-render/render/render.go index e070a6b..23b7a58 100644 --- a/internal/go-render/render/render.go +++ b/internal/go-render/render/render.go @@ -12,32 +12,46 @@ import ( "strconv" ) -var implicitTypeMap = map[reflect.Kind]string{ +var builtinTypeMap = map[reflect.Kind]string{ reflect.Bool: "bool", - reflect.String: "string", - reflect.Int: "int", - reflect.Int8: "int8", + reflect.Complex128: "complex128", + reflect.Complex64: "complex64", + reflect.Float32: "float32", + reflect.Float64: "float64", reflect.Int16: "int16", reflect.Int32: "int32", reflect.Int64: "int64", - reflect.Uint: "uint", - reflect.Uint8: "uint8", + reflect.Int8: "int8", + reflect.Int: "int", + reflect.String: "string", reflect.Uint16: "uint16", reflect.Uint32: "uint32", reflect.Uint64: "uint64", - reflect.Float32: "float32", - reflect.Float64: "float64", - reflect.Complex64: "complex64", - reflect.Complex128: "complex128", + reflect.Uint8: "uint8", + reflect.Uint: "uint", + reflect.Uintptr: "uintptr", } +var builtinTypeSet = map[string]struct{}{} + +func init() { + for _, v := range builtinTypeMap { + builtinTypeSet[v] = struct{}{} + } +} + +var typeOfString = reflect.TypeOf("") +var typeOfInt = reflect.TypeOf(int(1)) +var typeOfUint = reflect.TypeOf(uint(1)) +var typeOfFloat = reflect.TypeOf(10.1) + // Render converts a structure to a string representation. Unline the "%#v" // format string, this resolves pointer types' contents in structs, maps, and // slices/arrays and prints their field values. func Render(v interface{}) string { buf := bytes.Buffer{} s := (*traverseState)(nil) - s.render(&buf, 0, reflect.ValueOf(v)) + s.render(&buf, 0, reflect.ValueOf(v), false) return buf.String() } @@ -72,7 +86,7 @@ func (s *traverseState) forkFor(ptr uintptr) *traverseState { return fs } -func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) { +func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value, implicit bool) { if v.Kind() == reflect.Invalid { buf.WriteString("nil") return @@ -107,49 +121,76 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) { s = s.forkFor(pe) if s == nil { buf.WriteString("") return } } + isAnon := func(t reflect.Type) bool { + if t.Name() != "" { + if _, ok := builtinTypeSet[t.Name()]; !ok { + return false + } + } + return t.Kind() != reflect.Interface + } + switch vk { case reflect.Struct: - writeType(buf, ptrs, vt) + if !implicit { + writeType(buf, ptrs, vt) + } + structAnon := vt.Name() == "" buf.WriteRune('{') for i := 0; i < vt.NumField(); i++ { if i > 0 { buf.WriteString(", ") } - buf.WriteString(vt.Field(i).Name) - buf.WriteRune(':') + anon := structAnon && isAnon(vt.Field(i).Type) + + if !anon { + buf.WriteString(vt.Field(i).Name) + buf.WriteRune(':') + } - s.render(buf, 0, v.Field(i)) + s.render(buf, 0, v.Field(i), anon) } buf.WriteRune('}') case reflect.Slice: if v.IsNil() { - writeType(buf, ptrs, vt) - buf.WriteString("(nil)") + if !implicit { + writeType(buf, ptrs, vt) + buf.WriteString("(nil)") + } else { + buf.WriteString("nil") + } return } fallthrough case reflect.Array: - writeType(buf, ptrs, vt) + if !implicit { + writeType(buf, ptrs, vt) + } + anon := vt.Name() == "" && isAnon(vt.Elem()) buf.WriteString("{") for i := 0; i < v.Len(); i++ { if i > 0 { buf.WriteString(", ") } - s.render(buf, 0, v.Index(i)) + s.render(buf, 0, v.Index(i), anon) } buf.WriteRune('}') case reflect.Map: - writeType(buf, ptrs, vt) + if !implicit { + writeType(buf, ptrs, vt) + } if v.IsNil() { buf.WriteString("(nil)") } else { @@ -158,14 +199,17 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) { mkeys := v.MapKeys() tryAndSortMapKeys(vt, mkeys) + kt := vt.Key() + keyAnon := typeOfString.ConvertibleTo(kt) || typeOfInt.ConvertibleTo(kt) || typeOfUint.ConvertibleTo(kt) || typeOfFloat.ConvertibleTo(kt) + valAnon := vt.Name() == "" && isAnon(vt.Elem()) for i, mk := range mkeys { if i > 0 { buf.WriteString(", ") } - s.render(buf, 0, mk) + s.render(buf, 0, mk, keyAnon) buf.WriteString(":") - s.render(buf, 0, v.MapIndex(mk)) + s.render(buf, 0, v.MapIndex(mk), valAnon) } buf.WriteRune('}') } @@ -176,11 +220,9 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) { case reflect.Interface: if v.IsNil() { writeType(buf, ptrs, v.Type()) - buf.WriteRune('(') - fmt.Fprint(buf, "nil") - buf.WriteRune(')') + buf.WriteString("(nil)") } else { - s.render(buf, ptrs, v.Elem()) + s.render(buf, ptrs, v.Elem(), false) } case reflect.Chan, reflect.Func, reflect.UnsafePointer: @@ -191,7 +233,7 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) { default: tstr := vt.String() - implicit := ptrs == 0 && implicitTypeMap[vk] == tstr + implicit = implicit || (ptrs == 0 && builtinTypeMap[vk] == tstr) if !implicit { writeType(buf, ptrs, vt) buf.WriteRune('(') @@ -206,7 +248,7 @@ func (s *traverseState) render(buf *bytes.Buffer, ptrs int, v reflect.Value) { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: fmt.Fprintf(buf, "%d", v.Int()) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: fmt.Fprintf(buf, "%d", v.Uint()) case reflect.Float32, reflect.Float64: @@ -288,40 +330,148 @@ func writeType(buf *bytes.Buffer, ptrs int, t reflect.Type) { } } +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 { +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) { + s.elements[i], s.elements[j] = s.elements[j], s.elements[i] +} + +// 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 s.elements[i].String() < s.elements[j].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 + } - case reflect.Int: - return s.elements[i].Int() < s.elements[j].Int() + 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 + } - default: - panic(fmt.Errorf("unsupported sort kind: %s", s.kind)) + 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 + } } -} -func (s *sortableValueSlice) Swap(i, j int) { - s.elements[i], s.elements[j] = s.elements[j], s.elements[i] + return nil } 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, - } - sort.Sort(vs) + if cmp := cmpForType(mt.Key()); cmp != nil { + sort.Sort(sortableValueSlice{cmp, k}) } } diff --git a/internal/go-render/render/render_test.go b/internal/go-render/render/render_test.go index 1737cb7..6e7c92d 100644 --- a/internal/go-render/render/render_test.go +++ b/internal/go-render/render/render_test.go @@ -1,4 +1,4 @@ -// Copyright 2015 Google Inc. All rights reserved. +// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -7,6 +7,7 @@ package render import ( "bytes" "fmt" + "reflect" "regexp" "runtime" "testing" @@ -81,7 +82,7 @@ func TestRenderList(t *testing.T) { {struct { a int b string - }{123, "foo"}, `struct { a int; b string }{a:123, b:"foo"}`}, + }{123, "foo"}, `struct { a int; b string }{123, "foo"}`}, {[]string{"foo", "foo", "bar", "baz", "qux", "qux"}, `[]string{"foo", "foo", "bar", "baz", "qux", "qux"}`}, {[...]int{1, 2, 3}, `[3]int{1, 2, 3}`}, @@ -133,14 +134,48 @@ func TestRenderRecursiveMap(t *testing.T) { v := []map[string]interface{}{m, m} assertRendersLike(t, "Recursive map", v, - `[]map[string]interface{}{map[string]interface{}{`+ + `[]map[string]interface{}{{`+ `"bar":[]*string{(*string)("foo"), (*string)("foo")}, `+ - `"foo":}, `+ - `map[string]interface{}{`+ + `"foo":}, {`+ `"bar":[]*string{(*string)("foo"), (*string)("foo")}, `+ `"foo":}}`) } +func TestRenderImplicitType(t *testing.T) { + type namedStruct struct{ a, b int } + type namedInt int + + tcs := []struct { + in interface{} + expect string + }{ + { + []struct{ a, b int }{{1, 2}}, + "[]struct { a int; b int }{{1, 2}}", + }, + { + map[string]struct{ a, b int }{"hi": {1, 2}}, + `map[string]struct { a int; b int }{"hi":{1, 2}}`, + }, + { + map[namedInt]struct{}{10: {}}, + `map[render.namedInt]struct {}{10:{}}`, + }, + { + struct{ a, b int }{1, 2}, + `struct { a int; b int }{1, 2}`, + }, + { + namedStruct{1, 2}, + "render.namedStruct{a:1, b:2}", + }, + } + + for _, tc := range tcs { + assertRendersLike(t, reflect.TypeOf(tc.in).String(), tc.in, tc.expect) + } +} + func ExampleInReadme() { type customType int type testStruct struct { @@ -168,3 +203,71 @@ var pointerRE = regexp.MustCompile(`\(0x[a-f0-9]+\)`) 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) + } +}