Commit 9149aa10 authored by Martin Möhrmann's avatar Martin Möhrmann Committed by Rob Pike

fmt: unify array and slice formatting for bytes and other types

Make verbs b,c,o and U work for any array and slice of integer
type including byte and uint8.

Fix a bug that triggers badverb for []uint8 and []byte type
on the slice/array level instead of on each element like for
any other slice or array type.

Add tests that make sure we do not accidentally alter the
behavior of printing []byte for []byte and []uint8 type
if they are used at the top level when formatting with %#v.

name               old time/op  new time/op  delta
SprintfHexBytes-2   177ns ± 2%   176ns ± 2%   ~     (p=0.066 n=48+49)
SprintfBytes-2      330ns ± 1%   329ns ± 1%   ~     (p=0.118 n=45+47)

Fixes #13478

Change-Id: I99328a184973ae219bcc0f69c3978cb1ff462888
Reviewed-on: https://go-review.googlesource.com/20686
Run-TryBot: Rob Pike <r@golang.org>
Reviewed-by: 's avatarRob Pike <r@golang.org>
parent a637717e
...@@ -161,6 +161,8 @@ var fmtTests = []struct { ...@@ -161,6 +161,8 @@ var fmtTests = []struct {
// basic bytes // basic bytes
{"%s", []byte("abc"), "abc"}, {"%s", []byte("abc"), "abc"},
{"%s", [3]byte{'a', 'b', 'c'}, "abc"},
{"%s", &[3]byte{'a', 'b', 'c'}, "&abc"},
{"%q", []byte("abc"), `"abc"`}, {"%q", []byte("abc"), `"abc"`},
{"%x", []byte("abc"), "616263"}, {"%x", []byte("abc"), "616263"},
{"%x", []byte("\xff\xf0\x0f\xff"), "fff00fff"}, {"%x", []byte("\xff\xf0\x0f\xff"), "fff00fff"},
...@@ -534,22 +536,16 @@ var fmtTests = []struct { ...@@ -534,22 +536,16 @@ var fmtTests = []struct {
{"%v", &islice, "&[1 hello 2.5 <nil>]"}, {"%v", &islice, "&[1 hello 2.5 <nil>]"},
{"%v", &bslice, "&[1 2 3 4 5]"}, {"%v", &bslice, "&[1 2 3 4 5]"},
// byte slices and arrays with %d and %v variants // byte arrays and slices with %b,%c,%d,%o,%U and %v
{"%d", [0]byte{}, "[]"}, {"%b", [3]byte{65, 66, 67}, "[1000001 1000010 1000011]"},
{"%d", [1]byte{123}, "[123]"}, {"%c", [3]byte{65, 66, 67}, "[A B C]"},
{"%012d", []byte{}, "[]"}, {"%d", [3]byte{65, 66, 67}, "[65 66 67]"},
{"%d", [3]byte{1, 11, 111}, "[1 11 111]"}, {"%o", [3]byte{65, 66, 67}, "[101 102 103]"},
{"%d", [3]uint8{1, 11, 111}, "[1 11 111]"}, {"%U", [3]byte{65, 66, 67}, "[U+0041 U+0042 U+0043]"},
{"%06d", [3]byte{1, 11, 111}, "[000001 000011 000111]"}, {"%v", [3]byte{65, 66, 67}, "[65 66 67]"},
{"%-6d", [3]byte{1, 11, 111}, "[1 11 111 ]"}, {"%v", [1]byte{123}, "[123]"},
{"%-06d", [3]byte{1, 11, 111}, "[1 11 111 ]"}, // 0 has no effect when - is present.
{"%v", []byte{}, "[]"},
{"%012v", []byte{}, "[]"}, {"%012v", []byte{}, "[]"},
{"%#v", []byte{}, "[]byte{}"},
{"%#v", []uint8{}, "[]byte{}"},
{"%#012v", []byte{}, "[]byte{}"}, {"%#012v", []byte{}, "[]byte{}"},
{"%v", []byte{123}, "[123]"},
{"%v", []byte{1, 11, 111}, "[1 11 111]"},
{"%6v", []byte{1, 11, 111}, "[ 1 11 111]"}, {"%6v", []byte{1, 11, 111}, "[ 1 11 111]"},
{"%06v", []byte{1, 11, 111}, "[000001 000011 000111]"}, {"%06v", []byte{1, 11, 111}, "[000001 000011 000111]"},
{"%-6v", []byte{1, 11, 111}, "[1 11 111 ]"}, {"%-6v", []byte{1, 11, 111}, "[1 11 111 ]"},
...@@ -559,21 +555,6 @@ var fmtTests = []struct { ...@@ -559,21 +555,6 @@ var fmtTests = []struct {
{"%#06v", []byte{1, 11, 111}, "[]byte{0x000001, 0x00000b, 0x00006f}"}, {"%#06v", []byte{1, 11, 111}, "[]byte{0x000001, 0x00000b, 0x00006f}"},
{"%#-6v", []byte{1, 11, 111}, "[]byte{0x1 , 0xb , 0x6f }"}, {"%#-6v", []byte{1, 11, 111}, "[]byte{0x1 , 0xb , 0x6f }"},
{"%#-06v", []byte{1, 11, 111}, "[]byte{0x1 , 0xb , 0x6f }"}, {"%#-06v", []byte{1, 11, 111}, "[]byte{0x1 , 0xb , 0x6f }"},
{"%v", [0]byte{}, "[]"},
{"%-12v", [0]byte{}, "[]"},
{"%#v", [0]byte{}, "[0]uint8{}"},
{"%#v", [0]uint8{}, "[0]uint8{}"},
{"%#-12v", [0]byte{}, "[0]uint8{}"},
{"%v", [1]byte{123}, "[123]"},
{"%v", [3]byte{1, 11, 111}, "[1 11 111]"},
{"%06v", [3]byte{1, 11, 111}, "[000001 000011 000111]"},
{"%-6v", [3]byte{1, 11, 111}, "[1 11 111 ]"},
{"%-06v", [3]byte{1, 11, 111}, "[1 11 111 ]"},
{"%#v", [3]byte{1, 11, 111}, "[3]uint8{0x1, 0xb, 0x6f}"},
{"%#6v", [3]byte{1, 11, 111}, "[3]uint8{ 0x1, 0xb, 0x6f}"},
{"%#06v", [3]byte{1, 11, 111}, "[3]uint8{0x000001, 0x00000b, 0x00006f}"},
{"%#-6v", [3]byte{1, 11, 111}, "[3]uint8{0x1 , 0xb , 0x6f }"},
{"%#-06v", [3]byte{1, 11, 111}, "[3]uint8{0x1 , 0xb , 0x6f }"},
// f.space should and f.plus should not have an effect with %v. // f.space should and f.plus should not have an effect with %v.
{"% v", []byte{1, 11, 111}, "[ 1 11 111]"}, {"% v", []byte{1, 11, 111}, "[ 1 11 111]"},
{"%+v", [3]byte{1, 11, 111}, "[1 11 111]"}, {"%+v", [3]byte{1, 11, 111}, "[1 11 111]"},
...@@ -631,10 +612,20 @@ var fmtTests = []struct { ...@@ -631,10 +612,20 @@ var fmtTests = []struct {
{"%#v", "foo", `"foo"`}, {"%#v", "foo", `"foo"`},
{"%#v", barray, `[5]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`}, {"%#v", barray, `[5]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`},
{"%#v", bslice, `[]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`}, {"%#v", bslice, `[]fmt_test.renamedUint8{0x1, 0x2, 0x3, 0x4, 0x5}`},
{"%#v", []byte(nil), "[]byte(nil)"},
{"%#v", []int32(nil), "[]int32(nil)"}, {"%#v", []int32(nil), "[]int32(nil)"},
{"%#v", 1.2345678, "1.2345678"}, {"%#v", 1.2345678, "1.2345678"},
{"%#v", float32(1.2345678), "1.2345678"}, {"%#v", float32(1.2345678), "1.2345678"},
// Only print []byte and []uint8 as type []byte if they appear at the top level.
{"%#v", []byte(nil), "[]byte(nil)"},
{"%#v", []uint8(nil), "[]byte(nil)"},
{"%#v", []byte{}, "[]byte{}"},
{"%#v", []uint8{}, "[]byte{}"},
{"%#v", reflect.ValueOf([]byte{}), "[]uint8{}"},
{"%#v", reflect.ValueOf([]uint8{}), "[]uint8{}"},
{"%#v", &[]byte{}, "&[]uint8{}"},
{"%#v", &[]byte{}, "&[]uint8{}"},
{"%#v", [3]byte{}, "[3]uint8{0x0, 0x0, 0x0}"},
{"%#v", [3]uint8{}, "[3]uint8{0x0, 0x0, 0x0}"},
// slices with other formats // slices with other formats
{"%#x", []int{1, 2, 15}, `[0x1 0x2 0xf]`}, {"%#x", []int{1, 2, 15}, `[0x1 0x2 0xf]`},
...@@ -985,10 +976,10 @@ var fmtTests = []struct { ...@@ -985,10 +976,10 @@ var fmtTests = []struct {
{"%☠", interface{}(nil), "%!☠(<nil>)"}, {"%☠", interface{}(nil), "%!☠(<nil>)"},
{"%☠", int(0), "%!☠(int=0)"}, {"%☠", int(0), "%!☠(int=0)"},
{"%☠", uint(0), "%!☠(uint=0)"}, {"%☠", uint(0), "%!☠(uint=0)"},
{"%☠", []byte{0}, "%!☠([]uint8=[0])"}, {"%☠", []byte{0, 1}, "[%!☠(uint8=0) %!☠(uint8=1)]"},
{"%☠", []uint8{0}, "%!☠([]uint8=[0])"}, {"%☠", []uint8{0, 1}, "[%!☠(uint8=0) %!☠(uint8=1)]"},
{"%☠", [1]byte{0}, "%!☠([1]uint8=[0])"}, {"%☠", [1]byte{0}, "[%!☠(uint8=0)]"},
{"%☠", [1]uint8{0}, "%!☠([1]uint8=[0])"}, {"%☠", [1]uint8{0}, "[%!☠(uint8=0)]"},
{"%☠", "hello", "%!☠(string=hello)"}, {"%☠", "hello", "%!☠(string=hello)"},
{"%☠", 1.2345678, "%!☠(float64=1.2345678)"}, {"%☠", 1.2345678, "%!☠(float64=1.2345678)"},
{"%☠", float32(1.2345678), "%!☠(float32=1.2345678)"}, {"%☠", float32(1.2345678), "%!☠(float32=1.2345678)"},
......
...@@ -26,7 +26,6 @@ const ( ...@@ -26,7 +26,6 @@ const (
badIndexString = "(BADINDEX)" badIndexString = "(BADINDEX)"
panicString = "(PANIC=" panicString = "(PANIC="
extraString = "%!(EXTRA " extraString = "%!(EXTRA "
bytesString = "[]byte"
badWidthString = "%!(BADWIDTH)" badWidthString = "%!(BADWIDTH)"
badPrecString = "%!(BADPREC)" badPrecString = "%!(BADPREC)"
noVerbString = "%!(NOVERB)" noVerbString = "%!(NOVERB)"
...@@ -476,7 +475,7 @@ func (p *pp) fmtBytes(v []byte, verb rune, typeString string) { ...@@ -476,7 +475,7 @@ func (p *pp) fmtBytes(v []byte, verb rune, typeString string) {
case 'q': case 'q':
p.fmt.fmt_q(string(v)) p.fmt.fmt_q(string(v))
default: default:
p.badVerb(verb) p.printValue(reflect.ValueOf(v), verb, 0)
} }
} }
...@@ -655,7 +654,7 @@ func (p *pp) printArg(arg interface{}, verb rune) { ...@@ -655,7 +654,7 @@ func (p *pp) printArg(arg interface{}, verb rune) {
case string: case string:
p.fmtString(f, verb) p.fmtString(f, verb)
case []byte: case []byte:
p.fmtBytes(f, verb, bytesString) p.fmtBytes(f, verb, "[]byte")
case reflect.Value: case reflect.Value:
p.printValue(f, verb, 0) p.printValue(f, verb, 0)
default: default:
...@@ -775,54 +774,52 @@ func (p *pp) printValue(value reflect.Value, verb rune, depth int) { ...@@ -775,54 +774,52 @@ func (p *pp) printValue(value reflect.Value, verb rune, depth int) {
p.printValue(value, verb, depth+1) p.printValue(value, verb, depth+1)
} }
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
// Byte arrays and slices are special: switch verb {
// - Handle []byte (== []uint8) with fmtBytes. case 's', 'q', 'x', 'X':
// - Handle []T, where T is a named byte type, with fmtBytes only // Handle byte and uint8 slices and arrays special for the above verbs.
// for the s, q, x and X verbs. For other verbs, T might be a t := f.Type()
// Stringer, so we use printValue to print each element. if t.Elem().Kind() == reflect.Uint8 {
typ := f.Type() var bytes []byte
if typ.Elem().Kind() == reflect.Uint8 && if f.Kind() == reflect.Slice {
(typ.Elem() == byteType || verb == 's' || verb == 'q' || verb == 'x' || verb == 'X') { bytes = f.Bytes()
var bytes []byte } else if f.CanAddr() {
if f.Kind() == reflect.Slice { bytes = f.Slice(0, f.Len()).Bytes()
bytes = f.Bytes() } else {
} else if f.CanAddr() { // We have an array, but we cannot Slice() a non-addressable array,
bytes = f.Slice(0, f.Len()).Bytes() // so we build a slice by hand. This is a rare case but it would be nice
} else { // if reflection could help a little more.
// We have an array, but we cannot Slice() a non-addressable array, bytes = make([]byte, f.Len())
// so we build a slice by hand. This is a rare case but it would be nice for i := range bytes {
// if reflection could help a little more. bytes[i] = byte(f.Index(i).Uint())
bytes = make([]byte, f.Len()) }
for i := range bytes {
bytes[i] = byte(f.Index(i).Uint())
} }
p.fmtBytes(bytes, verb, t.String())
return
} }
p.fmtBytes(bytes, verb, typ.String())
return
} }
if p.fmt.sharpV { if p.fmt.sharpV {
p.buf.WriteString(typ.String()) p.buf.WriteString(f.Type().String())
if f.Kind() == reflect.Slice && f.IsNil() { if f.Kind() == reflect.Slice && f.IsNil() {
p.buf.WriteString(nilParenString) p.buf.WriteString(nilParenString)
return return
} else {
p.buf.WriteByte('{')
for i := 0; i < f.Len(); i++ {
if i > 0 {
p.buf.WriteString(commaSpaceString)
}
p.printValue(f.Index(i), verb, depth+1)
}
p.buf.WriteByte('}')
} }
p.buf.WriteByte('{')
} else { } else {
p.buf.WriteByte('[') p.buf.WriteByte('[')
} for i := 0; i < f.Len(); i++ {
for i := 0; i < f.Len(); i++ { if i > 0 {
if i > 0 {
if p.fmt.sharpV {
p.buf.WriteString(commaSpaceString)
} else {
p.buf.WriteByte(' ') p.buf.WriteByte(' ')
} }
p.printValue(f.Index(i), verb, depth+1)
} }
p.printValue(f.Index(i), verb, depth+1)
}
if p.fmt.sharpV {
p.buf.WriteByte('}')
} else {
p.buf.WriteByte(']') p.buf.WriteByte(']')
} }
case reflect.Ptr: case reflect.Ptr:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment