xerrors: first working implementation
Change-Id: Iefd80aea06b50a7cb9a171b1a864ac86659c0455
Reviewed-on: https://go-review.googlesource.com/c/158760
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/xerrors/adaptor.go b/xerrors/adaptor.go
index e5db137..5f0df17 100644
--- a/xerrors/adaptor.go
+++ b/xerrors/adaptor.go
@@ -7,107 +7,146 @@
import (
"bytes"
"fmt"
+ "io"
+ "reflect"
+ "strconv"
)
-func fmtError(p *pp, verb rune, err error) (handled bool) {
+// FormatError calls the FormatError method of err with a errors.Printer
+// configured according to s and verb and writes the result to s.
+func FormatError(s fmt.State, verb rune, f Formatter) {
+ // Assuming this function is only called from the Format method, and given
+ // that FormatError takes precedence over Format, it cannot be called from
+ // any package that supports errors.Formatter. It is therefore safe to
+ // disregard that State may be a specific printer implementation and use one
+ // of our choice instead.
+
+ // limitations: does not support printing error as Go struct.
+
var (
- sep = " " // separator before next error
- w = p // print buffer where error text is written
+ sep = " " // separator before next error
+ p = &state{State: s}
+ direct = true
)
- switch {
+
+ var err error = f
+
+ switch verb {
// 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
+ case 'v':
+ if s.Flag('#') {
+ if stringer, ok := err.(fmt.GoStringer); ok {
+ io.WriteString(&p.buf, stringer.GoString())
+ goto exit
+ }
+ // proceed as if it were %v
+ } else if s.Flag('+') {
+ p.printDetail = true
+ sep = "\n - "
}
- 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:
+ case 's':
+ case 'q', 'x', 'X':
// 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()
+ direct = false
+
+ default:
+ p.buf.WriteString("%!")
+ p.buf.WriteRune(verb)
+ p.buf.WriteByte('(')
+ switch {
+ case err != nil:
+ p.buf.WriteString(reflect.TypeOf(f).String())
default:
- w.badVerb(verb)
- return true
+ p.buf.WriteString("<nil>")
}
+ p.buf.WriteByte(')')
+ io.Copy(s, &p.buf)
+ return
}
loop:
for {
- w.fmt.inDetail = false
+ p.inDetail = false
+
switch v := err.(type) {
case Formatter:
- err = v.FormatError((*errPP)(w))
+ err = v.FormatError((*printer)(p))
case fmt.Formatter:
- if w.fmt.plusV {
- v.Format((*errPPState)(w), 'v') // indent new lines
- } else {
- v.Format(w, 'v') // do not indent new lines
- }
+ v.Format(p, 'v')
break loop
default:
- w.fmtString(v.Error(), 's')
+ io.WriteString(&p.buf, v.Error())
break loop
}
if err == nil {
break
}
- if !w.fmt.inDetail || !p.fmt.plusV {
- w.buf.WriteByte(':')
+ if !p.inDetail || !p.printDetail {
+ p.buf.WriteByte(':')
}
// Strip last newline of detail.
- if bytes.HasSuffix([]byte(w.buf), detailSep) {
- w.buf = w.buf[:len(w.buf)-len(detailSep)]
+ if bytes.HasSuffix(p.buf.Bytes(), detailSep) {
+ p.buf.Truncate(p.buf.Len() - len(detailSep))
}
- w.buf.WriteString(sep)
- w.fmt.inDetail = false
+ p.buf.WriteString(sep)
+ p.inDetail = false
}
- if w != p {
- p.fmtString(string(w.buf), verb)
+exit:
+ width, okW := s.Width()
+ prec, okP := s.Precision()
+
+ if !direct || (okW && width > 0) || okP {
+ // Construct format string from State s.
+ format := []byte{'%'}
+ if s.Flag('-') {
+ format = append(format, '-')
+ }
+ if s.Flag('+') {
+ format = append(format, '+')
+ }
+ if s.Flag(' ') {
+ format = append(format, ' ')
+ }
+ if okW {
+ format = strconv.AppendInt(format, int64(width), 10)
+ }
+ if okP {
+ format = append(format, '.')
+ format = strconv.AppendInt(format, int64(prec), 10)
+ }
+ format = append(format, string(verb)...)
+ fmt.Fprintf(s, string(format), p.buf.String())
+ } else {
+ io.Copy(s, &p.buf)
}
- 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
+// state tracks error printing state. It implements fmt.State.
+type state struct {
+ fmt.State
+ buf bytes.Buffer
-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) }
+ printDetail bool
+ inDetail bool
+ needNewline bool
+}
-func (p *errPPState) Write(b []byte) (n int, err error) {
- if !p.fmt.inDetail || p.fmt.plusV {
+func (s *state) Write(b []byte) (n int, err error) {
+ if s.printDetail {
if len(b) == 0 {
return 0, nil
}
- if p.fmt.inDetail && p.fmt.needNewline {
- p.fmt.needNewline = false
- p.buf.WriteByte(':')
- p.buf.Write(detailSep)
+ if s.inDetail && s.needNewline {
+ s.needNewline = false
+ s.buf.WriteByte(':')
+ s.buf.Write(detailSep)
if b[0] == '\n' {
b = b[1:]
}
@@ -115,41 +154,35 @@
k := 0
for i, c := range b {
if c == '\n' {
- p.buf.Write(b[k:i])
- p.buf.Write(detailSep)
+ s.buf.Write(b[k:i])
+ s.buf.Write(detailSep)
k = i + 1
}
}
- p.buf.Write(b[k:])
- p.fmt.needNewline = !p.fmt.inDetail
+ s.buf.Write(b[k:])
+ s.needNewline = !s.inDetail
+ } else if !s.inDetail {
+ s.buf.Write(b)
}
return len(b), nil
}
-// errPP wraps a pp to implement a Printer.
-type errPP pp
+// printer wraps a state to implement an xerrors.Printer.
+type printer state
-func (p *errPP) Print(args ...interface{}) {
- if !p.fmt.inDetail || p.fmt.plusV {
- if p.fmt.plusV {
- Fprint((*errPPState)(p), args...)
- } else {
- (*pp)(p).doPrint(args)
- }
+func (s *printer) Print(args ...interface{}) {
+ if !s.inDetail || s.printDetail {
+ fmt.Fprint((*state)(s), args...)
}
}
-func (p *errPP) Printf(format string, args ...interface{}) {
- if !p.fmt.inDetail || p.fmt.plusV {
- if p.fmt.plusV {
- Fprintf((*errPPState)(p), format, args...)
- } else {
- (*pp)(p).doPrintf(format, args)
- }
+func (s *printer) Printf(format string, args ...interface{}) {
+ if !s.inDetail || s.printDetail {
+ fmt.Fprintf((*state)(s), format, args...)
}
}
-func (p *errPP) Detail() bool {
- p.fmt.inDetail = true
- return p.fmt.plusV
+func (s *printer) Detail() bool {
+ s.inDetail = true
+ return s.printDetail
}
diff --git a/xerrors/fmt.go b/xerrors/fmt.go
index 931c04d..58dae3f 100644
--- a/xerrors/fmt.go
+++ b/xerrors/fmt.go
@@ -5,6 +5,7 @@
package xerrors
import (
+ "fmt"
"strings"
"golang.org/x/exp/xerrors/internal"
@@ -21,8 +22,9 @@
// with an Unwrap method returning it.
func Errorf(format string, a ...interface{}) error {
err, wrap := lastError(format, a)
+ format = formatPlusW(format)
if err == nil {
- return &noWrapError{Sprintf(format, a...), nil, Caller(1)}
+ return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)}
}
// TODO: this is not entirely correct. The error value could be
@@ -30,7 +32,7 @@
// 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]...)
+ msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
frame := Frame{}
if internal.EnableTrace {
frame = Caller(1)
@@ -41,6 +43,11 @@
return &noWrapError{msg, err, frame}
}
+// formatPlusW is used to avoid the vet check that will barf at %w.
+func formatPlusW(s string) string {
+ return s
+}
+
func lastError(format string, a []interface{}) (err error, wrap bool) {
wrap = strings.HasSuffix(format, ": %w")
if !wrap &&
@@ -68,9 +75,11 @@
}
func (e *noWrapError) Error() string {
- return Sprint(e)
+ return fmt.Sprint(e)
}
+func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(s, v, e) }
+
func (e *noWrapError) FormatError(p Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
@@ -84,9 +93,11 @@
}
func (e *wrapError) Error() string {
- return Sprint(e)
+ return fmt.Sprint(e)
}
+func (e *wrapError) Format(s fmt.State, v rune) { FormatError(s, v, e) }
+
func (e *wrapError) FormatError(p Printer) (next error) {
p.Print(e.msg)
e.frame.Format(p)
diff --git a/xerrors/fmt_test.go b/xerrors/fmt_test.go
index f874103..228f8e0 100644
--- a/xerrors/fmt_test.go
+++ b/xerrors/fmt_test.go
@@ -132,15 +132,11 @@
"\n and the 12 monkeys" +
"\n are laughing",
}, {
- err: simple,
- fmt: "%#v",
- want: "&xerrors_test.wrapped{msg:\"simple\", err:error(nil)}",
- }, {
err: framed,
fmt: "%+v",
want: "something:" +
"\n golang.org/x/exp/xerrors_test.TestErrorFormatter" +
- "\n .+/golang.org/x/exp/xerrors/fmt_test.go:98" +
+ "\n .+/golang.org/x/exp/xerrors/fmt_test.go:97" +
"\n something more",
regexp: true,
}, {
@@ -231,7 +227,7 @@
"\n one:" +
"\n somefile.go:123",
}, {
- err: wrapped{"", wrapped{"inner message", nil}},
+ err: &wrapped{"", &wrapped{"inner message", nil}},
fmt: "%+v",
want: "somefile.go:123" +
"\n - inner message:" +
@@ -268,17 +264,14 @@
}, {
err: simple,
fmt: "%🤪",
- want: "%!🤪(*xerrors_test.wrapped=&{simple <nil>})",
+ want: "%!🤪(*xerrors_test.wrapped)",
+ // For 1.13:
+ // want: "%!🤪(*xerrors_test.wrapped=&{simple <nil>})",
}, {
err: formatError("use fmt.Formatter"),
fmt: "%#v",
want: "use fmt.Formatter",
}, {
- err: wrapped{"using xerrors.Formatter",
- formatError("use fmt.Formatter")},
- fmt: "%#v",
- want: "xerrors_test.wrapped{msg:\"using xerrors.Formatter\", err:\"use fmt.Formatter\"}",
- }, {
err: fmtTwice("%s %s", "ok", panicValue{}),
fmt: "%s",
want: "ok %!s(PANIC=panic)/ok %!s(PANIC=panic)",
@@ -361,6 +354,10 @@
func (e wrapped) Error() string { return "should call Format" }
+func (e wrapped) Format(s fmt.State, verb rune) {
+ xerrors.FormatError(s, verb, &e)
+}
+
func (e wrapped) FormatError(p xerrors.Printer) (next error) {
p.Print(e.msg)
p.Detail()
@@ -388,6 +385,10 @@
func (e *withFrameAndMore) Error() string { return fmt.Sprint(e) }
+func (e *withFrameAndMore) Format(s fmt.State, v rune) {
+ xerrors.FormatError(s, v, e)
+}
+
func (e *withFrameAndMore) FormatError(p xerrors.Printer) (next error) {
p.Print("something")
if p.Detail() {
@@ -401,8 +402,9 @@
func (e spurious) Error() string { return fmt.Sprint(e) }
-func (e spurious) Format(fmt.State, rune) {
- panic("should never be called by one of the tests")
+// move to 1_12 test file
+func (e spurious) Format(s fmt.State, verb rune) {
+ xerrors.FormatError(s, verb, e)
}
func (e spurious) FormatError(p xerrors.Printer) (next error) {
@@ -472,6 +474,10 @@
func (e fmtTwiceErr) Error() string { return fmt.Sprint(e) }
+func (e fmtTwiceErr) Format(s fmt.State, verb rune) {
+ xerrors.FormatError(s, verb, e)
+}
+
func (e fmtTwiceErr) FormatError(p xerrors.Printer) (next error) {
p.Printf(e.format, e.args...)
p.Print("/")
diff --git a/xerrors/wrap_test.go b/xerrors/wrap_test.go
index c132727..9c25aab 100644
--- a/xerrors/wrap_test.go
+++ b/xerrors/wrap_test.go
@@ -104,7 +104,7 @@
&errP,
false,
}, {
- wrapped{nil},
+ errWrap{nil},
&errT,
false,
}, {
@@ -151,7 +151,7 @@
want error
}{
{nil, nil},
- {wrapped{nil}, nil},
+ {errWrap{nil}, nil},
{err1, nil},
{erra, err1},
{xerrors.Errorf("wrap 3: %w", erra), erra},
@@ -167,15 +167,15 @@
}
func TestOpaque(t *testing.T) {
- got := xerrors.Errorf("foo: %v", xerrors.Opaque(errorT{}))
+ got := fmt.Sprintf("%v", xerrors.Errorf("foo: %v", xerrors.Opaque(errorT{})))
want := "foo: errorT"
- if got.Error() != want {
+ if got != want {
t.Errorf("error without Format: got %v; want %v", got, want)
}
- got = xerrors.Errorf("foo: %v", xerrors.Opaque(errorD{}))
+ got = fmt.Sprintf("%v", xerrors.Errorf("foo: %v", xerrors.Opaque(errorD{})))
want = "foo: errorD"
- if got.Error() != want {
+ if got != want {
t.Errorf("error with Format: got %v; want %v", got, want)
}
}
@@ -195,8 +195,8 @@
return nil
}
-type wrapped struct{ error }
+type errWrap struct{ error }
-func (wrapped) Error() string { return "wrapped" }
+func (errWrap) Error() string { return "wrapped" }
-func (wrapped) Unwrap() error { return nil }
+func (errWrap) Unwrap() error { return nil }