1
0
mirror of https://github.com/golang/go synced 2024-11-22 03:34:40 -07:00

fmt: only use Stringer or Error for strings

This is a slight change to fmt's semantics, but means that if you use
%d to print an integer with a Stringable value, it will print as an integer.
This came up because Time.Month() couldn't cleanly print as an integer
rather than a name. Using %d on Stringables is silly anyway, so there
should be no effect outside the fmt tests.
As a mild bonus, certain recursive failures of String methods
will also be avoided this way.

R=golang-dev, adg
CC=golang-dev
https://golang.org/cl/5453053
This commit is contained in:
Rob Pike 2011-12-05 16:45:51 -08:00
parent af84892643
commit 2ed57a8cd8
3 changed files with 41 additions and 26 deletions

View File

@ -89,18 +89,22 @@
If an operand implements interface Formatter, that interface If an operand implements interface Formatter, that interface
can be used for fine control of formatting. can be used for fine control of formatting.
Next, if an operand implements the error interface, the Error method If the format (which is implicitly %v for Println etc.) is valid
for a string (%s %q %v %x %X), the following two rules also apply:
1. If an operand implements the error interface, the Error method
will be used to convert the object to a string, which will then will be used to convert the object to a string, which will then
be formatted as required by the verb (if any). be formatted as required by the verb (if any).
Finally, if an operand implements method String() string that method 2. If an operand implements method String() string, that method
will be used to convert the object to a string, which will then will be used to convert the object to a string, which will then
be formatted as required by the verb (if any). be formatted as required by the verb (if any).
To avoid recursion in cases such as To avoid recursion in cases such as
type X int type X string
func (x X) String() string { return Sprintf("%d", x) } func (x X) String() string { return Sprintf("<%s>", x) }
cast the value before recurring: cast the value before recurring:
func (x X) String() string { return Sprintf("%d", int(x)) } func (x X) String() string { return Sprintf("<%s>", string(x)) }
Format errors: Format errors:

View File

@ -12,6 +12,7 @@ import (
"runtime" // for the malloc count test only "runtime" // for the malloc count test only
"strings" "strings"
"testing" "testing"
"time"
) )
type ( type (
@ -352,7 +353,7 @@ var fmttests = []struct {
{"%s", I(23), `<23>`}, {"%s", I(23), `<23>`},
{"%q", I(23), `"<23>"`}, {"%q", I(23), `"<23>"`},
{"%x", I(23), `3c32333e`}, {"%x", I(23), `3c32333e`},
{"%d", I(23), `%!d(string=<23>)`}, {"%d", I(23), `23`}, // Stringer applies only to string formats.
// go syntax // go syntax
{"%#v", A{1, 2, "a", []int{1, 2}}, `fmt_test.A{i:1, j:0x2, s:"a", x:[]int{1, 2}}`}, {"%#v", A{1, 2, "a", []int{1, 2}}, `fmt_test.A{i:1, j:0x2, s:"a", x:[]int{1, 2}}`},
@ -430,6 +431,10 @@ var fmttests = []struct {
{"%p", make([]int, 1), "0xPTR"}, {"%p", make([]int, 1), "0xPTR"},
{"%p", 27, "%!p(int=27)"}, // not a pointer at all {"%p", 27, "%!p(int=27)"}, // not a pointer at all
// %d on Stringer should give integer if possible
{"%s", time.Time{}.Month(), "January"},
{"%d", time.Time{}.Month(), "1"},
// erroneous things // erroneous things
{"%s %", "hello", "hello %!(NOVERB)"}, {"%s %", "hello", "hello %!(NOVERB)"},
{"%s %.2", "hello", "hello %!(NOVERB)"}, {"%s %.2", "hello", "hello %!(NOVERB)"},
@ -772,9 +777,9 @@ var panictests = []struct {
out string out string
}{ }{
// String // String
{"%d", (*Panic)(nil), "<nil>"}, // nil pointer special case {"%s", (*Panic)(nil), "<nil>"}, // nil pointer special case
{"%d", Panic{io.ErrUnexpectedEOF}, "%d(PANIC=unexpected EOF)"}, {"%s", Panic{io.ErrUnexpectedEOF}, "%s(PANIC=unexpected EOF)"},
{"%d", Panic{3}, "%d(PANIC=3)"}, {"%s", Panic{3}, "%s(PANIC=3)"},
// GoString // GoString
{"%#v", (*Panic)(nil), "<nil>"}, // nil pointer special case {"%#v", (*Panic)(nil), "<nil>"}, // nil pointer special case
{"%#v", Panic{io.ErrUnexpectedEOF}, "%v(PANIC=unexpected EOF)"}, {"%#v", Panic{io.ErrUnexpectedEOF}, "%v(PANIC=unexpected EOF)"},

View File

@ -631,24 +631,30 @@ func (p *pp) handleMethods(verb rune, plus, goSyntax bool, depth int) (wasString
return return
} }
} else { } else {
// Is it an error or Stringer? // If a string is acceptable according to the format, see if
// The duplication in the bodies is necessary: // the value satisfies one of the string-valued interfaces.
// setting wasString and handled and deferring catchPanic // Println etc. set verb to %v, which is "stringable".
// must happen before calling the method. switch verb {
switch v := p.field.(type) { case 'v', 's', 'x', 'X', 'q':
case error: // Is it an error or Stringer?
wasString = false // The duplication in the bodies is necessary:
handled = true // setting wasString and handled, and deferring catchPanic,
defer p.catchPanic(p.field, verb) // must happen before calling the method.
p.printField(v.Error(), verb, plus, false, depth) switch v := p.field.(type) {
return case error:
wasString = false
handled = true
defer p.catchPanic(p.field, verb)
p.printField(v.Error(), verb, plus, false, depth)
return
case Stringer: case Stringer:
wasString = false wasString = false
handled = true handled = true
defer p.catchPanic(p.field, verb) defer p.catchPanic(p.field, verb)
p.printField(v.String(), verb, plus, false, depth) p.printField(v.String(), verb, plus, false, depth)
return return
}
} }
} }
handled = false handled = false