]> Cypherpunks repositories - gostls13.git/commitdiff
fmt: add frame info to Errorf and support %w
authorMarcel van Lohuizen <mpvl@golang.org>
Fri, 22 Feb 2019 23:29:15 +0000 (00:29 +0100)
committerMarcel van Lohuizen <mpvl@golang.org>
Wed, 27 Feb 2019 19:29:14 +0000 (19:29 +0000)
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>
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
src/fmt/doc.go
src/fmt/errors.go [new file with mode: 0644]
src/fmt/errors_test.go [new file with mode: 0644]
src/fmt/format.go
src/fmt/format_example_test.go [new file with mode: 0644]
src/fmt/print.go
src/go/build/deps_test.go

index c0265aafeee7ff132b6b37e366a088a97cd884e3..8f657b1bfa66b01daacce76e11b9fa463eb29296 100644 (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},
 }
index a7115809d3dcf7d82c99ace45f55d4a950c7bb5a..b784399e0d920962f844db490748c4b608216c70 100644 (file)
        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 (file)
index 0000000..0fd3e83
--- /dev/null
@@ -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 (file)
index 0000000..9e6ad74
--- /dev/null
@@ -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
+}
index 24e7e9551a151f49f9a2b0a759c19e09c041533d..546c456c50aac74bf520bd7fdf386925b3a70675 100644 (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.
diff --git a/src/fmt/format_example_test.go b/src/fmt/format_example_test.go
new file mode 100644 (file)
index 0000000..386f10e
--- /dev/null
@@ -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
+}
index 121c7c59e42441caddcba8cbba81bf4da0adc928..c4ec73c77a3facda212652af831c0389cb86894f 100644 (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)
index 73270d3a233882440ecc2aa7f546c4ba9f3724e5..3b6dbd62219b84a4c9dff5e1370f3a13944c2f21 100644 (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).