diff options
author | Marcel van Lohuizen <mpvl@golang.org> | 2019-02-23 00:29:15 +0100 |
---|---|---|
committer | Marcel van Lohuizen <mpvl@golang.org> | 2019-02-27 19:29:14 +0000 |
commit | 6be6f114e0d483a233101a67c9644cd72bd3ae7a (patch) | |
tree | 7860e6c34e77f2e2a8390dfc91f1db77028e70cf /src/fmt | |
parent | 62f5e8156ef56fa61e6af56f4ccc633bde1a9120 (diff) | |
download | go-6be6f114e0d483a233101a67c9644cd72bd3ae7a.tar.gz go-6be6f114e0d483a233101a67c9644cd72bd3ae7a.zip |
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>
Diffstat (limited to 'src/fmt')
-rw-r--r-- | src/fmt/doc.go | 18 | ||||
-rw-r--r-- | src/fmt/errors.go | 239 | ||||
-rw-r--r-- | src/fmt/errors_test.go | 534 | ||||
-rw-r--r-- | src/fmt/format.go | 5 | ||||
-rw-r--r-- | src/fmt/format_example_test.go | 46 | ||||
-rw-r--r-- | src/fmt/print.go | 35 |
6 files changed, 851 insertions, 26 deletions
diff --git a/src/fmt/doc.go b/src/fmt/doc.go index a7115809d3..b784399e0d 100644 --- a/src/fmt/doc.go +++ b/src/fmt/doc.go @@ -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). diff --git a/src/fmt/errors.go b/src/fmt/errors.go new file mode 100644 index 0000000000..0fd3e83814 --- /dev/null +++ b/src/fmt/errors.go @@ -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 +} diff --git a/src/fmt/errors_test.go b/src/fmt/errors_test.go new file mode 100644 index 0000000000..9e6ad74697 --- /dev/null +++ b/src/fmt/errors_test.go @@ -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: ¬AFormatterError{}, + fmt: "%+v", + want: "not a formatter", + }, { + err: &wrapped{"wrap", ¬AFormatterError{}}, + 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 +} diff --git a/src/fmt/format.go b/src/fmt/format.go index 24e7e9551a..546c456c50 100644 --- a/src/fmt/format.go +++ b/src/fmt/format.go @@ -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. diff --git a/src/fmt/format_example_test.go b/src/fmt/format_example_test.go new file mode 100644 index 0000000000..386f10ef23 --- /dev/null +++ b/src/fmt/format_example_test.go @@ -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 +} diff --git a/src/fmt/print.go b/src/fmt/print.go index 121c7c59e4..c4ec73c77a 100644 --- a/src/fmt/print.go +++ b/src/fmt/print.go @@ -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) |