errors/fmt: implementation of formatting interfaces

Change-Id: I9599b53a9c593aa560dd9fc3bd2d2cd0912afeae
Reviewed-on: https://go-review.googlesource.com/c/139500
Run-TryBot: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/errors/fmt/errors.go b/errors/fmt/errors.go
new file mode 100644
index 0000000..fbe8579
--- /dev/null
+++ b/errors/fmt/errors.go
@@ -0,0 +1,148 @@
+// 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 (
+	"golang.org/x/exp/errors"
+)
+
+// fmtError formats err according to verb, writing to p.
+// If it cannot handle the error, it does no formatting
+// and returns false.
+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.Format((*errPP)(w))
+		// TODO: This case is for supporting old error implementations.
+		// It may eventually disappear.
+		case interface{ FormatError(errors.Printer) error }:
+			err = v.FormatError((*errPP)(w))
+		case Formatter:
+			// Discard verb, but keep the flags. Discarding the verb prevents
+			// nested quoting and other unwanted behavior. Preserving flags
+			// recursively signals a request for detail, if interpreted as %+v.
+			w.fmt.fmtFlags = p.fmt.fmtFlags
+			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
+		}
+		w.buf.WriteString(sep)
+	}
+
+	if w != p {
+		p.fmtString(string(w.buf), verb)
+	}
+	return true
+}
+
+// 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.inDetail || p.fmt.plusV {
+		k := 0
+		if p.fmt.indent {
+			for i, c := range b {
+				if c == '\n' {
+					p.buf.Write(b[k:i])
+					p.buf.Write([]byte("\n    "))
+					k = i + 1
+				}
+			}
+		}
+		p.buf.Write(b[k:])
+	}
+	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 {
+		if p.fmt.indent {
+			Fprint((*errPPState)(p), args...)
+		} else {
+			(*pp)(p).doPrint(args)
+		}
+	}
+}
+
+func (p *errPP) Printf(format string, args ...interface{}) {
+	if !p.fmt.inDetail || p.fmt.plusV {
+		if p.fmt.indent {
+			Fprintf((*errPPState)(p), format, args...)
+		} else {
+			(*pp)(p).doPrintf(format, args)
+		}
+	}
+}
+
+func (p *errPP) Detail() bool {
+	p.fmt.indent = p.fmt.plusV
+	p.fmt.inDetail = true
+	(*errPPState)(p).Write([]byte("\n"))
+	return p.fmt.plusV
+}
diff --git a/errors/fmt/errors_test.go b/errors/fmt/errors_test.go
new file mode 100644
index 0000000..7b47440
--- /dev/null
+++ b/errors/fmt/errors_test.go
@@ -0,0 +1,262 @@
+// 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 (
+	"io"
+	"os"
+	"testing"
+
+	"golang.org/x/exp/errors"
+	"golang.org/x/exp/errors/fmt"
+)
+
+func TestErrorFormatter(t *testing.T) {
+	var (
+		simple   = &wrapped{"simple", nil}
+		elephant = &wrapped{
+			"can't adumbrate elephant",
+			detailed{},
+		}
+		transition = &wrapped2{"elephant still on strike", detailed{}}
+		nonascii   = &wrapped{"café", nil}
+		newline    = &wrapped{"msg with\nnewline",
+			&wrapped{"and another\none", nil}}
+		fallback  = &wrapped{"fallback", os.ErrNotExist}
+		oldAndNew = &wrapped{"new style", formatError("old style")}
+	)
+	testCases := []struct {
+		err  error
+		fmt  string
+		want string
+	}{{
+		err:  simple,
+		fmt:  "%s",
+		want: "simple",
+	}, {
+		err:  elephant,
+		fmt:  "%s",
+		want: "can't adumbrate elephant: out of peanuts",
+	}, {
+		err:  simple,
+		fmt:  "%+v",
+		want: "simple\n    somefile.go:123",
+	}, {
+		err: elephant,
+		fmt: "%+v",
+		want: `can't adumbrate elephant
+    somefile.go:123
+--- out of peanuts
+    the elephant is on strike
+    and the 12 monkeys
+    are laughing`,
+	}, {
+		err: transition,
+		fmt: "%+v",
+		want: `elephant still on strike
+    somefile.go:123
+--- out of peanuts
+    the elephant is on strike
+    and the 12 monkeys
+    are laughing`,
+	}, {
+		err:  simple,
+		fmt:  "%#v",
+		want: "&fmt_test.wrapped{msg:\"simple\", err:error(nil)}",
+	}, {
+		err:  fmtTwice("Hello World!"),
+		fmt:  "%#v",
+		want: "2 times Hello World!",
+	}, {
+		err:  fallback,
+		fmt:  "%s",
+		want: "fallback: file does not exist",
+	}, {
+		err:  fallback,
+		fmt:  "%+v",
+		want: "fallback\n    somefile.go:123\n--- file does not exist",
+	}, {
+		err:  oldAndNew,
+		fmt:  "%v",
+		want: "new style: old style",
+	}, {
+		err:  oldAndNew,
+		fmt:  "%q",
+		want: `"new style: old style"`,
+	}, {
+		err: oldAndNew,
+		fmt: "%+v",
+		// Note the extra indentation.
+		want: "new style\n    somefile.go:123\n--- old style\n    otherfile.go:456",
+	}, {
+		err:  simple,
+		fmt:  "%-12s",
+		want: "simple      ",
+	}, {
+		// Don't use formatting flags for detailed view.
+		err:  simple,
+		fmt:  "%+12v",
+		want: "simple\n    somefile.go:123",
+	}, {
+		err:  elephant,
+		fmt:  "%+50s",
+		want: "          can't adumbrate elephant: out of peanuts",
+	}, {
+		err:  nonascii,
+		fmt:  "%q",
+		want: `"café"`,
+	}, {
+		err:  nonascii,
+		fmt:  "%+q",
+		want: `"caf\u00e9"`,
+	}, {
+		err:  simple,
+		fmt:  "% x",
+		want: "73 69 6d 70 6c 65",
+	}, {
+		err:  newline,
+		fmt:  "%s",
+		want: "msg with\nnewline: and another\none",
+	}, {
+		err:  nil,
+		fmt:  "%+v",
+		want: "<nil>",
+	}, {
+		err:  (*wrapped)(nil),
+		fmt:  "%+v",
+		want: "<nil>",
+	}, {
+		err:  simple,
+		fmt:  "%T",
+		want: "*fmt_test.wrapped",
+	}, {
+		err:  simple,
+		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=panic)/ok %!s(PANIC=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)
+			if got != tc.want {
+				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 "should call Format" }
+
+func (e wrapped) Format(p errors.Printer) (next error) {
+	p.Print(e.msg)
+	p.Detail()
+	p.Print("somefile.go:123")
+	return e.err
+}
+
+type wrapped2 struct {
+	msg string
+	err error
+}
+
+func (e wrapped2) Error() string { return "should call Format" }
+
+func (e wrapped2) FormatError(p errors.Printer) (next error) {
+	p.Print(e.msg)
+	p.Detail()
+	p.Print("somefile.go:123")
+	return e.err
+}
+
+var _ errors.Formatter = detailed{}
+
+type detailed struct{}
+
+func (e detailed) Error() string { return fmt.Sprint(e) }
+
+func (detailed) Format(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
+}
+
+// formatError is an error implementing Format instead of errors.Formatter.
+// The implementation mimics the implementation of github.com/pkg/errors,
+// including that
+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) Format(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") }
diff --git a/errors/fmt/format.go b/errors/fmt/format.go
index 91103f2..83872fe 100644
--- a/errors/fmt/format.go
+++ b/errors/fmt/format.go
@@ -34,6 +34,10 @@
 	// different, flagless formats set at the top level.
 	plusV  bool
 	sharpV bool
+
+	// error-related flags. plusV indicates detail mode.
+	inDetail bool
+	indent   bool
 }
 
 // A fmt is the raw formatter used by Printf etc.
diff --git a/errors/fmt/print.go b/errors/fmt/print.go
index f67f805..8af4fef 100644
--- a/errors/fmt/print.go
+++ b/errors/fmt/print.go
@@ -570,6 +570,11 @@
 		formatter.Format(p, verb)
 		return
 	}
+	if err, ok := p.arg.(error); ok {
+		handled = true
+		defer p.catchPanic(p.arg, verb)
+		return fmtError(p, verb, err)
+	}
 
 	// If we're doing Go syntax and the argument knows how to supply it, take care of it now.
 	if p.fmt.sharpV {
@@ -586,18 +591,7 @@
 		// 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)
-				p.fmtString(v.Error(), verb)
-				return
-
-			case Stringer:
+			if v, ok := p.arg.(Stringer); ok {
 				handled = true
 				defer p.catchPanic(p.arg, verb)
 				p.fmtString(v.String(), verb)