errors/fmt: don't wrap in Errorf by default

Instead, wrap when the format ends in ": %w" instead
of ": %s" or ": %v"

Also, improved test coverage.

Change-Id: I42125f9c201230e619aaf0d61593154df8921993
Reviewed-on: https://go-review.googlesource.com/c/151258
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/errors/fmt/errors.go b/errors/fmt/errors.go
index a4de524..4bc00b3 100644
--- a/errors/fmt/errors.go
+++ b/errors/fmt/errors.go
@@ -12,15 +12,20 @@
 	"golang.org/x/exp/errors/internal"
 )
 
-// Errorf formats according to a format specifier and returns the string
-// as a value that satisfies error.
+// 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.
+
+// 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 := lastError(format, a)
+	err, wrap := lastError(format, a)
 	if err == nil {
-		return &simpleErr{Sprintf(format, a...), errors.Caller(1)}
+		return &noWrapError{Sprintf(format, a...), nil, errors.Caller(1)}
 	}
 
 	// TODO: this is not entirely correct. The error value could be
@@ -28,67 +33,70 @@
 	// substitutions. With relatively small changes to doPrintf we can
 	// have it optionally ignore extra arguments and pass the argument
 	// list in its entirety.
-	format = format[:len(format)-len(": %s")]
-	if !internal.EnableTrace {
-		return &withChain{msg: Sprintf(format, a[:len(a)-1]...), err: err}
+	msg := Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
+	frame := errors.Frame{}
+	if internal.EnableTrace {
+		frame = errors.Caller(1)
 	}
-	return &withChain{
-		msg:   Sprintf(format, a[:len(a)-1]...),
-		err:   err,
-		frame: errors.Caller(1),
+	if wrap {
+		return &wrapError{msg, err, frame}
 	}
+	return &noWrapError{msg, err, frame}
 }
 
-func lastError(format string, a []interface{}) error {
-	if !strings.HasSuffix(format, ": %s") && !strings.HasSuffix(format, ": %v") {
-		return nil
+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
+		return nil, false
 	}
 
 	err, ok := a[len(a)-1].(error)
 	if !ok {
-		return nil
+		return nil, false
 	}
 
-	return err
+	return err, wrap
 }
 
-type simpleErr struct {
-	msg   string
-	frame errors.Frame
-}
-
-func (e *simpleErr) Error() string {
-	return Sprint(e)
-}
-
-func (e *simpleErr) Format(p errors.Printer) (next error) {
-	p.Print(e.msg)
-	e.frame.Format(p)
-	return nil
-}
-
-type withChain struct {
-	// TODO: add frame information
+type noWrapError struct {
 	msg   string
 	err   error
 	frame errors.Frame
 }
 
-func (e *withChain) Error() string {
+func (e *noWrapError) Error() string {
 	return Sprint(e)
 }
 
-func (e *withChain) Format(p errors.Printer) (next error) {
+func (e *noWrapError) Format(p errors.Printer) (next error) {
 	p.Print(e.msg)
 	e.frame.Format(p)
 	return e.err
 }
 
-func (e *withChain) Unwrap() error {
+type wrapError struct {
+	msg   string
+	err   error
+	frame errors.Frame
+}
+
+func (e *wrapError) Error() string {
+	return Sprint(e)
+}
+
+func (e *wrapError) Format(p errors.Printer) (next error) {
+	p.Print(e.msg)
+	e.frame.Format(p)
+	return e.err
+}
+
+func (e *wrapError) Unwrap() error {
 	return e.err
 }
 
diff --git a/errors/fmt/errors_test.go b/errors/fmt/errors_test.go
index ff7e661..554992b 100644
--- a/errors/fmt/errors_test.go
+++ b/errors/fmt/errors_test.go
@@ -30,6 +30,12 @@
 		got  error
 		want []string
 	}{{
+		fmt.Errorf("no args"),
+		chain("no args/path.TestErrorf/path.go:xxx"),
+	}, {
+		fmt.Errorf("no args: %s"),
+		chain("no args: %!s(MISSING)/path.TestErrorf/path.go:xxx"),
+	}, {
 		fmt.Errorf("nounwrap: %s", "simple"),
 		chain(`nounwrap: simple/path.TestErrorf/path.go:xxx`),
 	}, {
@@ -37,15 +43,23 @@
 		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("wraps: %s", chained),
-		chain("wraps:wraps/path.TestErrorf/path.go:xxx",
+		fmt.Errorf("nowrapv: %v", chained),
+		chain("nowrapv/path.TestErrorf/path.go:xxx",
 			"chained/somefile.go:xxx"),
 	}, {
-		fmt.Errorf("wrapv: %v", chained),
-		chain("wraps:wrapv/path.TestErrorf/path.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),
@@ -135,7 +149,7 @@
 		fmt: "%+v",
 		want: "something:" +
 			"\n    golang.org/x/exp/errors/fmt_test.TestErrorFormatter" +
-			"\n        /Users/mpvl/dev/go/text/src/golang.org/x/exp/errors/fmt/errors_test.go:84" +
+			"\n        /Users/mpvl/dev/go/text/src/golang.org/x/exp/errors/fmt/errors_test.go:98" +
 			"\n    something more",
 	}, {
 		err:  fmtTwice("Hello World!"),
@@ -386,7 +400,7 @@
 func errToParts(err error) (a []string) {
 	for err != nil {
 		var p testPrinter
-		if _, ok := err.(errors.Wrapper); ok {
+		if errors.Unwrap(err) != nil {
 			p.str += "wraps:"
 		}
 		f, ok := err.(errors.Formatter)
diff --git a/errors/wrap_test.go b/errors/wrap_test.go
index 4a5c663..c4d6fc6 100644
--- a/errors/wrap_test.go
+++ b/errors/wrap_test.go
@@ -14,10 +14,10 @@
 
 func TestIs(t *testing.T) {
 	err1 := errors.New("1")
-	erra := fmt.Errorf("wrap 2: %v", err1)
-	errb := fmt.Errorf("wrap 3: %v", erra)
+	erra := fmt.Errorf("wrap 2: %w", err1)
+	errb := fmt.Errorf("wrap 3: %w", erra)
 	erro := errors.Opaque(err1)
-	errco := fmt.Errorf("opaque: %v", erro)
+	errco := fmt.Errorf("opaque: %w", erro)
 
 	err3 := errors.New("3")
 
@@ -89,7 +89,7 @@
 		target interface{}
 		match  bool
 	}{{
-		fmt.Errorf("pittied the fool: %v", errorT{}),
+		fmt.Errorf("pittied the fool: %w", errorT{}),
 		&errT,
 		true,
 	}, {
@@ -144,7 +144,7 @@
 
 func TestUnwrap(t *testing.T) {
 	err1 := errors.New("1")
-	erra := fmt.Errorf("wrap 2: %v", err1)
+	erra := fmt.Errorf("wrap 2: %w", err1)
 	erro := errors.Opaque(err1)
 
 	testCases := []struct {
@@ -155,10 +155,10 @@
 		{wrapped{nil}, nil},
 		{err1, nil},
 		{erra, err1},
-		{fmt.Errorf("wrap 3: %v", erra), erra},
+		{fmt.Errorf("wrap 3: %w", erra), erra},
 
 		{erro, nil},
-		{fmt.Errorf("opaque: %v", erro), erro},
+		{fmt.Errorf("opaque: %w", erro), erro},
 	}
 	for _, tc := range testCases {
 		if got := errors.Unwrap(tc.err); got != tc.want {