1
0
mirror of https://github.com/golang/go synced 2024-11-17 02:54:45 -07:00

fmt: add frame info to Errorf and support %w

Partly implements proposal Issue #29934.

Change-Id: Ibcf12f383158dcfbc313ab29c417a710571d1acb
Reviewed-on: https://go-review.googlesource.com/c/163559
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Damien Neil <dneil@google.com>
This commit is contained in:
Marcel van Lohuizen 2019-02-23 00:29:15 +01:00
parent 62f5e8156e
commit 6be6f114e0
8 changed files with 853 additions and 27 deletions

View File

@ -731,6 +731,7 @@ var printVerbs = []printVerb{
{'T', "-", anyType},
{'U', "-#", argRune | argInt},
{'v', allFlags, anyType},
{'w', noFlag, anyType},
{'x', sharpNumFlag, argRune | argInt | argString | argPointer},
{'X', sharpNumFlag, argRune | argInt | argString | argPointer},
}

View File

@ -149,20 +149,28 @@
1. If the operand is a reflect.Value, the operand is replaced by the
concrete value that it holds, and printing continues with the next rule.
2. If an operand implements the Formatter interface, it will
be invoked. Formatter provides fine control of formatting.
2. If an operand implements the Formatter interface, and not
errors.Formatter, it will be invoked. Formatter provides fine
control of formatting.
3. If the %v verb is used with the # flag (%#v) and the operand
implements the GoStringer interface, that will be invoked.
If the format (which is implicitly %v for Println etc.) is valid
for a string (%s %q %v %x %X), the following two rules apply:
for a string (%s %q %v %x %X), the following three rules apply:
4. If an operand implements the error interface, the Error method
4. If an operand implements errors.Formatter, the FormatError
method will be invoked with an errors.Printer to print the error.
If the %v flag is used with the + flag (%+v), the Detail method
of the Printer will return true and the error will be formatted
as a detailed error message. Otherwise the printed string will
be formatted as required by the verb (if any).
5. If an operand implements the error interface, the Error method
will be invoked to convert the object to a string, which will then
be formatted as required by the verb (if any).
5. If an operand implements method String() string, that method
6. If an operand implements method String() string, that method
will be invoked to convert the object to a string, which will then
be formatted as required by the verb (if any).

239
src/fmt/errors.go Normal file
View File

@ -0,0 +1,239 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fmt
import (
"errors"
"strings"
)
// Errorf formats according to a format specifier and returns the string as a
// value that satisfies error.
//
// The returned error includes the file and line number of the caller when
// formatted with additional detail enabled. If the last argument is an error
// the returned error's Format method will return it if the format string ends
// with ": %s", ": %v", or ": %w". If the last argument is an error and the
// format string ends with ": %w", the returned error implements errors.Wrapper
// with an Unwrap method returning it.
func Errorf(format string, a ...interface{}) error {
err, wrap := lastError(format, a)
if err == nil {
return &noWrapError{Sprintf(format, a...), nil, errors.Caller(1)}
}
// TODO: this is not entirely correct. The error value could be
// printed elsewhere in format if it mixes numbered with unnumbered
// substitutions. With relatively small changes to doPrintf we can
// have it optionally ignore extra arguments and pass the argument
// list in its entirety.
msg := Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
if wrap {
return &wrapError{msg, err, errors.Caller(1)}
}
return &noWrapError{msg, err, errors.Caller(1)}
}
func lastError(format string, a []interface{}) (err error, wrap bool) {
wrap = strings.HasSuffix(format, ": %w")
if !wrap &&
!strings.HasSuffix(format, ": %s") &&
!strings.HasSuffix(format, ": %v") {
return nil, false
}
if len(a) == 0 {
return nil, false
}
err, ok := a[len(a)-1].(error)
if !ok {
return nil, false
}
return err, wrap
}
type noWrapError struct {
msg string
err error
frame errors.Frame
}
func (e *noWrapError) Error() string {
return Sprint(e)
}
func (e *noWrapError) FormatError(p errors.Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
}
type wrapError struct {
msg string
err error
frame errors.Frame
}
func (e *wrapError) Error() string {
return Sprint(e)
}
func (e *wrapError) FormatError(p errors.Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
return e.err
}
func (e *wrapError) Unwrap() error {
return e.err
}
func fmtError(p *pp, verb rune, err error) (handled bool) {
var (
sep = " " // separator before next error
w = p // print buffer where error text is written
)
switch {
// Note that this switch must match the preference order
// for ordinary string printing (%#v before %+v, and so on).
case p.fmt.sharpV:
if stringer, ok := p.arg.(GoStringer); ok {
// Print the result of GoString unadorned.
p.fmt.fmtS(stringer.GoString())
return true
}
return false
case p.fmt.plusV:
sep = "\n - "
w.fmt.fmtFlags = fmtFlags{plusV: p.fmt.plusV} // only keep detail flag
// The width or precision of a detailed view could be the number of
// errors to print from a list.
default:
// Use an intermediate buffer in the rare cases that precision,
// truncation, or one of the alternative verbs (q, x, and X) are
// specified.
switch verb {
case 's', 'v':
if (!w.fmt.widPresent || w.fmt.wid == 0) && !w.fmt.precPresent {
break
}
fallthrough
case 'q', 'x', 'X':
w = newPrinter()
defer w.free()
default:
w.badVerb(verb)
return true
}
}
loop:
for {
w.fmt.inDetail = false
switch v := err.(type) {
case errors.Formatter:
err = v.FormatError((*errPP)(w))
case Formatter:
if w.fmt.plusV {
v.Format((*errPPState)(w), 'v') // indent new lines
} else {
v.Format(w, 'v') // do not indent new lines
}
break loop
default:
w.fmtString(v.Error(), 's')
break loop
}
if err == nil {
break
}
if w.fmt.needColon || !p.fmt.plusV {
w.buf.WriteByte(':')
w.fmt.needColon = false
}
w.buf.WriteString(sep)
w.fmt.inDetail = false
w.fmt.needNewline = false
}
if w != p {
p.fmtString(string(w.buf), verb)
}
return true
}
var detailSep = []byte("\n ")
// errPPState wraps a pp to implement State with indentation. It is used
// for errors implementing fmt.Formatter.
type errPPState pp
func (p *errPPState) Width() (wid int, ok bool) { return (*pp)(p).Width() }
func (p *errPPState) Precision() (prec int, ok bool) { return (*pp)(p).Precision() }
func (p *errPPState) Flag(c int) bool { return (*pp)(p).Flag(c) }
func (p *errPPState) Write(b []byte) (n int, err error) {
if p.fmt.plusV {
if len(b) == 0 {
return 0, nil
}
if p.fmt.inDetail && p.fmt.needColon {
p.fmt.needNewline = true
if b[0] == '\n' {
b = b[1:]
}
}
k := 0
for i, c := range b {
if p.fmt.needNewline {
if p.fmt.inDetail && p.fmt.needColon {
p.buf.WriteByte(':')
p.fmt.needColon = false
}
p.buf.Write(detailSep)
p.fmt.needNewline = false
}
if c == '\n' {
p.buf.Write(b[k:i])
k = i + 1
p.fmt.needNewline = true
}
}
p.buf.Write(b[k:])
if !p.fmt.inDetail {
p.fmt.needColon = true
}
} else if !p.fmt.inDetail {
p.buf.Write(b)
}
return len(b), nil
}
// errPP wraps a pp to implement an errors.Printer.
type errPP pp
func (p *errPP) Print(args ...interface{}) {
if !p.fmt.inDetail || p.fmt.plusV {
Fprint((*errPPState)(p), args...)
}
}
func (p *errPP) Printf(format string, args ...interface{}) {
if !p.fmt.inDetail || p.fmt.plusV {
Fprintf((*errPPState)(p), format, args...)
}
}
func (p *errPP) Detail() bool {
p.fmt.inDetail = true
return p.fmt.plusV
}

534
src/fmt/errors_test.go Normal file
View File

@ -0,0 +1,534 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fmt_test
import (
"errors"
"fmt"
"io"
"os"
"path"
"reflect"
"regexp"
"strconv"
"strings"
"testing"
)
func TestErrorf(t *testing.T) {
chained := &wrapped{"chained", nil}
chain := func(s ...string) (a []string) {
for _, s := range s {
a = append(a, cleanPath(s))
}
return a
}
noArgsWrap := "no args: %w" // avoid vet check
testCases := []struct {
got error
want []string
}{{
fmt.Errorf("no args"),
chain("no args/path.TestErrorf/path.go:xxx"),
}, {
fmt.Errorf(noArgsWrap),
chain("no args: %!w(MISSING)/path.TestErrorf/path.go:xxx"),
}, {
fmt.Errorf("nounwrap: %s", "simple"),
chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`),
}, {
fmt.Errorf("nounwrap: %v", "simple"),
chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`),
}, {
fmt.Errorf("%s failed: %v", "foo", chained),
chain("foo failed/path.TestErrorf/path.go:xxx",
"chained/somefile.go:xxx"),
}, {
fmt.Errorf("no wrap: %s", chained),
chain("no wrap/path.TestErrorf/path.go:xxx",
"chained/somefile.go:xxx"),
}, {
fmt.Errorf("%s failed: %w", "foo", chained),
chain("wraps:foo failed/path.TestErrorf/path.go:xxx",
"chained/somefile.go:xxx"),
}, {
fmt.Errorf("nowrapv: %v", chained),
chain("nowrapv/path.TestErrorf/path.go:xxx",
"chained/somefile.go:xxx"),
}, {
fmt.Errorf("wrapw: %w", chained),
chain("wraps:wrapw/path.TestErrorf/path.go:xxx",
"chained/somefile.go:xxx"),
}, {
fmt.Errorf("not wrapped: %+v", chained),
chain("not wrapped: chained: somefile.go:123/path.TestErrorf/path.go:xxx"),
}}
for i, tc := range testCases {
t.Run(strconv.Itoa(i)+"/"+path.Join(tc.want...), func(t *testing.T) {
got := errToParts(tc.got)
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("Format:\n got: %+q\nwant: %+q", got, tc.want)
}
gotStr := tc.got.Error()
wantStr := fmt.Sprint(tc.got)
if gotStr != wantStr {
t.Errorf("Error:\n got: %+q\nwant: %+q", gotStr, wantStr)
}
})
}
}
func TestErrorFormatter(t *testing.T) {
testCases := []struct {
err error
fmt string
want string
regexp bool
}{{
err: errors.New("foo"),
fmt: "%+v",
want: "foo:" +
"\n fmt_test.TestErrorFormatter" +
"\n .+/fmt/errors_test.go:\\d\\d",
regexp: true,
}, {
err: &wrapped{"simple", nil},
fmt: "%s",
want: "simple",
}, {
err: &wrapped{"can't adumbrate elephant", outOfPeanuts{}},
fmt: "%s",
want: "can't adumbrate elephant: out of peanuts",
}, {
err: &wrapped{"a", &wrapped{"b", &wrapped{"c", nil}}},
fmt: "%s",
want: "a: b: c",
}, {
err: &wrapped{"simple", nil},
fmt: "%+v",
want: "simple:" +
"\n somefile.go:123",
}, {
err: &wrapped{"can't adumbrate elephant", outOfPeanuts{}},
fmt: "%+v",
want: "can't adumbrate elephant:" +
"\n somefile.go:123" +
"\n - out of peanuts:" +
"\n the elephant is on strike" +
"\n and the 12 monkeys" +
"\n are laughing",
}, {
err: &wrapped{"simple", nil},
fmt: "%#v",
want: "&fmt_test.wrapped{msg:\"simple\", err:error(nil)}",
}, {
err: &notAFormatterError{},
fmt: "%+v",
want: "not a formatter",
}, {
err: &wrapped{"wrap", &notAFormatterError{}},
fmt: "%+v",
want: "wrap:" +
"\n somefile.go:123" +
"\n - not a formatter",
}, {
err: &withFrameAndMore{frame: errors.Caller(0)},
fmt: "%+v",
want: "something:" +
"\n fmt_test.TestErrorFormatter" +
"\n .+/fmt/errors_test.go:\\d\\d\\d" +
"\n something more",
regexp: true,
}, {
err: fmtTwice("Hello World!"),
fmt: "%#v",
want: "2 times Hello World!",
}, {
err: &wrapped{"fallback", os.ErrNotExist},
fmt: "%s",
want: "fallback: file does not exist",
}, {
err: &wrapped{"fallback", os.ErrNotExist},
fmt: "%+v",
// Note: no colon after the last error, as there are no details.
want: "fallback:" +
"\n somefile.go:123" +
"\n - file does not exist:" +
"\n os.init.ializers" +
"\n .+/os/error.go:\\d\\d",
regexp: true,
}, {
err: &wrapped{"outer",
errors.Opaque(&wrapped{"mid",
&wrapped{"inner", nil}})},
fmt: "%s",
want: "outer: mid: inner",
}, {
err: &wrapped{"outer",
errors.Opaque(&wrapped{"mid",
&wrapped{"inner", nil}})},
fmt: "%+v",
want: "outer:" +
"\n somefile.go:123" +
"\n - mid:" +
"\n somefile.go:123" +
"\n - inner:" +
"\n somefile.go:123",
}, {
err: &wrapped{"new style", formatError("old style")},
fmt: "%v",
want: "new style: old style",
}, {
err: &wrapped{"new style", formatError("old style")},
fmt: "%q",
want: `"new style: old style"`,
}, {
err: &wrapped{"new style", formatError("old style")},
fmt: "%+v",
// Note the extra indentation.
// Colon for old style error is rendered by the fmt.Formatter
// implementation of the old-style error.
want: "new style:" +
"\n somefile.go:123" +
"\n - old style:" +
"\n otherfile.go:456",
}, {
err: &wrapped{"simple", nil},
fmt: "%-12s",
want: "simple ",
}, {
// Don't use formatting flags for detailed view.
err: &wrapped{"simple", nil},
fmt: "%+12v",
want: "simple:" +
"\n somefile.go:123",
}, {
err: &wrapped{"can't adumbrate elephant", outOfPeanuts{}},
fmt: "%+50s",
want: " can't adumbrate elephant: out of peanuts",
}, {
err: &wrapped{"café", nil},
fmt: "%q",
want: `"café"`,
}, {
err: &wrapped{"café", nil},
fmt: "%+q",
want: `"caf\u00e9"`,
}, {
err: &wrapped{"simple", nil},
fmt: "% x",
want: "73 69 6d 70 6c 65",
}, {
err: &wrapped{"msg with\nnewline",
&wrapped{"and another\none", nil}},
fmt: "%s",
want: "msg with" +
"\nnewline: and another" +
"\none",
}, {
err: &wrapped{"msg with\nnewline",
&wrapped{"and another\none", nil}},
fmt: "%+v",
want: "msg with" +
"\n newline:" +
"\n somefile.go:123" +
"\n - and another" +
"\n one:" +
"\n somefile.go:123",
}, {
err: wrapped{"", wrapped{"inner message", nil}},
fmt: "%+v",
want: "somefile.go:123" +
"\n - inner message:" +
"\n somefile.go:123",
}, {
err: detail{"empty detail", "", nil},
fmt: "%s",
want: "empty detail",
}, {
err: detail{"empty detail", "", nil},
fmt: "%+v",
want: "empty detail",
}, {
err: detail{"newline at start", "\nextra", nil},
fmt: "%s",
want: "newline at start",
}, {
err: detail{"newline at start", "\n extra", nil},
fmt: "%+v",
want: "newline at start:" +
"\n extra",
}, {
err: detail{"newline at start", "\nextra",
detail{"newline at start", "\nmore", nil}},
fmt: "%+v",
want: "newline at start:" +
"\n extra" +
"\n - newline at start:" +
"\n more",
}, {
err: detail{"two newlines at start", "\n\nextra",
detail{"two newlines at start", "\n\nmore", nil}},
fmt: "%+v",
want: "two newlines at start:" +
"\n " + // note the explicit space
"\n extra" +
"\n - two newlines at start:" +
"\n " +
"\n more",
}, {
err: &detail{"single newline", "\n", nil},
fmt: "%+v",
want: "single newline",
}, {
err: &detail{"single newline", "\n",
&detail{"single newline", "\n", nil}},
fmt: "%+v",
want: "single newline:" +
"\n - single newline",
}, {
err: &detail{"newline at end", "detail\n", nil},
fmt: "%+v",
want: "newline at end:" +
"\n detail",
}, {
err: &detail{"newline at end", "detail\n",
&detail{"newline at end", "detail\n", nil}},
fmt: "%+v",
want: "newline at end:" +
"\n detail" +
"\n - newline at end:" +
"\n detail",
}, {
err: &detail{"two newlines at end", "detail\n\n",
&detail{"two newlines at end", "detail\n\n", nil}},
fmt: "%+v",
want: "two newlines at end:" +
"\n detail" +
"\n " +
"\n - two newlines at end:" +
"\n detail" +
"\n ", // note the additional space
}, {
err: nil,
fmt: "%+v",
want: "<nil>",
}, {
err: (*wrapped)(nil),
fmt: "%+v",
want: "<nil>",
}, {
err: &wrapped{"simple", nil},
fmt: "%T",
want: "*fmt_test.wrapped",
}, {
err: &wrapped{"simple", nil},
fmt: "%🤪",
want: "%!🤪(*fmt_test.wrapped=&{simple <nil>})",
}, {
err: formatError("use fmt.Formatter"),
fmt: "%#v",
want: "use fmt.Formatter",
}, {
err: wrapped{"using errors.Formatter",
formatError("use fmt.Formatter")},
fmt: "%#v",
want: "fmt_test.wrapped{msg:\"using errors.Formatter\", err:\"use fmt.Formatter\"}",
}, {
err: fmtTwice("%s %s", "ok", panicValue{}),
fmt: "%s",
want: "ok %!s(PANIC=String method: panic)/ok %!s(PANIC=String method: panic)",
}, {
err: fmtTwice("%o %s", panicValue{}, "ok"),
fmt: "%s",
want: "{} ok/{} ok",
}}
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d/%s", i, tc.fmt), func(t *testing.T) {
got := fmt.Sprintf(tc.fmt, tc.err)
var ok bool
if tc.regexp {
var err error
ok, err = regexp.MatchString(tc.want+"$", got)
if err != nil {
t.Fatal(err)
}
} else {
ok = got == tc.want
}
if !ok {
t.Errorf("\n got: %q\nwant: %q", got, tc.want)
}
})
}
}
var _ errors.Formatter = wrapped{}
type wrapped struct {
msg string
err error
}
func (e wrapped) Error() string { return fmt.Sprint(e) }
func (e wrapped) FormatError(p errors.Printer) (next error) {
p.Print(e.msg)
p.Detail()
p.Print("somefile.go:123")
return e.err
}
var _ errors.Formatter = outOfPeanuts{}
type outOfPeanuts struct{}
func (e outOfPeanuts) Error() string { return fmt.Sprint(e) }
func (e outOfPeanuts) Format(fmt.State, rune) {
panic("should never be called by one of the tests")
}
func (outOfPeanuts) FormatError(p errors.Printer) (next error) {
p.Printf("out of %s", "peanuts")
p.Detail()
p.Print("the elephant is on strike\n")
p.Printf("and the %d monkeys\nare laughing", 12)
return nil
}
type withFrameAndMore struct {
frame errors.Frame
}
func (e *withFrameAndMore) Error() string { return fmt.Sprint(e) }
func (e *withFrameAndMore) FormatError(p errors.Printer) (next error) {
p.Print("something")
if p.Detail() {
e.frame.Format(p)
p.Print("something more")
}
return nil
}
type notAFormatterError struct{}
func (e notAFormatterError) Error() string { return "not a formatter" }
type detail struct {
msg string
detail string
next error
}
func (e detail) Error() string { return fmt.Sprint(e) }
func (e detail) FormatError(p errors.Printer) (next error) {
p.Print(e.msg)
p.Detail()
p.Print(e.detail)
return e.next
}
// formatError is an error implementing Format instead of errors.Formatter.
// The implementation mimics the implementation of github.com/pkg/errors.
type formatError string
func (e formatError) Error() string { return string(e) }
func (e formatError) Format(s fmt.State, verb rune) {
// Body based on pkg/errors/errors.go
switch verb {
case 'v':
if s.Flag('+') {
io.WriteString(s, string(e))
fmt.Fprintf(s, ":\n%s", "otherfile.go:456")
return
}
fallthrough
case 's':
io.WriteString(s, string(e))
case 'q':
fmt.Fprintf(s, "%q", string(e))
}
}
func (e formatError) GoString() string {
panic("should never be called")
}
type fmtTwiceErr struct {
format string
args []interface{}
}
func fmtTwice(format string, a ...interface{}) error {
return fmtTwiceErr{format, a}
}
func (e fmtTwiceErr) Error() string { return fmt.Sprint(e) }
func (e fmtTwiceErr) FormatError(p errors.Printer) (next error) {
p.Printf(e.format, e.args...)
p.Print("/")
p.Printf(e.format, e.args...)
return nil
}
func (e fmtTwiceErr) GoString() string {
return "2 times " + fmt.Sprintf(e.format, e.args...)
}
type panicValue struct{}
func (panicValue) String() string { panic("panic") }
var rePath = regexp.MustCompile(`( [^ ]*)fmt.*test\.`)
var reLine = regexp.MustCompile(":[0-9]*\n?$")
func cleanPath(s string) string {
s = rePath.ReplaceAllString(s, "/path.")
s = reLine.ReplaceAllString(s, ":xxx")
s = strings.Replace(s, "\n ", "", -1)
s = strings.Replace(s, " /", "/", -1)
return s
}
func errToParts(err error) (a []string) {
for err != nil {
var p testPrinter
if errors.Unwrap(err) != nil {
p.str += "wraps:"
}
f, ok := err.(errors.Formatter)
if !ok {
a = append(a, err.Error())
break
}
err = f.FormatError(&p)
a = append(a, cleanPath(p.str))
}
return a
}
type testPrinter struct {
str string
}
func (p *testPrinter) Print(a ...interface{}) {
p.str += fmt.Sprint(a...)
}
func (p *testPrinter) Printf(format string, a ...interface{}) {
p.str += fmt.Sprintf(format, a...)
}
func (p *testPrinter) Detail() bool {
p.str += " /"
return true
}

View File

@ -34,6 +34,11 @@ type fmtFlags struct {
// different, flagless formats set at the top level.
plusV bool
sharpV bool
// error-related flags.
inDetail bool
needNewline bool
needColon bool
}
// A fmt is the raw formatter used by Printf etc.

View File

@ -0,0 +1,46 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package fmt_test
import (
"errors"
"fmt"
"path/filepath"
"regexp"
)
func baz() error { return errors.New("baz flopped") }
func bar() error { return fmt.Errorf("bar(nameserver 139): %v", baz()) }
func foo() error { return fmt.Errorf("foo: %s", bar()) }
func Example_formatting() {
err := foo()
fmt.Println("Error:")
fmt.Printf("%v\n", err)
fmt.Println()
fmt.Println("Detailed error:")
fmt.Println(stripPath(fmt.Sprintf("%+v\n", err)))
// Output:
// Error:
// foo: bar(nameserver 139): baz flopped
//
// Detailed error:
// foo:
// fmt_test.foo
// fmt/format_example_test.go:16
// - bar(nameserver 139):
// fmt_test.bar
// fmt/format_example_test.go:15
// - baz flopped:
// fmt_test.baz
// fmt/format_example_test.go:14
}
func stripPath(s string) string {
rePath := regexp.MustCompile(`( [^ ]*)fmt`)
s = rePath.ReplaceAllString(s, " fmt")
s = filepath.ToSlash(s)
return s
}

View File

@ -217,12 +217,6 @@ func Sprintf(format string, a ...interface{}) string {
return s
}
// Errorf formats according to a format specifier and returns the string
// as a value that satisfies error.
func Errorf(format string, a ...interface{}) error {
return errors.New(Sprintf(format, a...))
}
// These routines do not take a format string
// Fprint formats using the default formats for its operands and writes to w.
@ -576,12 +570,22 @@ func (p *pp) handleMethods(verb rune) (handled bool) {
if p.erroring {
return
}
// Is it a Formatter?
if formatter, ok := p.arg.(Formatter); ok {
switch x := p.arg.(type) {
case errors.Formatter:
handled = true
defer p.catchPanic(p.arg, verb, "FormatError")
return fmtError(p, verb, x)
case Formatter:
handled = true
defer p.catchPanic(p.arg, verb, "Format")
formatter.Format(p, verb)
x.Format(p, verb)
return
case error:
handled = true
defer p.catchPanic(p.arg, verb, "Error")
return fmtError(p, verb, x)
}
// If we're doing Go syntax and the argument knows how to supply it, take care of it now.
@ -599,18 +603,7 @@ func (p *pp) handleMethods(verb rune) (handled bool) {
// Println etc. set verb to %v, which is "stringable".
switch verb {
case 'v', 's', 'x', 'X', 'q':
// Is it an error or Stringer?
// The duplication in the bodies is necessary:
// setting handled and deferring catchPanic
// must happen before calling the method.
switch v := p.arg.(type) {
case error:
handled = true
defer p.catchPanic(p.arg, verb, "Error")
p.fmtString(v.Error(), verb)
return
case Stringer:
if v, ok := p.arg.(Stringer); ok {
handled = true
defer p.catchPanic(p.arg, verb, "String")
p.fmtString(v.String(), verb)

View File

@ -183,7 +183,7 @@ var pkgDeps = map[string][]string{
},
// Formatted I/O: few dependencies (L1) but we must add reflect and internal/fmtsort.
"fmt": {"L1", "os", "reflect", "internal/fmtsort"},
"fmt": {"L1", "bytes", "strings", "os", "reflect", "internal/fmtsort"},
"log": {"L1", "os", "fmt", "time"},
// Packages used by testing must be low-level (L2+fmt).